Web Shell & Server Compromise¶
When to use this
An internet-facing (or internal) web server is behaving oddly: an unfamiliar script in the web root, the web-server process (w3wp.exe, httpd, tomcat, php-fpm) spawning shells, or an exploit alert on the app. Web shells are a common initial access and persistence foothold — a small script that turns an HTTP request into command execution on the server.
1. Triage (first 15 minutes)¶
- Confirm: is there a web shell on disk (a script in the web root the developers didn't put there), and/or is the web-server process spawning child processes (cmd/powershell/sh)?
- Identify the server role and exposure (internet-facing? what app/CMS/version?). A known-vulnerable app + a new script = exploited.
- Preserve the web shell file and the relevant web-server access logs before anything is cleaned — the access log ties requests to the shell and to the attacker's IP.
- If actively being used, network-isolate the server (keep it running for RAM/artifacts).
2. Collect¶
| Source | What | Where |
|---|---|---|
| Web server access/error logs | Requests to the shell, attacker IP, exploit attempts | IIS (%SystemDrive%\inetpub\logs\LogFiles), Apache/Nginx (/var/log/…), Tomcat |
| The web shell file(s) | The payload itself | Web root; hash it, read it |
| Host triage | Execution, persistence, lateral movement from the server | KAPE / Velociraptor (Linux artifacts too) |
| $MFT / $UsnJrnl | When the shell was written (and by what) | File creation time = upload time |
| Network | The exploit request + any follow-on C2/lateral | Zeek http.log / Arkime |
| App/DB logs | SQLi, auth bypass, admin actions | Application-specific |
3. Analyse¶
- Find the shell: web-server process spawning a shell is the loudest signal (suspicious parents —
w3wp.exe/httpd/tomcat→cmd/powershell/sh/whoami). On disk, a recently created script in the web root with suspicious content (eval,system(,exec(,passthru(,Runtime.exec, base64 blobs). Hash it and search the fleet — attackers drop the same shell on multiple servers. - When and how it got there:
$MFT/$UsnJrnlcreation time of the shell = the upload moment; correlate with the access log entry (aPUT/POSTto that path, or an exploit request just before). This gives the initial-access vector (upload feature, deserialization, RCE, path traversal, stolen creds). - What the attacker did through it: the access log shows every request to the shell —
POSTbodies (if logged) or the follow-on child processes (Sysmon 1) reveal the commands:whoami,net user, downloading tools, dumping creds, moving laterally. - Attacker IP & scope: the source IP(s) hitting the shell → search for other paths they touched, other servers, and pre-compromise recon (
404scanning). One shell often implies more. - Persistence beyond the shell: check for services/tasks/Run keys, new web-app admin accounts, modified app config, and additional shells (attackers plant backups).
4. Contain / Eradicate¶
- Remove all web shells (find every one — they plant spares) and any attacker-added accounts, tasks, services.
- Patch or disable the vulnerable app/feature that allowed the upload/RCE — removing the shell without closing the vector just invites re-compromise.
- Rotate any credentials/secrets the server held (app service accounts, DB creds, API keys, machine keys) — assume they were read.
- If the attacker moved off the server, pivot to lateral movement / domain.
- Block the attacker IP; consider WAF rules for the exploit.
5. Recover & lessons¶
- Rebuild the server from clean media if the compromise went beyond a single removed file — you can't be certain you found every shell/backdoor on a web server.
- Restore the app patched, with rotated secrets, behind a WAF, with reduced web-root write permissions (the web process shouldn't be able to write executable scripts into its own root).
- Harden: least-privilege app pools, disable script execution in upload directories, file-integrity monitoring on the web root, Sysmon rule for web-server processes spawning shells, and continuous access-log monitoring for shell-like request patterns.
Useful queries¶
index=botsv3 sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventID=1
| eval parent=lower(replace(ParentImage,".*\\\\",""))
| where parent IN ("w3wp.exe","httpd.exe","nginx.exe","php-cgi.exe","php-fpm","tomcat*.exe","java.exe","sqlservr.exe")
AND match(lower(Image),"cmd\.exe|powershell|whoami|net1?\.exe|cscript|wscript|bitsadmin|certutil|/bin/(ba)?sh")
| table _time, host, parent, Image, CommandLine, User
| sort 0 _time
Sysmon 1 = process create. A web-server or app process spawning a shell or discovery command is the defining web-shell signal — legitimate web apps almost never do this. parent is stripped to the bare filename for the comparison.
index=<your_web_index> sourcetype=iis
| stats count, values(c_ip) as src, values(cs_method) as methods, min(_time) as first, max(_time) as last by cs_uri_stem
| where match(cs_uri_stem,"(?i)\.(aspx?|php|jsp|jspx)$") AND match(mvjoin(methods,","),"POST")
| where count < 50
| convert ctime(first) ctime(last)
| sort first
Web-server access logs grouped by requested page. A script endpoint that receives POST requests from few sources and is rarely hit (low count) — especially one whose filename isn't part of the app — is a candidate web shell; first/last bound when it was active and src gives the attacker IP. Adapt field names (cs_uri_stem, c_ip, cs_method) to your log source via data discovery.