From File Upload to Full Account Takeover — Chaining Unrestricted Upload, Stored XSS, and CSRF
Summary
While testing on an external bug bounty target I found an unrestricted file upload vulnerability that allowed me to upload an html file to a subdomain of the main target (which was technically out of scope), so a stored XSS impact would be out of scope so since a subdomain is a same site (not same origin) I could bypass same site restrictions on the cookies and I also bypassed Authorization header validation this allowed my to perform a CSRF attack that changes user password which led to an ATO.
📌First Stage of the story is a low impact IDOR
first, during my testing on this web app let me call it target.com there was a file upload function that allowed me to upload files to a post for my own organization publicly or for certain roles.
when I first uploaded an image the response had the URL to which the image was uploaded to
https://files.target.com/<id>/as/<user_id>/img.jpg
URL has two identifiers
File id: this is 32 unpredictable identifier
User id : this is incremental and can be predicted.
At first, I tested for an IDOR, so I tried to access it from another browser in which I have no active section in this app and it loaded the file which is supposed to be in a private post.
But this IDOR will be low impact and might be considered informative because to prove an IDOR's impact most of the time you have to show how you can predict or access the identifier to access other users resources.
keep in mind yes it has low impact if you want to access other users resources, but not if you want them to access yours
I took note of this IDOR and kept going.
📌Second stage Unrestricted file upload to stored XSS
Then I tried to upload harmful file types and I found that there are no restrictions on file extension, content type, or file content, so I tried to upload a .html file to inject a stored XSS POC that triggers a simple alert.

As shown in the screenshot it was uploaded successfully and when I accessed the URL it triggered the alert.

Now we have stored XSS, but files.target.com is out of scope, so it is worthless in this case unless I can use it to get an impact on the main app.
So let's take note of this one also and keep going
📌Authorization header validation bypass
I had a note on this app since I started my testing which is that all the requests to do any action are sent to the same API endpoint /api/v3/batch and have included in the body a requests parameter that contains another API URL with a request method and body content like the following.

when the URL is decoded it is a JSON object that looks like the following
what first comes to mind is to test for an SSRF, but it had a very strong filter i tried every technique I know to bypass and still couldn't and this was somewhat expected, as SSRF is often one of the first things researchers attempt when they encounter this type of structure.
I then tried to skip that fixed endpoint /api/v3/batch and send the HTTP request to the second endpoint directly api/v1/users/heartbeat with my credentials and i got the same response, then i asked myself why they have this extra layer if it has no use why don't they send requests to the second API endpoint directly?
Each request has a session cookie and an Authorization header that contains a Bearer token.

so I started to compare the difference when I sent the request to the fixed endpoint /api/v3/batch let's call it "the general" and then I send it to the endpoint api/v1/users/heartbeat that really perform the action directly the "the exclusive"
With Authorization header only
working
not working
With cookie only
working
working
Notice that the Second API accept either of the cookie or the header to be present.
boom if i send to the second API endpoints directly i will bypass the Authorization header validation and request will rely solely on the cookie.
Now we can perform a CSRF attack and induce a user to carry do any action on the app using the session cookie which has same-site attribute left to default LAX on chrome and None on firefox.
But a CSRF on its own will be limited because it will be applicable only on some browsers that have the same site default value None like Firefox and Safari but will not work on Chrome which has more than 66% of browsers' market share, So i won't stop here for sure.
take a look at :https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#browser_compatibility
📌Last stage CSRF and ATO
This was the moment when it all connected in my head
if I create a CSRF payload and upload it in the file upload function since the files.target.com is a same-site I can bypass same-site restrictions even if the sameSite attribute of the session cookie is set to strict.
now I have to pick a critical action to perform using this CSRF and I picked password change because it didn't ask the user for the old password to change it
so here is the payload I created:

when I opened the payload URL my account password was changed to helloFromTheOtherSide and I was automatically logged out of the account.
And I finally got an ATO ✅.
📍Escalation attempt
I had an idea that I wanted to escalate to a CORS attack but I couldn't because files.target.com is a same site yes but not same origin. Therefore, I had to find a CORS misconfigurations vulnerability to do this and this will be a little more severe because CSRF is a one-way interaction you can't read or exfiltrate data from the response but with CORS you can do.
Some might ask why cors would've been more severe while both will lead to an account takeover?
The key difference lies in data exposure and scalability of the attack. In the change password endpoint, the response included a significant amount of sensitive user information—such as the user's email, name, ID, the emails of other users in their organization, and even their Authorization Bearer token.

With CSRF, the attacker must target individual users by luring them into visiting a specific payload URL. The impact is limited to those specific victims.
With CORS misconfiguration, an attacker could host a malicious site publicly, and harvest sensitive data (including email, other PII and tokens) from any user who clicks the link, without needing to know or target them individually.
This makes CORS exploitation not only easier to scale but also much more impactful, as it enables mass data exfiltration without interaction from the attacker beyond sharing a link.
Thank you for taking the time to read this write-up. I know it was a bit lengthy, but I wanted to walk you through my thought process and present the scenario as a story rather than just a technical summary. My goal was to make it both informative and engaging, and I truly hope you found it helpful and interesting. Thanks again for reading!
Last updated