---
name: detect-malicious-upload
description: "Detection, hunting, and hardening for malicious file upload, where an attacker abuses an upload feature to plant a web shell or polyglot file under a web-accessible path and then requests it for code execution. Maps to MITRE ATT&CK T1505.003, T1190, T1059, T1036.007, and T1036.008. Keys off web server access logs (IIS, Apache, Nginx), WAF and reverse-proxy multipart captures, Sysmon Event ID 11 and 1, Windows Security Event ID 4688, and EDR file-scan verdicts. Gives the defender view, log sources, content-inspection and polyglot detection logic, public rule pointers, a hypothesis-driven hunt, upload hardening, and blind spots. Use when hunting malicious file uploads, triaging a suspected web shell, investigating a web app process that spawned cmd or /bin/sh, reviewing uploads whose magic bytes disagree with their extension, hunting for polyglot or GIFAR files, or hardening an upload feature against webshell drops."
---

# Malicious File Upload - Defensive Methodology

> **Tier 2 - methodology skill.** No bespoke UMBRASEC detection rule ships with this skill yet. It gives the defender's method, the log sources and signals, and the hardening, and points to public rule sources. Promotion to Tier 1 (a sourced, tuned UMBRASEC rule) is tracked in [COVERAGE.md](https://github.com/umbrasec-labs/umbra-blue/blob/main/COVERAGE.md). Do not treat the content below as citing a UMBRASEC rule; there is none for this threat yet.

## TL;DR for defenders

A malicious upload is an attacker getting a server-executable file (a web shell) past an upload feature that was supposed to accept only documents or images, then requesting it to run commands as the web service account. The two ways past a weak filter are a spoofed type (double extension `shell.php.jpg`, mismatched `Content-Type`, trailing dot or null byte, case tricks like `.pHp`) and a polyglot (one blob that is a valid image header and valid script at the same time, for example a GIF or JPEG whose trailing bytes are PHP). You catch it at two layers. At the request layer, an upload whose declared extension or `Content-Type` disagrees with its actual magic bytes, or an upload of a server-executable extension at all, is the anomaly, and a GET or POST to a brand-new file under the upload directory right after is the use of what was planted. At the host layer, a new server-side script file appears under the web root (MITRE ATT&CK **T1505.003 Web Shell**), and the application process spawns a child it never normally spawns. The content-mismatch signal is noisy and sometimes blind to TLS bodies; the new-script-file and parent-child process signals are the higher-fidelity catches. This skill ships no UMBRASEC rule for this threat; it points to maintained public rule sets instead.

## What it looks like, defender view

In telemetry the attack presents in two beats: the drop, then the use. The drop is a multipart POST to an upload handler carrying a file whose real content does not match what the feature is supposed to accept: the declared extension says `.jpg` but the leading bytes are `<?php` or `<%`, or the `Content-Type` says `image/png` while the body is a script, or the filename carries a second extension or a null byte so a naive check sees the wrong suffix. A polyglot hides the same thing inside a genuinely valid image: the file opens as a picture and passes an image-type check, yet the web server's PHP or ASP handler, keyed off the extension or a content handler, still executes the embedded code. The use is the attacker requesting the planted file: a GET or POST to a new, odd path under the upload or web-root directory, often with command output in the response or parameters that look like commands. When a web shell runs, the host shows it: a new `.php`, `.aspx`, `.jsp`, `.svg`, or `.htaccess` written under the web root by the web service account, and the application process stepping outside its normal behaviour to spawn a shell, write more files, or open an outbound connection. The planted file on disk is the artifact that survives even when the upload itself rode an encrypted request body.

## Log sources and signals

Collect from the web tier and from the application host. The request layer catches the upload on the wire; the host catches the file on disk and the execution that proves the shell works.

