Prerequisites
- Target system, dependencies and environment configured.
Usage
Purpose
An upload feature lets a user put a file on your server. Get the validation wrong and that file becomes a web shell, a stored XSS payload, or an overwrite of something important. This skill covers testing the upload surface for those outcomes and the layered controls that make uploads safe.
When to use it
Any feature that accepts files: avatars, document uploads, import tools, attachments. The worst case — uploading a script that then executes — is why this is a high-severity area worth testing thoroughly.
Procedure
- Understand the upload: what types are accepted, where files are stored, and — critically — is the storage location web-accessible and does it execute code? An upload dir that runs
.php/.jsp/.aspx is the dangerous setup.
- Bypass type restrictions to upload a server-executable file. Test, in order, whether the app checks only:
- the extension (try
shell.php, then .php5, .phtml, .pHp, double extension shell.jpg.php, trailing dot/space, null byte on old stacks);
- the
Content-Type header (set it to image/png while the body is a script);
- magic bytes (prepend a real image header to a polyglot).
curl -F 'file=@shell.php;type=image/png' https://app.tld/upload
- If you get an executable file stored in a web-served path, request it and confirm code execution — the critical outcome:
curl "https://app.tld/uploads/shell.php?cmd=id"
- If code execution isn't possible, test lesser but real impacts:
- Stored XSS via an uploaded
.html/.svg served inline (SVG can carry script).
- Path traversal / overwrite via a crafted filename (
../../config) letting you write outside the intended dir.
- Content spoofing / malicious file hosting on a trusted domain.
- Test size and resource handling — huge files or zip bombs (decompression DoS) if the app unpacks archives.
Cheatsheet
extension bypass ladder
shell.php -> shell.php5 / .phtml / .phar -> shell.pHp
shell.jpg.php (double extension)
shell.php. (trailing dot) shell.php%00.jpg (null byte, legacy)
content-type spoof
filename=shell.php ; Content-Type: image/png
magic-byte / polyglot
prepend GIF89a; or a PNG header, then the script payload
lesser impacts if no RCE
.svg with <script> served inline -> stored XSS
../../path in filename -> traversal / overwrite
serve .html on the app's origin -> stored XSS / phishing on trusted domain
confirm RCE: request the uploaded file, pass a command
Reading the output
- An uploaded script executing (your command runs, the shell responds) = remote code execution, the top-severity outcome. Report immediately.
- A polyglot/renamed file accepted and stored in a web-executable path is RCE-adjacent even before you trigger it — flag the combination.
- An SVG/HTML served inline that runs script = stored XSS scoped to the app's trusted origin — serious even without RCE.
- A filename with
../ landing outside the upload dir = traversal/overwrite; impact depends on what you can clobber.
- Only client-side type checks (JS blocks it but the request still works when sent directly) = no real validation; treat as vulnerable.
The fix
Defence in depth — no single check is enough:
- Store uploads outside the web root, or in a location/bucket that never executes code, and serve them through a handler (or a separate cookieless domain) rather than by direct path.
- Validate type by allowlist, checking both extension and content (magic bytes / a real parse), not just the
Content-Type header. Reject anything not on the allowlist.
- Generate the stored filename yourself (random name + validated extension); never trust the client filename, which kills traversal and overwrite.
- Serve with
Content-Disposition: attachment and a correct Content-Type so files download rather than render, and set X-Content-Type-Options: nosniff.
- Limit size, scan with AV where relevant, and guard archive extraction against zip bombs.
- Re-encode images server-side where possible — it strips embedded payloads.
Pitfalls
- Checking
Content-Type or extension alone. Both are attacker-controlled; validate the actual content and use an allowlist.
- Client-side validation only. The JS check is bypassed by sending the request directly. Enforce on the server.
- Storing under the web root in an executable path. The single most dangerous choice — it turns a weak filter into RCE.
- Trusting the client filename. That's how traversal and overwrite happen. Rename server-side.
- Forgetting SVG/HTML. They're "images"/"documents" that can carry script when served inline.
References
- OWASP WSTG-BUSL-09 (Test Upload of Malicious Files)
- OWASP File Upload Cheat Sheet
- CWE-434 (Unrestricted Upload of File with Dangerous Type)
Inputs
- Relevant source code, logs, network traces, or system specifications.
Outputs
- Analysis findings, security audit report, or generated code artifacts.
1---2name: file-upload-vulnerabilities3description: Use when an app accepts file uploads — testing whether the upload can lead to code execution, stored XSS, or overwrite, and how to build a safe upload.4---5678## Prerequisites9- Target system, dependencies and environment configured.1011## Usage12### Purpose1314An upload feature lets a user put a file on your server. Get the validation wrong and that file becomes a web shell, a stored XSS payload, or an overwrite of something important. This skill covers testing the upload surface for those outcomes and the layered controls that make uploads safe.1516### When to use it1718Any feature that accepts files: avatars, document uploads, import tools, attachments. The worst case — uploading a script that then executes — is why this is a high-severity area worth testing thoroughly.1920### Procedure21221. Understand the upload: what types are accepted, where files are stored, and — critically — **is the storage location web-accessible and does it execute code**? An upload dir that runs `.php`/`.jsp`/`.aspx` is the dangerous setup.232. **Bypass type restrictions** to upload a server-executable file. Test, in order, whether the app checks only:24 - the extension (try `shell.php`, then `.php5`, `.phtml`, `.pHp`, double extension `shell.jpg.php`, trailing dot/space, null byte on old stacks);25 - the `Content-Type` header (set it to `image/png` while the body is a script);26 - **magic bytes** (prepend a real image header to a polyglot).27 ```28 curl -F 'file=@shell.php;type=image/png' https://app.tld/upload29 ```303. If you get an executable file stored in a web-served path, request it and confirm code execution — the critical outcome:31 ```32 curl "https://app.tld/uploads/shell.php?cmd=id"33 ```344. If code execution isn't possible, test lesser but real impacts:35 - **Stored XSS** via an uploaded `.html`/`.svg` served inline (SVG can carry script).36 - **Path traversal / overwrite** via a crafted filename (`../../config`) letting you write outside the intended dir.37 - **Content spoofing / malicious file hosting** on a trusted domain.385. Test **size and resource** handling — huge files or zip bombs (decompression DoS) if the app unpacks archives.3940### Cheatsheet4142```43extension bypass ladder44 shell.php -> shell.php5 / .phtml / .phar -> shell.pHp45 shell.jpg.php (double extension)46 shell.php. (trailing dot) shell.php%00.jpg (null byte, legacy)4748content-type spoof49 filename=shell.php ; Content-Type: image/png5051magic-byte / polyglot52 prepend GIF89a; or a PNG header, then the script payload5354lesser impacts if no RCE55 .svg with <script> served inline -> stored XSS56 ../../path in filename -> traversal / overwrite57 serve .html on the app's origin -> stored XSS / phishing on trusted domain5859confirm RCE: request the uploaded file, pass a command60```6162### Reading the output6364- **An uploaded script executing** (your command runs, the shell responds) = remote code execution, the top-severity outcome. Report immediately.65- **A polyglot/renamed file accepted and stored in a web-executable path** is RCE-adjacent even before you trigger it — flag the combination.66- **An SVG/HTML served inline that runs script** = stored XSS scoped to the app's trusted origin — serious even without RCE.67- **A filename with `../` landing outside the upload dir** = traversal/overwrite; impact depends on what you can clobber.68- **Only client-side type checks** (JS blocks it but the request still works when sent directly) = no real validation; treat as vulnerable.6970### The fix7172Defence in depth — no single check is enough:7374- **Store uploads outside the web root**, or in a location/bucket that never executes code, and serve them through a handler (or a separate cookieless domain) rather than by direct path.75- **Validate type by allowlist**, checking both extension and content (magic bytes / a real parse), not just the `Content-Type` header. Reject anything not on the allowlist.76- **Generate the stored filename yourself** (random name + validated extension); never trust the client filename, which kills traversal and overwrite.77- **Serve with `Content-Disposition: attachment`** and a correct `Content-Type` so files download rather than render, and set `X-Content-Type-Options: nosniff`.78- **Limit size**, scan with AV where relevant, and guard archive extraction against zip bombs.79- Re-encode images server-side where possible — it strips embedded payloads.8081### Pitfalls8283- **Checking `Content-Type` or extension alone.** Both are attacker-controlled; validate the actual content and use an allowlist.84- **Client-side validation only.** The JS check is bypassed by sending the request directly. Enforce on the server.85- **Storing under the web root in an executable path.** The single most dangerous choice — it turns a weak filter into RCE.86- **Trusting the client filename.** That's how traversal and overwrite happen. Rename server-side.87- **Forgetting SVG/HTML.** They're "images"/"documents" that can carry script when served inline.8889### References9091- OWASP WSTG-BUSL-09 (Test Upload of Malicious Files)92- OWASP File Upload Cheat Sheet93- CWE-434 (Unrestricted Upload of File with Dangerous Type)9495## Inputs96- Relevant source code, logs, network traces, or system specifications.9798## Outputs99- Analysis findings, security audit report, or generated code artifacts.