Skip to main content
Offensive360
Home / Knowledge Base / Unrestricted File Upload
High CWE-434 A04:2021 Insecure Design

Unrestricted File Upload

Unrestricted file upload vulnerabilities allow attackers to upload malicious files that can lead to remote code execution, XSS, or server compromise. Learn validation and storage best practices.

Affects: C#JavaJavaScriptPHPPythonRuby

What is Unrestricted File Upload?

Unrestricted file upload occurs when an application accepts files from users without properly validating their content, type, or name. At minimum, attackers can upload files that cause denial of service (large files, zip bombs). At worst, they can upload server-side scripts (webshells) and achieve Remote Code Execution (RCE) if the upload directory is web-accessible.

Even when direct execution is prevented, malicious files can introduce stored XSS (e.g., an SVG with embedded script), SSRF via crafted XML in Office documents, or path traversal if the original filename is used without sanitization.

How exploitation works

An attacker uploads a PHP webshell to a profile photo endpoint:

POST /api/upload-avatar HTTP/1.1
Content-Type: multipart/form-data

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

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

If the server stores the file under its original name in a web-accessible directory and the web server executes PHP, the attacker visits https://site.com/uploads/shell.php?cmd=id and has achieved RCE.

Vulnerable code examples

C# / ASP.NET Core

// VULNERABLE: Uses original filename, stores in web root, no type validation
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
    var path = Path.Combine("wwwroot/uploads", file.FileName); // Path traversal risk
    using var stream = System.IO.File.Create(path);
    await file.CopyToAsync(stream);
    return Ok(new { url = "/uploads/" + file.FileName });
}

PHP

// VULNERABLE: Trusts Content-Type header, uses original filename
$target = "uploads/" . $_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'], $target);

Secure code examples

C# / ASP.NET Core — validated upload

// SECURE: Extension allow-list, magic bytes check, random filename, outside web root
private static readonly HashSet<string> AllowedExtensions = new() { ".jpg", ".jpeg", ".png", ".gif" };
private static readonly string UploadPath = "/var/app/uploads"; // Outside wwwroot

[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
    var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
    if (!AllowedExtensions.Contains(ext)) return BadRequest("File type not allowed.");
    if (file.Length > 5 * 1024 * 1024) return BadRequest("File too large.");

    // Validate magic bytes for images
    using var reader = new BinaryReader(file.OpenReadStream());
    var header = reader.ReadBytes(4);
    if (!IsValidImageHeader(header)) return BadRequest("Invalid file content.");

    var safeFileName = $"{Guid.NewGuid()}{ext}";
    var fullPath = Path.Combine(UploadPath, safeFileName);
    using var stream = System.IO.File.Create(fullPath);
    await file.CopyToAsync(stream);
    return Ok(new { id = safeFileName });
}

Python / Django — secure upload

import uuid, os, imghdr
from django.core.exceptions import ValidationError

ALLOWED_TYPES = {'jpeg', 'png', 'gif'}
MAX_SIZE = 5 * 1024 * 1024

def handle_upload(request):
    f = request.FILES['file']
    if f.size > MAX_SIZE:
        raise ValidationError("File too large")
    img_type = imghdr.what(f)
    if img_type not in ALLOWED_TYPES:
        raise ValidationError("File type not permitted")
    # Generate a random name — never use original filename
    safe_name = f"{uuid.uuid4()}.{img_type}"
    save_path = os.path.join('/var/uploads', safe_name)  # Outside web root
    with open(save_path, 'wb') as dest:
        for chunk in f.chunks():
            dest.write(chunk)
    return safe_name

What Offensive360 detects

  • Unvalidated file extensions — Upload handlers that do not check or allow-list file extensions
  • Original filename usage — Code that uses file.FileName or $_FILES[...]['name'] directly as the storage path
  • Content-Type trust — Validation relying solely on the Content-Type header (attacker-controlled)
  • Web-accessible upload paths — Files stored within the web server’s document root without execution restrictions
  • Missing size limits — Upload handlers with no file size constraint

Remediation guidance

  1. Validate by content, not extension or MIME type — Check magic bytes (file header bytes) to confirm the actual file format.

  2. Use an extension allow-list — Define exactly which extensions are permitted; deny everything else.

  3. Generate random filenames — Never use the original filename. Generate a UUID and append the validated extension.

  4. Store uploads outside the web root — Files should not be directly accessible via URL. Serve them through a controlled endpoint that sets Content-Disposition: attachment.

  5. Set file size limits — Enforce maximum file sizes at both the application and web server levels.

  6. Re-encode images — For image uploads, re-encode the file server-side (e.g., with ImageSharp or Pillow) to strip any embedded malicious content.

References

By Offensive360 Security Research Reviewed: March 2026

Detect Unrestricted File Upload automatically

Run Offensive360 SAST on your codebase to find this and 100+ other vulnerabilities.