Reflected XSS Demonstration
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
});
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:
<, >, etc.).Content-Security-Policy as defense in depth, so
even a missed sanitization point can't execute inline scripts.