The days of adversaries poking around the network perimeter looking for a way in are long gone. Having learned from earlier mistakes, firewall administrators now know all the tricks, and, as a result, adversaries have moved to different tactics.
In the age where everything is connected to the Internet, attacking and exploiting web servers is a routine practice among adversaries. Time and time again, patient zero ends up being a vulnerable web server that an adversary exploited to gain unauthorized entry. If you want to keep a breach from going from bad to worse, then it’s important to know what to monitor on a web server.
Typically when adversaries own a web server, they’ll try to upload a web shell, which will allow them to execute commands on the server, gathering information, elevating privileges, and eventually pivoting deeper into the environment. All variety of threat actors use web shells, and they are very prevalent. However, this doesn’t mean that we expect to see a web shell every time a server is popped.
What do we look for on a Windows server running IIS?
We expect to see w3wp.exe in the chain of processes.
StackExchange offers a pretty good explanation of the the function of w3wp.exe:
“An Internet Information Services (IIS) worker process is a Windows process (w3wp.exe) which runs web applications, and is responsible for handling requests sent to a web Server for a specific application pool.”
Where in the chain of processes should we expect w3wp.exe?
Let’s say an adversary exploited a web application, uploaded a web shell, and executed a simple ping command. The process of execution should look like this:
- Services.exe will spawn svchost.exe (with -k iissvcs)
- Svchost.exe will spawn w3wp.exe (with parameters calling the application pool, config file, etc)
- W3wp.exe will spawn cmd.exe
- Cmd.exe will spawn ping.exe
So it makes sense to monitor all child processes associated with w3wp.exe, right? Yes, but there is a caveat. The downside with this is that legitimate applications will also spawn w3wp.exe to issue commands that might appear to be reconnaissance or status checks. With that being said, just because you see w3wp.exe executing doesn’t mean you need to isolate the server and phone the authorities. The tricky part is knowing what is legitimate server/application activity and what is malicious activity being performed by an adversary.
If you’re curious, you can run a PowerShell script on a web server running IIS to see how many legitimate parent/child processes related to w3wp.exe actually execute on that server. I’ve included a PowerShell script that you can use to do this at the end of this blog.
W3wp.exe activity IRL
Case #1 (w3wp.exe spawns cmd.exe):
As discussed previously, we see svchost.exe spawning IIS.
Svchost.exe then spawns w3wp.exe. We can see from the screenshot that this server is running SharePoint.
W3wp.exe then launched cmd.exe and issued echo commands, which redirected the output to a file called t.aspx—not to the console. The text that was written to the files was code that you would typically see within a web shell.
Upon examination by the detection engineering team, we decided to treat this as suspicious activity, and we notified the customer accordingly. We told them that files were written to disk and outlined that these files appeared to constitute a web shell. However, it’s important to understand that we didn’t actually detect web shell usage, just activity that was suggestive of a web shell.
So what did we detect?
This activity tripped on multiple detectors that alert us to w3wp.exe process-spawning activity. In this case, the customer later confirmed that it was legitimate application activity.
Case #2 (w3wp.exe spawns powershell.exe):
As with case #1, we see the same parent-child relationship. Services.exe spawned svchost.exe to launch IIS.
We see similar command line syntax as with case #1, but this SharePoint server was external facing. As a note, it’s best to monitor all web servers, not just the external facing ones.
Instead of w3wp.exe spawning cmd.exe, we see w3wp.exe spawning powershell.exe with an encoded payload. It’s worth remembering that our detector triggers when w3wp.exe spawns a child process.
Instead of a Web shell being written to disk and commands being executed from the Web shell, as would be expected, we see w3wp.exe spawning powershell.exe. In this case, PowerShell queried a domain name system (DNS) server to obtain further commands stored in TXT records. Domain registrars typically allow you to manipulate TXT records to prove that you own a domain. Here we see that TXT records were used to store encoded commands. This is a unique way to pull down code.
Now the chain of execution continues with w3wp.exe (grandparent) spawning powershell.exe (parent) and powershell.exe, in turn, spawning cmd.exe. Reconnaissance commands were executed via cmd.exe. In this case, nltest was executed to determine the domain trust for the endpoint’s active directory domain.
We observed commands that looked similar to PowerSploit cmdlets in the chain of execution. The adversary seemed to be using these commands as an additional reconnaissance phase.
The output of these processes were fed to its child processes.
Finally, based on the reconnaissance activity we observed here, it seems that the adversary was focused on a particular user. They must have determined that this user had logged onto the server before, and, therefore, the adversary was seemingly attempting to determine the user’s privilege level.
How does this chain of execution look in a tool such as Carbon Black?
What process was observed that started this all?
You guessed it: w3wp.exe.
Our detection engineering team alerted the customer of the malicious activity associated with w3wp.exe. It was later confirmed that the adversary exploited a remote code execution-enabling vulnerability in SharePoint (CVE-2019-0604) to execute these commands on the server.
Detection opportunity
Several of our detectors were triggered to flag this activity. The one relating specifically to w3wp.exe looks for instances where it spawns a child process. In this case, that child process was powershell.exe.
Conclusion
If you’re running SharePoint servers in your environment, which most all Windows enterprises are, closely monitor child processes related to w3wp.exe, especially if they’re external facing SharePoint servers. By monitoring this IIS worker process and its child processes, you will certainly be able to quickly catch an adversary attempting to use the server to gain entry into the environment.
PowerShell script for enumerating w3wp.exe processes
File #1: Get-W3WP.ps1
<#
.SYNOPSIS
A Powershell script that will output the child process, along with its command line arguments, for w3wp.exe processes.
.EXAMPLE
./Get-W3WP.ps1
.NOTES
The script monitors cmd.exe child procs for w3wp.exe. The 2nd script is needed to work with this script, Get-W3WPChildProc.ps1.
Sam Vega (Red Canary)
#>
While($true) {
$w3wp = Get-WmiObject -Class Win32_Process -Filter "Name='w3wp.exe'"
If($w3wp) {
$p1 = $w3wp | Select-Object ProcessID, CreationDate
$p2 = $p1.ProcessID
Get-WmiObject -Class Win32_Process -Filter "ParentProcessId=$p2" | Where-Object ($_.Name -eq "cmd.exe") | & .\Get-W3WPChildProc.ps1
}
}
File #2: Get-W3WPChildProc.ps1
<#
.SYNOPSIS
A Powershell script that will output the child process, along with its command line arguments, for w3wp.exe processes.
.EXAMPLE
./Get-W3WP.ps1
.NOTES
The script monitors cmd.exe child procs for w3wp.exe. The 2nd script is needed to work with this script, Get-W3WP.ps1.
Credit: Adam Roben for pstree.ps1 (https://gist.github.com/aroben/5542538)
Modified: Sam Vega (Red Canary)
#>
While($true) {
$w3wp = Get-WmiObject -Class Win32_Process -Filter "Name='w3wp.exe'"
If($w3wp) {
$p = $w3wp | Select-Object ProcessID
$ProcessesById = @{}
foreach ($Process in (Get-WMIObject -Class Win32_Process)) {
$ProcessesById[$Process.ProcessId] = $Process
}
$ProcessesWithoutParents = @()
$ProcessesByParent = @{}
foreach ($Pair in $ProcessesById.GetEnumerator()) {
$Process = $Pair.Value
If (($Process.ParentProcessId -eq 0) -or !$ProcessesById.ContainsKey($Process.ParentProcessId)) {
$ProcessesWithoutParents += $Process
continue
}
If (!$ProcessesByParent.ContainsKey($Process.ParentProcessId)) {
$ProcessesByParent[$Process.ParentProcessId] = @()
}
$Siblings = $ProcessesByParent[$Process.ParentProcessId]
$Siblings += $Process
$ProcessesByParent[$Process.ParentProcessId] = $Siblings
}
function Show-ProcessTree([UInt32]$ProcessId, $IndentLevel) {
$Process = $ProcessesById[$ProcessId]
$Indent = " " * $IndentLevel
If ($Process.CommandLine) {
$Description = $Process.CommandLine
} else {
$Description = $Process.Caption
}
foreach ($Child in ($ProcessesByParent[$ProcessId] | Sort-Object CreationDate)) {
Write-Output ("{0,6} {1}" -f "PID", "Command Line")
Write-Output ("{0,6} {1}" -f "---", "------------")
Write-Output ("{0,6}{1} {2}" -f $Process.ProcessId, $Indent, $Description)
Show-ProcessTree $Child.ProcessId ($IndentLevel + 4)
}
}
Show-ProcessTree $p.ProcessId 0
}
}