Introduction
This post focuses on web shells on Linux systems, though many of the concepts apply to Windows/IIS environments as well–they’re just not covered here explicitly.
Web servers come in all shapes and sizes, with different languages, features, logging behavior, and configurations. These setups can very wildly depending on how clever (or careless, clueless, …) the systems administrators and developers are.
This guide doesn’t aim to cover web exploitation techniques–that’s a deep and complex topic on its own that many folks build entire careers around. Instead, this post focuses on what to look for after a web shell has been placed: how to detect it, and how to harden systems against web shell-based attacks.
This post isn’t an exhaustive guide to web shells, but it offers practical, real-world strategies to find and prevent this type of persistence.
What is a Web Shell?
A web shell is a method of achieving arbitrary code execution through
a web server. Once deployed, attackers can execute system commands and
receive output via a browser or command-line tools like curl
–often
over HTTP/S, blending in with normal web traffic.
Web shells can be as simple as a one-liner:
<?php system($_GET['cmd']); ?>
Or they may be complex, full-featured control panels with authentication, file upload/download capabilities, command histories, and built-in tools for enumeration, and lateral movement.
Web shells are commonly dropped intentionally by attackers, deployed by automated exploitation frameworks, or embedded within payloads. They’re often missed–sometimes for years. I’ve worked on several systems that had been compromised by at least six different actors over a five-year period, all of whom left behind their own dormant web shells.
There are tons of web shells floating around in the wild. They come in many languages–if web applications can be written in a given language, you’ll find dozens of examples written in it. This is by necessity: a web server running PHP for example typically won’t support Ruby or Python without additional setup.
As with other types of malware, attackers may use common, open-sourced shells as-is or modify them slightly for their own purposes. Examples of web shells are easy to find–in forums, email list archives, pastebins, GitHub, and more. A good starting point is one of the many “awesome” collections, such as this one: https://github.com/alphaSeclab/awesome-webshell/blob/master/Readme_en.md
Initial Access Vectors
If you are interested in how exploitation occurs, I highly recommend reading ~The Web Application Hacker’s Handbook~ and working through the free PortSwigger Web Security Academy training. Explaining web hacking is well beyond the scope of this document–people build entire careers around web application hacking.
That said, here are some common ways web shells end up on systems, without diving too deep into the specifics.
Remote Code Execution (RCE) Vulnerabilities
Attackers may exploit vulnerabilities to directly execute system commands–this is often convenient and stealthy, since nothing gets written to disk.
Tools like Metasploit frequently include modules for these vulnerabilities, making interaction easy and offering a familiar interface to the attacker.
This category goes very deep, and there are dozens of techniques that fall under the umbrella of RCE, including:
-
OS Command Injection
-
File Inclusion (both local and remote)
-
Deserialization vulnerabilities
-
And many others…
File Upload Abuse
Many web applications allow users to upload files–profile pictures, documents, attachments, etc. If these upload mechanisms aren’t properly secured or sanitized, attackers can simply upload a web shell and then access it via the browser.
This is one of the most common ways web shells are deployed. All it takes is a misconfigured endpoint that:
-
Doesn’t restrict file types or extensions
-
Fails to validate MIME types
-
Allows files to be written to a web-accessible directory
Often, attackers bypass filters by using double extensions (e.g.,
shell.jpg.php
) or uploading files with harmless-looking names (e.g.,
invoice_20250416.php
) or by using an intercepting proxy such as Burp
or Zap to lie about the MIME type. If the file is uploaded under
/var/www
or another web-accessible path, the shell may be usable.
Manual Placement
Attackers who have already gained access to a system–via SSH, another web vulnerability, stolen credentials, or some other method–may manually place web shells as a way to maintain persistence.
They may upload a new file directly into a web-accessible directory,
or modify an existing web application file to include their web
shell’s code. For an example, an attacker may insert a PHP backdoor
right in the middle of index.php
, or add a small eval
-based
payload into a theme or plugin file.
Because these changes can be subtle and easily blend in with legitimate code, defenders often overlook them–especially if the application is large, poorly-documented, or rarely updated.
Web Shell Capabilities
Most web shells offer a common core set of features designed to give attackers control over the victim’s system. These often include command execution, filesystem manipulation, and basic access controls.
The capabilities outlined below are common across many shells, regardless of language or complexity.
Command Execution
A core feature of nearly every web shell is the ability to execute shell commands–shocker, I know!
This is typically done using built-in language functions like
system()
, eval()
, passthru()
, exec()
and so on. The
aforementioned examples are PHP-specific, but the concept is the same
across languages: run a command and return the output.
File Uploads and Downloads
Another core functionality often provided by web shells is the ability to upload and download files to and from the victim’s system. This can be used to exfiltrate data, drop additional payloads, or to modify existing files.
File deletion is also common–though it may be done via shell
execution (rm
, del
, etc.) rather than a dedicated UI function.
Authentication
Some web shells include basic authentication mechanisms–typically a hard-coded password or key–to prevent unauthorized access.
At a minimum, red teamers should include authentication on their web shell payloads to avoid inadvertently exposing access to malicious actors. Unauthenticated and exposed web shells can easily be hijacked–with potentially disastrous consequences to their customers and organization.
Attackers who reuse passwords in their web shells across multiple victims or engagements are often grouped into activity clusters by security companies and research groups. These reused credentials can serve as loose indicators for attribution.
Obfuscation and Evasion
Web shells often use obfuscation and evasion techniques to avoid detection by defenders, scanners, and automated tools. These techniques range from basic encoding tricks to more advanced efforts to blend in with legitimate code, hiding in plain sight.
The methods below are commonly seen in the wild and can significantly increase a shell’s lifespan on a compromised system.
The Usual Suspects: base64, ROT, concatenation, etc.
Simple obfuscation techniques like base64
encoding, alphabet
rotation schemes like ROT13
, string concatenation, and character
substitution are extremely common in web shells. These methods don’t
prevent analysis, but they’re often enough to evade basic
keyword-based detections or cursory manual inspection.
For example, a shell might encode the payload and wrap it in an
eval(base64_decode(...))
call, making the code unreadable at first
glance. Others might split function names into parts and reassemble
them at runtime (e.g., "sys"."tem"
), substitute variables to
disguise intent, or introduce bogus, non-operational code to throw off
defenders.
These tricks are usually trivial to reverse but effective at slipping past signature-based scanners and slowing down analysts.
Compiled Objects
Many “scripting” languages–like PHP and Python–offer the ability to compile source code into bytecode for improved performance. This eliminates the need to re-parse or re-interpret the code on each request, which can be helpful on high-traffic systems.
Attackers may abuse this functionality as a form of
obfuscation. Instead of dropping readable .php
or .py
files, they
might deploy pre-compiled .pyc
or .phar
files, which are harder to
casually inspect. In some cases, the compiled object may be embedded
within another file or delivered in formats unfamiliar to the
defender.
These artifacts can be easy to miss–especially if your tools aren’t able to scan bytecode or the files look innocuous.
Masquerading as Benign Files
Web shells are often disguised as harmless-looking files–such as images, icons, or documents. This can effectively blend in with legitimate content.
A common trick is to give the shell a misleading extension like
.jpg
, .gif
, or .ico
, even though the file actually contains
executable code. Some servers are misconfigured and will still
interpret embedded code despite the incorrect file type.
In some cases, the shell may be appended to real image files–the image will still display correctly, but the embedded code is executed when interpreted by the server. These types of dual-purposed files can bypass naive filetype checks and casual human review.
Hidden Files
Attackers may name their web shells with a leading dot (e.g.,
.shell.php
) to hide them from directory listings. On Linux systems,
files prefixed with a dot are considered hidden by default and won’t
show up in a standard directory listing with ls
unless explicitly
requested with ls -a
.
This technique doesn’t prevent access or hide the file from the web server, but it may evade casual observation or manual inspection by defenders.
Random or Nonsensical File Names
Some attackers use randomized or nonsensical filenames like
alksfjw4092ytjoiwjgw24t.php
to avoid detection. These filenames
don’t stand out in large directories and may not be caught by simple
static detections that rely on specific strings like webshell.php
or
cmd.php
.
This technique can be especially effective on busy servers or large web applications where hundreds or thousands of files or present.
Blending in With Legitimate Filenames
Rather than using random or nonsensical filenames, some attackers opt
into choosing filenames that blend in with legitimate application
components–for example, wp_feature.php
, config_backup.php
, or
admin_ajax,php
.
This approach is especially common on CMS platforms like WordPress, Drupal, or Joomla, where attackers name their shells to look like core files, plugins, or theme components. The goal is to avoid suspicion during casual inspection or even manual review by defenders unfamiliar with the application’s expected file structure.
This technique tends to work best when the web shell is placed in theme/plugin directories, making the file seem like it belongs.
Code Insertion
Instead of dropping a standalone web shell, attackers may inject
malicious code directly into existing application files–such as
index.php
, functions.php
, or a plugin file. This technique is
especially effective in large web applications, wheredefenders may not
notice a few extra lines buried inside a 3,000-line PHP file.
The injected code can be added to the beginning, middle, or end of the file. In some cases, the code is placed in the middle of legitimate logic or conditional statements to avoid execution unless triggered in a specific way.
Because the filename remains unchanged and the application still performs its intended functions, this type of persistence often evades detection–especially without integrity checks or known-good references.
Avoiding the GET Verb
Web shells often avoid using the GET
method to receive commands,
since GET
request parameters are typically logged by default in
access logs. In contrast, POST
, OPTIONS
and other requests usually
do not have their payloads logged–making them more appealing for
stealthy interactions.
By accepting commands via POST
or another non-logged method,
attackers gain a basic layer of protection against defenders reviewing
access logs. This doesn’t make the traffic invisible, but it can
significantly reduce the visibility of the commands being run unless
deeper inspection is performed (e.g., full packet captures or extended
logging).
Detection Techniques
Here are some practical techniques for detecting web shells–whether they were dropped by an opportunistic worm, APTs, or just some random attacker.
Some of these methods catch obvious shells; others are aimed at stealthier, well-hidden backdoors. Most rely on a combination of log analysis, filesystem inspection, and knowing what “normal” looks like on your web stack.
Webserver Discovery
Before you can hunt for web shells, you need to know what web server software is running, where it is running, and how it is configured. This is often a pain point for analysts–especially in environments with custom applications, non-standard development stacks, or poor (or non-existent) documentation.
If you’re unfamiliar with how web applications work, I highly recommend installing a few common platforms in a virtual machine like WordPress, phpMyAdmin, MediaWiki, or Magento. These will give you a good sense of how real-world applications are structured, where logs live, how files are organized, and what misconfigurations look like.
For those interested in exploitation, check out intentionally vulnerable applications like DVWA (Damn Vulnerable Web Application), Metasploitable, or OWASP Juice Shop. These are great for building intuition around attack chains.
There’s a lot more out there than just Apache or Nginx. You are likely to encounter:
-
Apache, Nginx, or Tomcat
-
Lightweight web servers like Flask or FastAPI
-
Embedded HTTP servers baked into applications
-
Framework-specific servers running via
python
,node
,ruby
, etc. -
Containers with internal web services
Start by identifying listening services with netstat
or ss
:
ss -tulpn
Look for common web ports (80, 443, 8080, 8000, 4000, etc.) and correlate them with process names. From here, you may be able to deduce the software, its configuration location, etc.
You may also encounter:
-
Virtual hosts, each with separate document roots and log paths
-
Tomcat Manager panels–if exposed and compromised, attackers can directly deploy WAR backdoors (and the logging is often terrible).
-
Containerized applications–you’ll need to
exec
into the container to access files, logs, and other information relevant to the investigation.
Filesystem Analysis
Like other forms of malware, web shells often leave traces on the filesystem. With the right approach, defenders can use standard tools to find these artifacts.
The key is to combine context (e.g., web server layout and behavior) with indicators such as file modification times, unexpected ownership, unusual locations, or file hashes. If you know what “normal” looks like on a system, deviations become easier to spot.
Modification Times Within Incident Timeline
A simple and effective technique is to look for files that were
modified or created during the timeframe of the suspected
intrusion. If you have logs, alerts, or other indicators showing when
an attack occurred, use tools like find
, stat
, or ls -lt
to
identify files modified around the time.
For example:
find /var/www -type f -newermt "2025-04-15 00:00" ! -newermt "2025-04-15 23:59"
This approach often highlights dropped shells, modified application files, and other unauthorized changes.
Permissions, Ownership, and Group Membership
Web application files are usually owned by a specific user or
group–often something like www-data
, apache
, www
, or
nginx
. If you see files with unexpected ownership (e.g. root
) or
some other user account, that’s worth investigating.
Be on the lookout for:
-
Files with overly-permissive modes (e.g.
chmod 777
) -
Files with non-uniform permissions compared to other files in the same directory (e.g.
664
vs.644
) -
Files with non-uniform ownership or group membership.
Use commands like ls -al
, find
, or stat
to enumerate for
anomalies:
find /var/www -not -user www-data
Strange permissions or ownership mismatches often indicate files that were manually placed or tampered with.
File Hashing and Diffing
One of the most reliable ways to detect web shells is by hashing
application files and comparing them against known-good
versions. Tools like sha256sum
, md5sum
, or diff
can help
identify modified files–especially when the web shell is hidden
inside otherwise legitimate code.
For example:
diff -ruN /var/www/html /path/to/known-good-wordpress/
This kind of diffing often reveals subtle modifications–like a single
line of PHP added midway through index.php
.
Note: At a CTF, I once built a local database of file hashes for
every release of WordPress, Joomla, Drupal, and other web
applications that I expected to see at the event. I stored all of the
.tar.gz
and .zip
files for these releases on an external drive for
quick and easy diff
ing. Comparing hashes and running diffs between
known good copies and what was active on the game systems immediately
exposed every web shell placed by the red team.
Package Manager Verification
If the web application or its dependencies were installed through a package manager, you can often verify the integrity of the installed files against known-good versions provided by the package.
For more on this approach, see the related post: Finding Bad with Linux Package Managers.
Signature-based Detections
Web shells can often be detected using signature-based tools like YARA
or ClamAV. These tools look for known patterns in files–such as
suspicious function names (eval
, base64_decode
, system
,
webshell
, etc,), common shell payloads, or high-entropy blobs.
YARA is especially useful for this purpose. You can write simple rules that look for common obfuscation patterns, function combinations commonly found in shells, or strings found in specific shells.
This won’t catch everything–especially well-obfuscated or custom shells–but it can be a great tool for finding commodity web shells or as a first pass on sweeping through web application directories.
File Entropy
Source code typically has low to moderate entropy–it’s mostly readable text and predictable structures. In contrast, packed, encoded, or obfuscated files often have high entropy, which can serve as a useful and easy detection signal.
You can use tools like ent
or your own scripts to scan for unusually
high entropy.
ent suspicious.php
This may result in false positives and doesn’t tell you what the file does, but may be a good indicator that something isn’t normal–especially in directories that are expected to contain plain-text source code.
Log Analysis
Some web server software–or misconfigured installations–simply don’t log, which can be a real bummer. Even when logging is enabled, it is common for admins to modify the format or location of logs, breaking compatibility with off-the-shelf parsers and making analysis painful.
To make matters worse, many environments have unconventional directory
structures, multiple vhosts with separate logging paths, or logs
buried deep in non-standard locations. You’ll often need to read the
server’s configuration files (e.g., httpd.conf
, nginx.conf
,
virtual host configurations, etc.) to figure out where logs are stored
and how they are structured.
There’s no shortcut here–you have to get comfortable wrangling and
cutting up log files. Tools like grep
, awk
, sed
, cut
, sort
,
and uniq
, are usually available and can take you a long way when
slicing through large access logs.
access.log, error.log
By default, Apache and Nginx maintain two primary logs: access.log
and error.log
. These typically use a standardized format–the Common
Log Format (CLF) or the Combined Log Format, which includes additional
fields like the User-Agent and referrer.
However, administrators can (and often do) change the logging format
using the LogFormat
and CustomLog
directives in Apache or the
log_format
directive in Nginx. Custom logging can break
compatibility with pre-built parsers or tooling.
Things to look for in access logs:
-
Long, encoded requests (e.g.,
cmd=
followed by base64 blobs) -
Requests containing obvious shell commands
-
Weird or outdated User-Agents–e.g., Windows XP User-Agents or tools outright announcing themselves
-
Multiple different User-Agents from the same IP address (not conclusive, but can be suspicious)
-
IP addresses doing unexpected things like POSTing to image files or sending malformed requests
-
Other behavior from known attacker IPs. (e.g., initial reconnaissance followed by uploads or command execution)
-
Correlate access and error logs with known command execution times or suspicious process activity
Web Servers Executing Unexpected Commands
If a web server process is spawning child processes or executing
system commands, something is probably wrong–especially if it’s
running tools like bash
, nc
, python
, perl
, wget
, curl
,
socat
, etc.
You can spot this behavior a few different ways:
-
Process trees - Look for unusual child processes under web server processes. If the web server is launching a shell, downloader, or reverse proxy command, that’s a major red flag.
-
Audit logs - If
auditd
is enabled, it can trackexecve
calls from the web server. This can help identify which binaries were executed, with what arguments, and by which user. -
EDR tools - Endpoint Detection and Response solutions can often correlate process ancestry, file events, and network activity. Correlating EDR data with activity around suspicious
access.log
timestamps can often reveal which file or application contains a vulnerability or web shell.
JA3
JA3 is a TLS fingerprinting technique that creates a hash based on the SSL/TLS handshake parameters–like cipher suites, extensions and supported versions. Many attack tools (including those used to interact with web shells) have distinctive JA3 signatures.
A common detection strategy is to look for mismatches between JA3 fingerprints and User-Agent strings. For example:
-
The JA3 fingerprint matches Python requests, but the User-Agent states that it is Internet Explorer.
-
The JA3 fingerprint matches
Metasploit
orcurl
, but the User-Agent is mimicking Chrome. -
The JA3 fingerprint is uncommon or unique in the environment
These mismatches often reveal specific tool usage, and can serve as high-confidence indicators–especially when combined with suspicious request patterns.
Hardening and Prevention
This section outlines techniques that can help harden systems and prevent web shells from being deployed in the first place.
These techniques won’t eliminate all risk–and some may introduce friction into administrative or development workflows–but they can significantly raise the bar for attackers and make post-exploitation more difficult.
Patching and Updates
Keep your software up to date–especially CMS plugins, themes, and third-party components. These are often neglected and are frequent targets for attackers.
If possible, install web applications through your system’s package
manager and enable unattended or scheduled updates. While this isn’t
foolproof–and some web applications may not be available or up to
date in your distribution’s official repositories–this approach helps
reduce exposure windows and ensures you’re pulling from a vetted
source rather than some random .zip
files from the internet.
If you have multiple systems utilizing the same web application, it may be worth packaging them yourself and hosting a local package repository for your organization. This gives you more control over update workflows, ensures consistency across environments, and often provides a built-in ledger of known-good hashes–which can later be used to detect tampering if the need arises.
AppArmor, SELinux, …
Mandatory access control systems like AppArmor
and SELinux
can
limit what a web server process is allowed to do–and that includes
running unexpected binaries.
In most environments, a web server should never be executing
commands like ps
, ls
, or whoami
. Some legitimate web
applications do invoke external tools (e.g., for image processing, PDF
generation, diagnostics, etc.), but the set of allowed commands should
be very limited and explicitly allow listed.
By applying restrictive, well-scoped AppArmor or SELinux profiles, you can block common web shell behavior even if the shell is successfully deployed. These tools aren’t a silver bullet, but they drastically reduce the impact of a compromised web application.
Web Application Firewalls
Web Application Firewalls (WAFs)–including tools like
mod_security
–can help block common web shell deployment and usage
patterns by inspecting incoming requests and flagging or blocking
suspicious behavior.
Like any other protection mechanism, WAFs aren’t cure-alls. They can be noisy, bypassed, or misconfigured–and many organizations simply disable rules that interfere with production traffic instead of troubleshooting and tuning them properly. Still, when properly configured, they can be a strong layer of defense.
Disable Dangerous and Unnecessary Functions
Languages like PHP allow you to disable specific functions such as
those commonly abused in web shells, such as system()
, eval()
,
passthru()
, and others. This can be done using the
disable_functions
directive in php.ini
:
disable_functions = system, passthru, eval
Disabling these functions doesn’t break most web applications–unless they rely on calling external binaries, which is often a sign of fragile or risky design. Blocking them can prevent many web shells from functioning at all, even if successfully deployed.
Other languages may offer similar restrictions. Your mileage may vary.
File Integrity Monitoring
Monitoring the integrity of files under /var/www
and other web
application directories can help detect unauthorized changes–like the
insertion of web shells or modification of existing application code.
Tools like AIDE
, Tripwire
, the system’s package manager, or even
custom sha256sum
scripts can be used to:
-
Alert on changes to core files
-
Detect unauthorized uploads
-
Catch code injection into legitimate scripts
This works best when paired with known-good baselines and alerting systems. Even basic cron jobs that compare file hashes periodically can go a long way in identifying web shells.
Egress Restriction
A powerful–and often overlooked–hardening technique is restricting outbound (egress) traffic from web servers.
Most web applications only need to make outbound connections to a few places: other internal services such as databases, software update servers, or maybe administrative activity with SSH. They usually don’t need unrestricted internet access.
By restricting egress, you limit the attacker’s options. They can’t just fire off a reverse shell to port 4444 or exfiltrate data over arbitrary ports. Instead, they’re forced to operate within allowed channels.
This won’t stop all shells, but it forces attackers to be louder, more creative, and creates a lot of opportunities for detections.
Attack Surface Reduction
A common tenet of hardening is attack surface reduction–if you don’t need it, get rid of it. The smaller the attack surface, the fewer the chances an attacker has to succeed, and the easier it is for defenders to monitor what matters.
-
Disable unused web server modules (e.g., CGI, WebDAV, or unused language interpreters like PHP, Python, or Perl)
-
Remove default files, unused applications, plugins, themes, and administrative panels.
For example: if your site doesn’t use PHP, remove the PHP module and interpreter entirely. Fewer moving parts means fewer places for an attacker to hide–and fewer tools they can abuse.
Drop Privileges
Web servers should never run as root–but in practice, some appliances or misconfigured setups do exactly that. Web exploitation is exceedingly common, and attackers achieve code execution in one of these setups will immediately have full control over the system.
Most web servers (like Apache and Nginx) start as root
only to bind
to privileged ports (e.g., 80 or 443), then drop privileges to a
non-privileged user like www-data
. This is the behavior you probably
want.
On modern systems, it’s even possible to avoid starting as root
entirely by assigning the CAP_NET_BIND_SERVICE
capability to the web
server binary. This allows it to bind to low-numbered ports without
needing root privileges:
setcap 'cap_net_bind_service=+ep' /usr/bin/httpd
Ensure that the user the server drops privileges to:
-
Has minimal access to the filesystem
-
Can’t write to sensitive directories
-
Isn’t part of any elevated or administrative groups
If you’re building or deploying a custom stack, verify that privilege dropping is actually happening.
Conclusion
Web shells are one of the most common and effective persistence mechanisms seen in the wild–but they’re also one of the most detectable if you know what to look for. Take the time to understand how your web stack behaves, and even the sneakiest backdoors will stand out.