This may be vulnerable. There are a few conditions that you didn't specify that are needed for it to be vulnerable, though:
- No kind of CSRF protection, intentional or otherwise, on the upload endpoint. If so (and it doesn't trust some other page you control and can host script on), you won't be able to launch any kind of cross-site attack at all, and would need a stored way to run script of your control within the target site for a cross-user attack.
- Uploading a file either navigates to the page with the reflected name, or the page with the reflected name dynamically updates if an upload happens from another page. In other words, you need to be able to initiate the upload from an external site (that you, the attacker, control), and cause the victim to see the result. The most common case for this is if the file is uploaded directly to the server using an HTML form and file-type input, but it might be possible even if the upload is script-driven, so long as the page that shows the reflected filename can be caused to show it from an external upload (without the user taking action on the vulnerable page).
- The file upload must use a content type that can be sent from an external site, such as
multipart/form-data
(the type used for file uploads from forms, though it can be used for any kind of POST request body data). Technically this requirement is part of condition #1 - if the server strictly enforced that the upload use content type application/json
, that is a viable CSRF protection and blocks this attack - but it's not always thought of as CSRF protection.
You might, given #3, see where this is going already. You can submit files via script. There are two ways to do this.
- The straightforward one is to use a CORS (or non-CORS fetch) request to simulate submission of a
<form action="{vulnerable page}" method="post" enctype="multipart/form-data"><input type="file"></form>
HTML form. This isn't that hard to do - it's a bit of typing, but fundamentally it's no different from other script-based requests, and because multipart/form-data
is an allowed content type for forms, it won't even make your request non-simple and require a CORS pre-flight. The problem is, this doesn't navigate the browser to the response, so it's only useful for XSS if you can send the user to a vulnerable page before or after submitting the attack, and have the payload reflected anyway. That's quite rare.
- The complicated[1] one is to create an HTML form on a page you control, dynamically set it to upload a blob and "filename" of your choosing, and submit it immediately using script. A lot of people will tell you this can't be done and a lot of file upload endpoints rely on that assumption (which means, among other things, they rarely have CSRF protection), but it's totally possible, just complicated. See https://stackoverflow.com/a/70485949/3909000[2] for the technique.
The full PoC attack page for the second approach - the one you'll probably have to use if this attack is possible at all, since I expect the vulnerable site will require a navigation request - might look something like this (changing any string with "target" in it as appropriate, of course):
<html><body>
<form action="https://target.site.tld/file/upload/endpoint" method="post" enctype="multipart/form-data">
<input name="target_input_name" type="file">
</form><script>
let dt = new DataTransfer()
let f = new File(['foo'], "<svg onload=\"alert('XSS!\n'+document.location+'\n'+document.cookie')\">.png", {type: 'image/png'})
dt.items.add(f)
document.querySelector('input').files = dt.files
document.forms[0].submit()
</script>
</body></html>
[1] I spent way too long researching this. You can't set an HTMLInputElement
's files
property to anything except a FileList
("HTMLInputElement.files setter: Value being assigned does not implement interface FileList.", you can't construct a FileList
("TypeError: Illegal constructor"), and you can't set the elements of a FileList
directly (it has a setter for item
but this just fails silently).
[2] The steps are:
- Create a
DataTransfer
object (let dt = new DataTransfer()
)
- Add a
File
object to its items
property (dt.items.add(new File(['fake file contents'], '<img src=x onerror=alert(1)>.png'))
)
- Access the DataTransfer object's
items
as a FileList
through the files
property and assign that to an HTMLInputElement's files
property (document.querySelector('input').files = dt.files
)