How to Find a Web Shell on a Compromised Server
Last verified on Nov 8, 2026.
A web shell is a script an attacker places on a web server so they can run commands on it through ordinary HTTP requests. NSA and the Australian Signals Directorate describe web shell malware as software deployed on a web server that can execute arbitrary system commands, commonly sent over HTTP or HTTPS, and note that attackers often create one by adding or modifying a file in an existing web application. MITRE tracks it as T1505.003, a persistence technique. Because the traffic looks like web traffic, you find a shell by checking three places: access logs, the file system, and what the web server process is doing.
What a Web Shell Leaves in Access Logs
A web shell's traffic differs from a visitor's in ways the attacker rarely hides. NSA's guidance points to user agent, referrer, and IP address. CISA adds that an attacker often visits only the URI where the shell was created, while a normal user arrives from a linked page and loads further resources, so frequency analysis on access logs can locate a shell.
In practice, look for these:
- A script nothing links to, requested by one or two IP addresses and no one else.
- POST requests to a script in an upload, cache, or image directory. Static directories rarely serve scripts.
- A command in a parameter, such as
?cmd=id, on a path that is not part of your application. - No referrer on requests below the root, since normal browsing sends one.
- An unusual user agent. An attacker without a foothold does not know which agents are common on your server, so early access is likely to use an uncommon one.
- Successful responses. A 200 from a script that should not exist is the signal. NSA's log checks look at URIs with 2xx status.
NSA warns that its log analytic is likely to produce significant false positives and is easy to evade, so use these checks to build a shortlist rather than a verdict.
For Apache or nginx combined-format logs, this lists URIs that got 2xx responses, with the number of distinct client addresses, total hits, and hits with no referrer. Sort ascending and read the top:
zcat -f /var/log/nginx/access.log* | awk '
$9 ~ /^2/ { u=$7; sub(/\?.*/, "", u); seen[u" "$1]=1; tot[u]++; if ($11=="\"-\"") nr[u]++ }
END { for (k in seen) { split(k,a," "); ips[a[1]]++ }
for (u in tot) print ips[u], tot[u], nr[u]+0, u }' | sort -n | head -40Then look for command-style parameters and POSTs to script files in directories that should only hold static content:
zgrep -hE '[?&](cmd|exec|command|shell|c|e)=' /var/log/nginx/access.log*
zcat -f /var/log/nginx/access.log* | awk '$6=="\"POST" && $9 ~ /^2/ {print $7}' \
| sed 's/?.*//' | grep -E '/(uploads?|images?|cache|tmp|files)/.*\.(php|phtml|jsp|jspx|aspx?)$' | sort | uniq -cOnce you have a candidate path, pull every request for it and every request from the same address:
zgrep -h ' /uploads/.cache.php' /var/log/nginx/access.log*
zgrep -h '^203.0.113.9 ' /var/log/nginx/access.log*The first request in that timeline is often the upload or exploit request that placed the file. Check what came right before it. For payload families that deliver shells, see attack payloads, and for the exploit side after a new CVE, check your logs for exploit attempts.
Check the File System
NSA calls comparison against a known-good copy the most effective way to discover most web shells: compare the production web root to a fresh install of the same application with updates applied, then review everything added or changed. On Linux:
diff -r -q /path/to/known-good /var/www/htmlWithout a clean copy, use modification times and location. Scripts that appeared after your last deploy, especially in directories that hold uploads, are the first suspects:
# Script files changed in the last 14 days, newest first
find /var/www/html -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.jsp' -o -name '*.jspx' -o -name '*.aspx' -o -name '*.asp' \) \
-mtime -14 -printf '%TY-%Tm-%Td %TH:%TM %u %p\n' | sort -r
# Anything newer than the last deploy marker
find /var/www/html -type f -newer /var/www/html/.deployed -name '*.php'
# Scripts and hidden files inside upload directories
find /var/www/html/uploads -type f \( -name '*.php' -o -name '.*' \)Then look inside the candidates. Shells commonly contain execution and encoding functions. This finds PHP files that call them, which is a lead, not a verdict, because legitimate code uses them too:
grep -rlE --include='*.php' '(eval|assert|system|passthru|shell_exec|base64_decode)[[:space:]]*\(' /var/www/htmlNSA publishes YARA rules in two sets. The core set targets indicators unlikely to appear in benign files and gives few false positives. The extended set adds obfuscation, dynamic execution, and encoding, and gives many more. Review core hits first. NSA also notes that attackers can alter shells to avoid file signatures, and in rare cases a shell runs entirely in memory, so a clean scan does not clear the server.
Timestamps can be set by an attacker, so treat a missing recent date as weak evidence and check content and logs too.
Check What the Web Server Is Doing
A web shell runs commands as the web server user, so its footprint is child processes of the web server and unexpected network connections.
Start with what is running now under the web server account:
ps -u www-data -o pid,ppid,etime,comm,args | grep -E ' (sh|bash|dash|nc|curl|wget|python3?|perl) '
ps -eo pid,ppid,user,comm --forest | grep -B1 -A4 -E 'apache2|nginx|php-fpm|httpd|java'A web server that spawns sh or curl once is worth a look. NSA's auditd method records them over time. It finds the web server's uid, then logs every execve made by that uid:
id www-data # note the uid, for example 33
# /etc/audit/rules.d/audit.rules
-a always,exit -F arch=b64 -F uid=33 -S execve -k webexec
-a always,exit -F arch=b32 -F uid=33 -S execve -k webexecThen review what was launched:
grep webexec /var/log/audit/audit.log | grep -oE 'exe="[^"]+"' | sort | uniq -c | sort -rnMany applications launch helper processes legitimately. NSA's list of tools commonly used by attackers and rarely launched by benign Apache applications is cat, crontab, hostname, ifconfig, ip, iptables, ls, netstat, pwd, route, uname, and whoami. NSA recommends mirroring auditd logs to a central system where they can be queried together with web logs.
Also check network behavior. A web server that starts making web requests to internal hosts may be proxying for an attacker, and a non-web device answering web requests from outside is also abnormal. Compare listening and established connections against what the server should do:
ss -tnp | grep -E 'nginx|apache2|php-fpm|httpd|java'Respond Once You Find One
- Preserve first. Copy the file, its metadata (
stat), the web and auditd logs for the surrounding days, and a memory or disk image if your process requires one. Then disable it. - Build the timeline. From the first request to the file, work backwards for the delivery method (an upload form, an application vulnerability, stolen admin credentials) and forwards for every command the shell ran. Use auditd for the commands.
- Assume everything the web user could read is exposed. Rotate database passwords, API keys, cloud credentials, and signing secrets in config files and environment variables. See leaked API key for the order of operations.
- Look for more than one. Attackers often leave several shells and other persistence. Repeat the checks above across every web server and hosts that share credentials with it. Attackers also chain shells across systems to reach internal networks, per NSA.
- Rebuild from known-good. Redeploy the application from source on a clean host where you can, and fix the original vulnerability before the server returns to service.
NSA also recommends file integrity monitoring on web directories, since web shells almost always rely on modifying existing application files or creating new files in a web-accessible directory. Prevent a repeat with the mitigations MITRE lists: limit which accounts can modify the web directory, so the web server user cannot write to code directories, and disable functions that web shells abuse, such as PHP's eval.
Searching Web and Audit Logs in Sazabi
Sazabi, an AI-native observability and security platform, receives Linux auditd records through Fluent Bit or the OpenTelemetry Collector and parses fields such as exe, uid, and key into attributes, so process launches by the web server user can be searched next to your access logs. Each audit line is its own record, and multi-line events such as SYSCALL and EXECVE share an audit_serial but are not joined.
Frequently Asked Questions
What does a web shell look like in access logs?
Usually a request to a script that nothing links to, often in an upload or cache directory, from one or few IP addresses with no referrer and an unusual user agent. Requests are often POSTs, or GETs with a command in a parameter, and return a 200.
Can I find a web shell by searching for file names?
Not reliably. Attackers name shells to look like normal files. Compare the web root against a known-good copy and review added or modified scripts, and read the logs for what requests them.
Should I delete the web shell as soon as I find it?
Copy it and the surrounding logs first, then remove it. The file is evidence of how the attacker got in, and there is usually more than one way back. Deleting it alone does not close the original vulnerability.
What should I rotate after a web shell is found?
Anything the web server process could read: database passwords, API keys, and cloud credentials in config files or environment variables, plus session signing secrets. The shell ran with the web server permissions.
Sources
- NSA and ASD guidance: compare production against a known-good copy to find added or changed files; look for URIs hit by few user agents or IPs, or with unusual referrers; log processes launched by the web server with auditd and review them against a list of reconnaissance tools; watch for unexpected network flows; use file integrity monitoring on web directories (accessed Nov 8, 2026)
- Web shell malware is software deployed on a web server that runs arbitrary system commands, usually sent over HTTP or HTTPS, and attackers create web shells by adding or modifying a file in an existing web application (accessed Nov 8, 2026)
- An attacker often visits only the URI where the web shell was created while a normal user arrives from a referrer, so frequency analysis on web access logs can locate a web shell (accessed Nov 8, 2026)
- MITRE ATT&CK T1505.003: a web shell is a web script placed on an openly accessible web server that gives an adversary command execution and persistence; mitigations include least privilege on the web directory and disabling dangerous functions such as PHP eval (accessed Nov 8, 2026)
- Sazabi receives Linux auditd records through Fluent Bit or the OpenTelemetry Collector and parses fields such as exe, uid, and key into attributes (accessed Nov 8, 2026)