A web shell is a script placed on an openly accessible web server that lets an adversary use that server as a gateway into the network. MITRE tracks it as T1505.003, Web Shell, under Server Software Component in the Persistence tactic. The platforms listed are Linux, macOS, network devices and Windows.
Web shells matter because the web server is already trusted, already reachable from the internet and often already allowed to talk to internal systems. A small script on it can be a very durable foothold.
How attackers use it
MITRE's description is short and clear. A web shell is a web script that provides a set of functions or a command line interface on the system that hosts the web server. The adversary reaches it over the same HTTP or HTTPS ports as normal visitors, so it blends in with ordinary traffic.
Many web shells come with a client interface program that talks to the web server. MITRE gives China Chopper as an example of such a client.
Two properties are worth remembering:
- The file itself can be tiny and look like a normal page, which makes it easy to miss during a directory review.
- The shell runs with the privileges of the web server process. What that account can read, write and execute defines the damage.
The technique sits under persistence because it lets the adversary return later without repeating the original break in. The exact way the file first got onto the server varies and is covered by other techniques, such as exploiting a public facing application.
How to detect it
MITRE's detection strategy is about behavior chains: a file appears in a web directory, and then the web server process does something it normally does not.
- On Windows: unexpected file creation in web directories, followed by a web server process such as w3wp.exe spawning command shells or script interpreters such as cmd.exe or powershell.exe.
- On Linux: creation of an unauthorized script such as a PHP or shell file in the web root, followed by apache or nginx running unexpected system utilities, for example curl, bash or nc.
- On macOS: web servers such as httpd spawning abnormal processes after a file upload into the web document directories.
From that, a practical set of detections:
- File integrity monitoring on web roots. New or changed script files outside your deployment process are suspicious by default.
- Process lineage alerts. A web server worker that starts a shell or an interpreter is rarely normal. Log process creation with parent and child names, for example with Sysmon event ID 1 on Windows or auditd execve records on Linux.
- Web access log review. Look for requests to a file that was created recently, requests that return small responses to a single client over and over, and POST requests to scripts in upload folders.
- Outbound connections from the web tier. A web server making new connections to the internet or to internal admin ports is worth a look.
How to reduce the risk
MITRE lists two mitigations, and both are practical:
- Disable or remove risky functions. Consider turning off functions in web technologies that can be abused for web shells. MITRE gives PHP's eval function as an example.
- Apply least privilege to accounts. Limit user account privileges so that only authorized accounts can modify the web directory. If the web server process itself cannot write to the web root, a whole class of upload to execute paths closes.
Those two ideas combine well with habits you may already have:
- Deploy web content from a build pipeline and make the web root read only for the service account.
- Keep upload directories separate from executable paths, and configure the server not to run scripts from them.
- Patch public facing applications promptly, since those flaws are a common way to get a file onto the server.
- Restrict outbound traffic from web servers to what they genuinely need.
Check your own environment
A read only starting point on a Linux web server, assuming your web root is /var/www (adjust the path):
# Script files changed in the last 7 days under the web root
find /var/www -type f \( -name '*.php' -o -name '*.sh' -o -name '*.jsp' \) -mtime -7 -ls
On Windows with IIS, list recently written script files under your site folder:
Get-ChildItem -Path C:\inetpub\wwwroot -Recurse -Include *.aspx,*.asp,*.ashx -File |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) } |
Select-Object FullName, LastWriteTime
Then work through this checklist:
- Does every recent change map to a deployment or a ticket?
- Which account does the web server run as, and can it write to the web root?
- Do your logs record process creation with parent process names for web servers?
- Is there an alert when w3wp.exe, apache or nginx starts a shell?
- Which outbound destinations does the web tier actually use?
Quick check
Which single signal is most telling for a web shell?
A web server process starting a shell or script interpreter, especially soon after a new script file appeared in a web directory.
Why does making the web root read only for the service account help?
A web shell has to exist as a file the server can run. If the server account cannot write to the web directory, many ways of getting a script into place stop working. MITRE's mitigation is to limit modification of the web directory to authorized accounts.