- **Web server, WAF, and reverse-proxy logs (request layer).** IIS, Apache httpd, Nginx access logs, and WAF or API-gateway multipart captures that include the filename, the declared `Content-Type`, and ideally the body. Signals: a multipart upload whose `filename=` ends in a server-executable extension (`.php`, `.phtml`, `.php5`, `.phar`, `.asp`, `.aspx`, `.ashx`, `.jsp`, `.jspx`, `.war`, `.cfm`, `.pl`, `.cgi`, `.svg`, `.htaccess`, `.config`); a double or trailing extension (`.php.jpg`, `.asp;.jpg`, `.php%00.jpg`, `.php.`); a declared image `Content-Type` on a body whose first bytes are not that image's magic number; and, the beat that confirms it, a GET or POST to a newly seen path under the upload directory shortly after the POST that created it. A request-body inspection that reads the first bytes is where the magic-vs-extension mismatch becomes legible.
- **Content type vs magic-byte mismatch.** The core content-inspection signal is the declared type (extension and `Content-Type`) disagreeing with the file's actual leading bytes: a real JPEG begins `FF D8 FF`, PNG `89 50 4E 47`, GIF `GIF87a`/`GIF89a`, PDF `%PDF`, ZIP and its derivatives (`.docx`, `.jar`, `.war`, `.apk`) `50 4B 03 04`. A file that claims to be an image but whose bytes are a script, or an archive uploaded where only images are allowed, is the anomaly. A polyglot has the valid image magic AND an executable payload later in the stream (for example PHP tags after the image data, or a GIFAR that is a valid GIF and a valid JAR), so magic-byte alone passes it; detecting a polyglot needs scanning the whole file for script markers (`<?php`, `<?=`, `<%`, `<script`, `system(`, `eval(`, `base64_decode(`) and tool-type polyglot checks, not just the header.
- **File-create telemetry (host).** Windows **Sysmon Event ID 11 (FileCreate)** and Linux auditd watches or an eBPF sensor (Falco, Tetragon) on the web root and upload directories. The high-fidelity signal is a new server-side script (`.php`, `.phtml`, `.aspx`, `.ashx`, `.jsp`, `.jspx`, `.cfm`, `.svg`, `.htaccess`, `.config`) created under a web-accessible path by the web service account. Fields: `TargetFilename`, `Image` (the creating process, expect `w3wp.exe`, `php-fpm`, `httpd`, `nginx`, `java`), `User`. A `.htaccess` or `web.config` appearing in an upload folder is especially suspicious: it is an attempt to re-map a benign extension to a script handler.
- **Process-creation telemetry (host).** Windows **Sysmon Event ID 1** or Security **Event ID 4688**, and Linux **auditd `execve`**. The web shell betrays itself when it runs: a parent-child anomaly where `w3wp.exe`, `php-fpm`, `httpd`, `nginx`, `java`/`javaw`, `tomcat`, or `node` spawns `cmd.exe`, `powershell.exe`, `/bin/sh`, `bash`, `whoami`, `python`, or a LOLBIN (`certutil`, `bitsadmin`, `curl`, `wget`). Fields: `ParentImage`, `Image`, `CommandLine`, `User`. Application servers almost never launch an interactive shell in normal operation.
- **Antimalware and EDR file-scan verdicts.** On-access AV, an ICAP scan in front of storage, or EDR file reputation firing on an uploaded or newly written file. Treat a detection on a file under an upload directory as an incident, not just a quarantine line.
- **Network telemetry (host).** Sysmon **Event ID 3 (network connect)** for an outbound connection from the web app process to an unexpected host, which a shell commonly makes to fetch a second stage or beacon out.

What normal looks like: an upload feature receives the file types it is designed for, and their magic bytes match their declared type; uploaded files land outside the web root, or in a directory where the server is configured never to execute them; the web service account creates cache and session files, not new `.php` or `.aspx` scripts; the app process spawns only its known worker children; no `.htaccess` or `web.config` ever appears in an upload folder.

## Detection approach

Alert on the drop at the edge, on the new script file on disk, and on the execution, then correlate them so an upload with a type mismatch followed by a request to a new upload-directory path and a web-process shell becomes one high-confidence incident.

