Powershell Empire - Evading Nessus Plugin 99592

By in ,

Back in November 2017, Tenable Network Security released a Nessus plugin that was capable of identifying the HTTP Listeners used by the Empire post-exploitation framework [1]. Since Empire is a tool that we often use during pentests and red team engagements, we felt inspired to develop a patch for Empire’s HTTP Listeners (both HTTP and HTTP_Com) that renders this plugin ineffective.

We succeeded in doing so within a month, and pushed our updates upstream to the Empire Project shortly after doing so [2][3]. This blog post illustrates the tactics, techniques, and procedures used to achieve this result, and makes the case that signature-based methods are not enough to deter a motivated adversary.

About Nessus Plugin 99592

When Tenable released Nessus Plugin 99592 back in November 2017, they also released a blog post that describes how the plugin works and how the signature that it uses is constructed [1]. It’s a pretty interesting read, so we definitely recommend checking it out if you have the time.

Original Blog Post:

Nessus Plugin 99592:

We have also provided a high-level summary in the next section.

How Plugin 99592 Works

Jacob Baines, the author of Plugin 99592, made an interesting point in his blog post about the plugin: “a lot of work has gone into making [Empire] agents difficult to find . . . less has been done to hide listeners” [1]. Prior to our update, this was absolutely true, especially if you consider the sheer number of attributes that were available from which to construct a signature that could be used to detect Empire’s HTTP Listeners.

Here are the attributes identified by Baines in his post:

  • Nearly all HTTP responses from the Listener included a hardcoded Werkzeug error message (Empire uses Flask, which in turn is built on top of Werkzeug. Both Flask and Werkzeug are Python frameworks for building web applications). [1]
  • The 404 page was always 233 bytes in length [1]
  • Empire was using atypical cache-control headers [1]
  • Making a request to the HTTP Listener’s root directory always resulted in a 404 response, while making a request to any other route resulted in a 200 response [1]

The last point was particularly interesting. There was no route defined for the root directory. This meant that anytime a user made a request to the root directory, he or she would receive the hardcoded Werkzeug 404 response shown in the console output below.

Console Output:

┌─[root@somberlain] - [~/Empire/lib/listeners] - [8550]
└─[$] curl -i
Content-Type: text/html
Content-Length: 233
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Server: Microsoft-IIS/7.5
Date: Thu, 28 Dec 2017 05:06:07 GMT

<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>

On the other hand, sending a request to any other route — including /index.html — would result in a 200 response containing text from a default index page.

Console Output:

┌─[root@somberlein] - [~/Empire/lib/listeners] - [8551]
└─[$] curl -i [23:06:07]
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 173
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Server: Microsoft-IIS/7.5
Date: Thu, 28 Dec 2017 05:07:16 GMT

<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>

Baines astutely pointed out that this behavior was actually rather strange for a production web server, and made the Empire HTTP Listener stand out like a sore thumb within a production environment [1]. Most “real” production servers would actually do the opposite: requests to both the root directory and /index.html would result in a default page, whereas requests to unavailable resources would result in a 404 error).

How Production Servers Behave (default setup)


How Empire’s HTTP listener behaved (prior to our updated)

To make matters worse, Empire’s HTTP Listener was sending Server headers associated with Microsoft IIS 7.5, despite issuing error messages that could be immediately traced back to Werkzeug [1].

Tenable’s development team seized upon these inconsistencies and used them to create a Nessus plugin that was able to detect attackers running Empire [1].

Fixing the Problem

The HTTP Listener’s atypical behavior was what made it so easy to detect. With this in mind, we focused on making the HTTP Listener’s behavior more generic when developing our signature evasion. We also focused on replacing the hardcoded Werkzeug errors with default content used by Microsoft IIS 7.5, which is much more commonly seen in production environments.

We added the following enhancements to Empire’s HTTP and HTTP_Com Listeners, allowing them to evade the signature-based detection used by Nessus Plugin 99592:

  • The HTTP Listener behaves more like a “real” web server (which makes its signature more generic)
  • The Content-Length for default and error pages is now randomly set on startup (since Content-Length was used as part of the plugin’s signature)

To make the HTTP Listener seem more like a generic web server, we first added routes in Flask for the following endpoints:

  • /
  • /index.html

Since our update, users are now presented with a 200 response containing the default index page for IIS 7.5.

However, when users attempt to access any route other than / or /index.html, they are now presented with a generic IIS 404 page.

This makes the HTTP Listener respond identically to a generic IIS 7.5 web server, and eliminates the easily identifiable Werkzeug 404 text that was previously in use.

To make it difficult to fingerprint the HTTP Listener based on Content-Length, we simply added some code that appends a whitespace buffer to the end of the index and 404 pages. The length of this whitespace buffer is randomly generated on startup but consistent across all requests to avoid raising red flags.

We decided not to do anything about the atypical cache-control headers, since RFC violations are actually not particularly atypical and therefore do not represent a signature unique to Empire. More often than not, non-compliant cache-control headers are indicative of a load balancer or layer 7 security appliance.


In the following screenshot, we perform a Nessus scan using Plugin 99592 against a host running an Empire HTTP Listener. As you can see, the plugin detects the HTTP Listener.


Next, we perform the same Nessus scan, but this time against a host running our modified HTTP Listener. Notice how our patch allows the HTTP Listener to successfully avoid detection.


Once we had verified that our evasion attempt was successful, we made a pull request to the Empire Project on Github. Our changes were added to the project in version 2.5 [4].

The original pull requests and updated changelogs can be found below:

Nessus’s signature-based Empire detection has been defeated: for now. Efforts to both create and hide from signature-based detection mechanisms are ultimately a game of cat and mouse.

It is paramount that defenders be cognizant of the limitations of signature-based tools. While they can be a highly effective means of identifying technological weaknesses such as outdated software, they tend to fall short when it comes to identifying human beings. Unlike the outdated Apache server sitting within your network, a motivated adversary will actively attempt to thwart your detection mechanisms.

Conversely, attackers need to be acutely aware of the signatures generated by their tools. Default configurations should not be relied upon. Tactics, techniques, and procedures should be constantly updated, since SOC analysts will eventually pick up on them.

All defensive and offensive systems have inherent limitations. Security, whether offensive or defensive, is the art of reducing those limitations to a point where they are manageable.


[1] https://www.tenable.com/blog/identifying-empire-http-listeners

[2] https://github.com/EmpireProject/Empire/pull/890
[3] https://github.com/EmpireProject/Empire/pull/964
[4] https://github.com/EmpireProject/Empire/blob/8cfd54ff516050564e8de4d147227eaf1190cc48/changelog

Leave a reply

Your email address will not be published. Required fields are marked *