A web shell is a small script placed on a web server that an attacker can reach through a normal web request, giving them a way to run commands on that server. MITRE ATT&CK classifies it as a persistence technique, because its purpose is to keep access open long after the original way in has been closed.
What it is for
MITRE ATT&CK catalogues web shells as T1505.003, Server Software Component: Web Shell, and files the technique under persistence rather than under initial access. That classification is the most useful thing to understand about them.
A web shell is not how an attacker gets in. Something else does that: an unpatched component, a stolen credential, a misconfigured permission, an upload feature that accepted the wrong thing. The web shell is what they leave behind so that fixing the original problem does not remove them.
MITRE describes it as a server-side script placed on an accessible web server that grants gateway access into a network, providing command execution or a command-line interface on the compromised host. In plain terms, it turns a URL into a way of issuing instructions to your server, and it keeps working after the patch is applied and the passwords are rotated.
That is why they matter out of proportion to their size. A few lines of script can convert a single lapse into indefinite access.
How an upload feature becomes the route in
File upload is the classic delivery mechanism, and it works when three conditions happen to line up.
- The application accepts a file it should not. Usually because validation trusts the filename or the declared content type, or because a polyglot file satisfies a content check while also being valid as something executable
- The file lands somewhere the server will execute. Uploads written into a directory inside the web root, on a server configured to run scripts from it, are the dangerous combination. If the same file lands outside the web root it is inert
- The attacker can reach it. They need the resulting address, which is straightforward if you keep the original filename and store files in a predictable place
All three are required. Break any one and the route closes, which is what makes this defensible: you do not have to win the argument about whether a file is malicious in order to make it harmless.
Two industry lists point at the same conclusions from different directions. PortSwigger's guidance is to rename uploaded files, keep them off the permanent filesystem until fully validated and check extensions against a permitted list. MITRE's mitigations are to remove dangerous functionality that is not needed, and to apply least privilege so that only authorised accounts can modify web directories. Those overlap on the same principle: the upload should never be able to become a resident, executable part of your application.
Why they are hard to find later
Web shells are small, and they sit among files that are supposed to be there. On a server with thousands of application files, one more does not announce itself, and requests to it look like ordinary web traffic in a log full of ordinary web traffic.
MITRE's detection guidance is instructive because it does not attempt to describe what the file looks like. It points at behaviour instead:
- Scripts appearing unexpectedly in web-accessible directories, particularly followed by the web server process spawning a command shell
- Unauthorised script files in directories the web server serves, followed by execution of system utilities
- Abnormal process creation from a web server process after files have been written to web directories
The common thread is that a web server has a narrow job. It should serve content and talk to your application. When it starts a command interpreter, something has gone wrong regardless of which file caused it, and that signal holds even against a script nobody has catalogued.
For an application team, the practical version is that file integrity monitoring on served directories, and alerting on unexpected process creation from the web server, will tell you more than trying to recognise the script itself.
Keeping them out
The controls that matter are mostly about where files live and what is allowed to happen to them, not about detection.
- Store uploads outside the web root, ideally in object storage rather than on the application server, and serve them through code that reads the file rather than by exposing a path
- Take away execution. The directory holding user content should not be a place the server will run anything from
- Rename every upload to an identifier you generate, and keep the original name as metadata for display only
- Serve user content from a separate origin, which also limits what a file could do in a visitor's browser
- Restrict write permissions so the web server account cannot write into the directories it serves, per MITRE's least-privilege mitigation
- Rebuild what you accept. Re-encoding images and applying content disarm and reconstruction to documents means what you store is assembled from legitimate content rather than merely permitted to pass
Notice that most of these hold even when validation is wrong. That is deliberate, and it is the right way to think about the problem. Validation is a filter that will occasionally be beaten. Storage location and execution permissions decide what happens on the occasion it is.
Our guide to file upload security covers how these fit together in a working upload pipeline.
Where it lands matters. So does what it is. Storage and permissions limit what an unexpected file could do. The Red Eagle Tech CDR API handles the other half, rebuilding documents from their safe components before you ever store them. Published per-document pricing, and you can be running in minutes.