Cybreach Consulting Prendre RDV
Back to articles
Vulnerabilities

Arbitrary file upload: insufficient validation and code execution

File upload becomes a critical vulnerability when the application allows uploading executable files into a directory accessible by the web server. An attacker can upload a webshell (PHP, JSP, ASPX file containing malicious code), access it via a URL, and execute commands on the server. It's one of the most direct exploitation paths to full compromise.

Minimal PHP webshell

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

# Accès : https://app.example.com/uploads/shell.php?cmd=id
# Résultat : uid=33(www-data) gid=33(www-data)

Bypassing client-side validations

Client-side file type validation (HTML form accept attribute, JavaScript MIME type check) can be bypassed in seconds. An attacker intercepts the HTTP request after the browser has built it (with Burp Suite or a proxy) and modifies the file extension or Content-Type before sending. These validations can improve user experience but have no security value.

Required server-side validations

All validations must be performed server-side. Checks to perform: file extension (whitelist of allowed extensions, not a blacklist), actual MIME type (read the file's magic bytes, don't trust the request's Content-Type), maximum size, filename (generate a random name, never use the user-supplied name), content (scan image files with a dedicated library to detect hidden code).

Secure upload validation (Node.js)

const path = require('path');
const crypto = require('crypto');
const FileType = require('file-type');

async function handleUpload(file) {
  // 1. Vérification par magic bytes (pas par extension ou Content-Type)
  const type = await FileType.fromBuffer(file.buffer);
  const ALLOWED = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
  if (!type || !ALLOWED.includes(type.mime)) {
    throw new Error('Type de fichier non autorisé');
  }

  // 2. Nom de fichier aléatoire avec extension validée
  const ext = type.ext;
  const safeName = crypto.randomBytes(16).toString('hex') + '.' + ext;

  // 3. Stockage hors du répertoire web (non exécutable)
  const dest = path.join('/var/uploads', safeName);
  await fs.writeFile(dest, file.buffer);

  return safeName;
}

Isolating uploaded files

Even with correct validation, uploaded files must never be stored in a directory directly accessible by the web server. Store them outside the document root (a directory the web server doesn't serve directly) and serve them via a dedicated endpoint that controls access. This architecture ensures that even if a malicious file passes validation, it cannot be executed.

For applications that accept images, consider reprocessing them (resize, re-encode) with an image library before storage. This operation destroys most malicious payloads potentially hidden in metadata or pixels.

A question after reading?

Does your application accept uploaded files? Send a message.

Or book a 30-minute scoping call directly

Book a call