XSS Security Lab

Reflected XSS Demonstration

Test Your Knowledge

VULNERABLE - Reflected XSS

Lab #8: Reflected XSS in Image Upload

When "just a filename" becomes attacker-controlled HTML

Image upload forms often echo the uploaded file's name back to the user — e.g. "You uploaded: vacation.jpg" — so people can confirm the right file was selected. Developers frequently assume filenames are "just text" and skip sanitization when inserting that value into the page.

The problem: a filename is 100% attacker-controlled. On most operating systems and in the browser's File API, a filename can legally contain characters like <, >, and ". If that string is written into the DOM with innerHTML instead of a safe text-setting method, the browser parses it as markup — not as a display string — and any script inside it executes.

Try renaming a file (before selecting it) to something like:
<img src=x onerror=alert('XSS')>.jpg
then choose it below. The filename is reflected directly into the page via innerHTML.

Can't easily rename a real file? Use the simulate button to mimic a file whose name contains an XSS payload, exactly as the vulnerable code would receive it from input.files[0].name.

// VULNERABLE CODE
fileInput.addEventListener('change', function (e) {
    const fileName = e.target.files[0].name;
    document.getElementById('uploadResult').innerHTML =
        'You uploaded: ' + fileName;   // <-- raw HTML injection point
});

The Fix

Never insert user-controlled strings — including filenames, metadata, or anything else from an upload — into the DOM with innerHTML. Use textContent (or innerText) so the browser treats the value strictly as text, never as markup:

// FIXED CODE
fileInput.addEventListener('change', function (e) {
    const fileName = e.target.files[0].name;
    document.getElementById('uploadResult').textContent =
        'You uploaded: ' + fileName;   // safe: rendered as text, not parsed
});

Additional hardening for real-world upload features:

  • Validate file type and size server-side, not just in the client.
  • Never trust the client-supplied filename for storage — generate your own server-side identifier and store the original name only as metadata.
  • If the filename must ever be rendered as HTML (e.g. in an email template), HTML-encode it at output time (&lt;, &gt;, etc.).
  • Set a strict Content-Security-Policy as defense in depth, so even a missed sanitization point can't execute inline scripts.
  • Serve uploaded files from a separate doindex/subdoindex with no cookies or session access, so a stored file can't be used to pivot into an XSS attack on the index app.