At the request layer, alert when a multipart upload carries a server-executable extension, a double or trailing extension, or a null byte in the filename; when the declared `Content-Type` or extension disagrees with the body's magic bytes; and, highest value, when a new path under the upload directory is requested shortly after the POST that created it. At the host layer, alert on a new server-side script or `.htaccess`/`web.config` written under the web root by the web service account, and on the application process spawning a shell, scripting interpreter, or LOLBIN. The new-script-under-web-root rule and the parent-child process rule are the detections to invest in first, because they survive an encrypted request body and a novel polyglot alike: the attacker still has to land a file and run it. Baseline which endpoints legitimately accept uploads and what those files should be before alerting broadly, so a genuine document upload does not page an analyst.

This skill ships **no UMBRASEC rule** for malicious file upload. Use these public, maintained rule and signature sources:

- NSA/CISA "Mitigating Web Shells": https://github.com/nsacyber/Mitigating-Web-Shells - ships Splunk detections, YARA rules, Snort signatures, HIPS rules, and a known-good web-directory comparison script aimed squarely at web shell drops.
- SigmaHQ: https://github.com/SigmaHQ/sigma - `rules/web/` for web shell access and upload-abuse request patterns, `rules/windows/process_creation/` for IIS `w3wp.exe` or `httpd` spawning a shell, and `rules/windows/file/file_event/` for a web shell file being written under the web root.
- OWASP CRS (ModSecurity Core Rule Set): https://github.com/coreruleset/coreruleset - `rules/REQUEST-922-MULTIPART-ATTACK.conf` for malformed multipart tricks, the restricted-file-extension controls in the request rules, and the PHP and Java attack rule files that carry script-payload signatures.
- Florian Roth signature-base (YARA): https://github.com/Neo23x0/signature-base - a large, maintained set of web shell YARA rules under `yara/` for scanning uploaded and web-root files.
- Elastic detection rules: https://github.com/elastic/detection-rules - web shell detection and suspicious child processes of web application servers.
- MITRE ATT&CK data sources and detections for T1505.003 and T1190: https://attack.mitre.org/techniques/T1505/003/ and https://attack.mitre.org/techniques/T1190/
- MITRE D3FEND countermeasures (below).

## Threat hunt

Hypothesis: an attacker has uploaded a web shell or polyglot through a public-facing upload feature and has run commands as the web service account.

1. From file-create telemetry over a long window, list every server-side script file (`.php`, `.phtml`, `.aspx`, `.ashx`, `.jsp`, `.jspx`, `.cfm`, `.svg`, `.htaccess`, `.config`) created or modified under a web-accessible path, excluding paths your deployment pipeline legitimately writes. Each hit is a candidate web shell. Rank by whether the directory is an upload folder and whether the service is internet-exposed.
2. For each candidate file, pivot to web and WAF logs for that host and window and look for the multipart POST that created it and any GET or POST to its path afterward. An upload with a type mismatch, then a request to the new file, then command-like parameters or responses, is near-conclusive.
3. Scan the web root and upload directories with a maintained web shell YARA set (signature-base) and diff the tree against known-good deployment state; a file present on disk but absent from the build artifact is suspect regardless of extension.
4. For files that look like valid images, check for polyglot content: scan the full file (not just the header) for script markers after the image data, and compare the file's magic bytes to its extension. A valid image that also contains `<?php` or `<%` is a polyglot.
5. From process telemetry, list every child process whose parent is a web application server (`w3wp.exe`, `php-fpm`, `httpd`, `nginx`, `java`, `tomcat`, `node`) and whose image is a shell, scripting interpreter, or LOLBIN; each is candidate web shell execution. Join back to the file and request signals above.
6. For the dormant shell that has been planted but not yet used, the standing exposure is the writable, executable upload path itself: enumerate upload directories that the web server is configured to execute, and treat each as an exposure to close even with no hit yet.

## Hardening

