Fall of an Empire

While setting up my C2 nodes and redirectors for an engagement, I decided to explore Empire and Meterpreter's default setups. I thought to myself - who would be foolish to allow anyone on the Internet to connect to their C2 servers, much less what information could be extracted in a legal manner.  The results were interesting.  The first exploratory investigation revealed twenty+ C2 nodes running stock Empire or Meterpreter reverse http/s sessions.

History Of Failure

Coding is hard.  Empire and Metasploit projects have a history of Remote Code Execution vulnerabilities. Which means Red Teams need to go to great lengths in order to manage risks and mitigate evil entities from compromising their crown jewels which includes active agents and client data.

Empire RCE
Metasploit RCE


Empire, now in beta for 2.0 includes both Powershell Empire as well as the python version Empyre. The Empire listener is based on BaseHTTPServer in Python and provides an extraction layer on top of it. Let's take a look at the HTTP headers that are present in default Empire configuration.

Empire Headers

Using the HTTP request of GET / HTTP/1.1, the following headers were returned.

HTTP/1.0 200 OK
Server: Microsoft-IIS/7.5
Date: Wed, 05 Apr 2017 18:26:10 GMT

The thing that stands out here is the general lack of headers that would normally be present in a request. Also, the fact that we used HTTP/1.1 as the protocol, but the reply is still for HTTP/1.0

Empire Default Page

<html><body><h1>It works!</h1><p>This is the default web page for this server.</p><p>The web server software is running but no content has been added, yet.</p></body></html>

Hashes Of Defaul Page

MD5: 885ecd7910c988f1f15fcacca5e1734e
SHA1: b642227fbc703af1a67edb665241fc709ecd6f6e
SHA2: a58fb107072d9523114a1b1f17fbf5e7a8b96da7783f24d84f83df34abc48576

Finding Empire Listeners With Shodan

Shodan is a search engine for Security Researchers and other inquisitive mindsets.  Shodan routinely scans common ports across the Internet to enable public consumption and inquiry of the resulting data set.  APIs are also provided for those who wish to work smarter, not harder.  

Using the common headers, and default web page listed above, we are able to narrow down the list of possible Empire C2 nodes on the Internet with a simple query.

'Microsoft-IIS/7.5' 'It works!' -'Content-Type' -'Set-Cookie'

You'll notice that the results returned all are HTTP/1.0 with matching profiles that we scoped out above.


Finding An Exception In Empire

The HTTP module in Empire is located in lib/common/http.py. Go ahead and use your favorite text editor to open that up, and have a look around at the code.

In the class RequestHandler and method do_GET we have the following piece of code for handling parsing of cookie data.

if cookie:
# search for a SESSIONID value in the cookie
parts = cookie.split(";")
for part in parts:
if "SESSIONID" in part:
# extract the sessionID value
name, sessionID = part.split("=")

name, sessionID = part.split("=")
If there is more than one equal sign in the cookie field, it'll continue to split on equal signs. That line should be this. 
name, sessionID = part.split("=", 1)
In order to limit the number of items to one.

Let's go ahead and try to exploit this from the client side with the following request.

curl http://target:port --Cookie 'SESSIONID=id=id'

Curl will return the following error, because Python threw an exception upon parsing the cookies.

curl: (52) Empty reply from server

Changing Default Values

While executing a Red Team engagement, it is STRONGLY recommended to change the default values of tools that you use, whether it be a scanner or C2 infrastructure. This will make it harder for Blue Team elements to detect portions of your activity. You should also either utilize Empire's whitelisting feature or setup proper access control lists.  There is no excuse for leaving your C2 node exposed to the entire Internet.

You should have noticed while browsing http.py that the default page served is also located in that file in the function named default_page.

In order to change the default server name, you must edit the configuration in the empire.db file located in data/. Open it up by using sqlite3 data/empire.db. You can view the current setting by typing SELECT server_version from config;
In order to update it, something like the following will do the job.

update config set server_version = 'nginx' where server_version = 'Microsoft-IIS/7.5';

Going Beyond Shodan

Scans.io is another great resource for looking at Internet-wide scans including those for HTTPS sites. The scan sets are huge, but offer a very current view of HTTPs servers across the globe. Data is in JSON format, and the default page is saved in base64 format within each node.

zgrep 'PGh0bWw+PGJvZHk+PGgxPkl0IHdvcmtzITwvaDE+PHA+VGhpcyBpcyB0aGUgZGVmYXVsdCB3ZWIgcGFnZSBmb3IgdGhpcyBzZXJ2ZXIuPC9wPjxwPlRoZSB3ZWIgc2VydmVyIHNvZnR3YXJlIGlzIHJ1bm5pbmcgYnV0IG5vIGNvbnRlbnQgaGFzIGJlZW4gYWRkZWQsIHlldC48L3A+PC9ib2R5PjwvaHRtbD4=' 20170221-https.gz > /tmp/results.json

This may take several minutes to run, as the datasets are generally several gigabytes in size. The result will be a file containing JSON data for each host that returned the default Empire HTML. You can parse this file and extract each IP address that should be tested, and then feed them into the script below.

Automating Detection With Python

Use the following to run this script.

#!/usr/bin/env python3

from urllib.request import build_opener, HTTPSHandler

from http.client import RemoteDisconnected

from hashlib import sha256

from sys import argv, exit

from binascii import hexlify

import ssl

class NoException(Exception):


steps = [


'url': 'https://{}/',

'response': 'a58fb107072d9523114a1b1f17fbf5e7a8b96da7783f24d84f83df34abc48576',

'exception': NoException



'url': 'https://{}/',

'cookie': 'SESSIONID=id=id',

'exception': RemoteDisconnected



def main():

if len(argv) != 2:

print("Usage: %s <ip>" % argv[0])


context = ssl._create_unverified_context()

for step in steps:

opener = build_opener(HTTPSHandler(context=context))

if 'cookie' in step:

opener.addheaders.append(('Cookie', step['cookie']))


resp = opener.open(step['url'].format(argv[1]))

except step['exception']:

print("[+] Exception correctly called")

except Exception:

print("[!] Unexpected exception found")

print("[-] IP %s is not an Empire listener" % argv[1])


data = resp.read()

if 'response' in step:

shasum = sha256()


if hexlify(shasum.digest()).decode('utf-8') == step['response']:

print("[+] Response matches")


print("[!] Response doesn't match")

print("[-] IP %s is not an Empire listener" % argv[1])


print("[+] IP %s is an Empire listener" % argv[1])

if __name__=='__main__':