Shrink the standing surface (what the feature will accept and where it can execute) and the blast radius (what a shell can do once it lands). The mappings below use the control codes carried in the UMBRASEC [foundation guide](https://umbrasec.dev/guide/foundation.html) and [detection and response guide](https://umbrasec.dev/guide/detection-and-response.html); a cell is left blank where no single control maps cleanly.

| Control | What it does | Essential Eight | ISO 27001:2022 | NIST CSF 2.0 |
|---|---|---|---|---|
| Validate uploads server-side: allowlist expected types by magic bytes (not extension or `Content-Type`), reject double and trailing extensions and null bytes, re-encode images, and rename to a server-generated name with a fixed safe extension | Removes the spoofed-type and polyglot path into the handler | - | A.8.26 Application security requirements | PR.PS Platform Security |
| Store uploads outside the web root, or in a directory where the server is configured never to execute scripts (no PHP/ASP handler, deny `.htaccess`/`web.config` override), and serve them through a pass-through handler | A dropped script cannot be reached or run even if it lands | Application control | A.8.27 Secure system architecture and engineering principles | PR.PS Platform Security |
| Antimalware or ICAP scan of every upload before it is stored | Catches known web shells and malware at the boundary | - | A.8.7 Protection against malware | DE.CM Continuous Monitoring |
| Application control / disable interpreter execution in upload and temp directories | Blocks execution of any script that does slip in | Application control | A.8.19 Installation of software on operational systems | PR.PS Platform Security |
| Patch upload-handling and image-processing libraries (for example ImageMagick, parsers) | Closes the parser and conversion CVEs an upload can trigger | Patch applications | A.8.8 Management of technical vulnerabilities | ID.RA |
| Run the web app under a least-privilege service account with egress filtering | Limits what a web shell can reach and do once it executes | Restrict administrative privileges | A.8.2 Privileged access rights | PR.AA-05 Least privilege |
| Runtime monitoring on every web host (Sysmon/auditd plus EDR) | Produces the file-create, process, and network telemetry this detection depends on | - | A.8.16 Monitoring activities | DE.CM Continuous Monitoring |

## Honest limitations

Request-layer inspection only sees what it can read: an upload inside a TLS body that is not terminated and inspected, or a filename and content inside an encrypted or chunked stream, can slip past the edge, which is why the on-disk file and host-process signals matter more. A polyglot defeats magic-byte-only checks by construction, because its header is a genuinely valid image; catching it needs a full-file content scan and, better, re-encoding the image so embedded payloads are stripped. A new-script-file rule depends on complete file-create telemetry on the web root, and a deployment pipeline that writes scripts legitimately will generate false positives until its paths are allowlisted. The parent-child process rule misses a web shell used only for file read, data staging, or an in-process action that never spawns a child or opens a socket. A shell planted and left dormant produces no execution signal at all until it is used, so the file-create and known-good-diff signals are what find it in the meantime. None of this fires unattended: baseline your legitimate upload endpoints, expected file types, and your web servers' normal child processes first, correlate the request and host layers, and let an analyst make the call.

## ATT&CK and D3FEND

- MITRE ATT&CK: [T1505.003 Server Software Component: Web Shell](https://attack.mitre.org/techniques/T1505/003/) (Persistence) for the planted shell; [T1190 Exploit Public-Facing Application](https://attack.mitre.org/techniques/T1190/) (Initial Access) for the upload vector; follow-on [T1059 Command and Scripting Interpreter](https://attack.mitre.org/techniques/T1059/) (Execution); and [T1036.007 Double File Extension](https://attack.mitre.org/techniques/T1036/007/) and [T1036.008 Masquerade File Type](https://attack.mitre.org/techniques/T1036/008/) (Defense Evasion) for extension and content-type spoofing.
- MITRE D3FEND: File Content Rules (D3-FCR) and File Analysis (D3-FA) for inspecting uploaded content against its declared type and for web shell signatures; Process Spawn Analysis (D3-PSA) for catching the web server launching an unexpected child; Message Authentication (D3-MAN) and Software Update (D3-SU) for upload integrity and for patching the parser and handler. See https://d3fend.mitre.org/
- Public rule sources: NSA/CISA Mitigating-Web-Shells https://github.com/nsacyber/Mitigating-Web-Shells, SigmaHQ https://github.com/SigmaHQ/sigma, OWASP CRS https://github.com/coreruleset/coreruleset, signature-base YARA https://github.com/Neo23x0/signature-base, Elastic detection rules https://github.com/elastic/detection-rules.

**Scope note.** This is a defensive skill. It explains malicious file upload only to the depth needed to detect and mitigate it and deliberately ships no web shells, polyglot generators, or filter-bypass instructions. UMBRASEC publishes defense, not offense.
