Turning Self-XSS to Exploitable XSS
In this write-up, I will explain two cases of Self-XSS where I managed to escalate them into something impactful. Let’s jump right into the story.
First XSS
The First XSS was found on a private bug bounty program. Let’s start with how I find this Self-XSS.
This target was an online store and there was a signup page. I believe you got where to inject the XSS payload. right! In the First Name.
After creating the account with the XSS payload in the First Name, When I tried to log into my account I got 403 Forbidden
and I was not able to log into my account.
At that point I was curious why I was not able to log into my account so I opened the Burp and intercepted the login request to see why I was getting 403 Forbidden
. I realized that to log into my account two requests were sent to the server.
The First Request was a POST request that had my Email
and Password
.
POST /login HTTP/2
Host: www.example.com
loginID=attacker@gmail.com&password=xxx123
The Response to that First Request was 200 OK
and it has my Info like FirstName
, lastName
, UID
, DateOfBirth
, etc…
The Second Request was a POST request that had the same data from the previous First Response
POST /login HTTP/2
Host: www.example.com
Data={"eventName":"login","remember":false,"UID":"9046b58424b84429ab2d5b811baer3xc",
"profile":{"firstName":"<svg+Only=1+onload=alert(1)>","lastName":"sssss","email":"attacker@gmail.com","zip":"55551"},
"data":{"phoneNumber":"2111443343","smsOptin":false,"dateOfBirth":"11/11/1999","emailOptin":true}}
The Response of that Second Request was 403 Forbidden
. So here I knew that the problem was on that Second Request. After thinking for a while I thought maybe there was a kind of WAF or Server-Side validation on that Second Request and since there was an XSS payload in firstName
so maybe this was the problem. so I sent that request again but I deleted the firstName
Value (which contains the XSS payload).
POST /login HTTP/2
Host: www.example.com
Data={"eventName":"login","remember":false,"UID":"9046b58424b84429ab2d5b811baer3xc",
"profile":{"firstName":"","lastName":"sssss","email":"attacker@gmail.com","zip":"55551"},
"data":{"phoneNumber":"2111443343","smsOptin":false,"dateOfBirth":"11/11/1999","emailOptin":true}}
And YES the Response changed from 403 Forbidden
to 200 OK
Response. at that point, I was able to log into my account successfully, and the Self-XSS alert :)
So let’s summarize how to get this Self-XSS
- Create an account with an XSS payload in the
First Name
field. - log in to your previously created account while intercepting the login requests, and remove the XSS payload from the
firstName
parameter of the second request. - You will be able to view your account and see the Self-XSS popup.
Now after that all, we got a useless Self-XSS. At that point, I was looking at how to turn this Self-XSS into something impactful, so I looked for any possible chain I could do via Cache Poisoning or Cache Deception - but it seemed to be impossible. It looks like bombón found Cache Deception at the same target :)
After some time by going to Account Settings, I noticed that I could change my account email, So I changed it from attacker@gmail.com
to attacker+1@gmail.com
When I changed my email I found that I had received an email from the company informing me that my information had changed.
As you can see from the previous screenshot on that email there is an option to click on View web version
. so I clicked on it.
And YES, the XSS alerts. now all I have to do is to send that link to the victim
So let’s summarize the whole bug.
- Create an account with an XSS payload in the
First Name
field using an email that you own, (e.g.:attacker@gmail.com
) - log in to your previously created account while intercepting the login request, and remove the XSS payload from the
firstName
parameter of the second request. - Go to the created Account Settings and change the email to another email that you also own (e.g.:
attacker+1@gmail.com
) - You will receive a confirmation email on
attacker@gmail.com
informing you that your email has been changed. - Open the confirmation email and click on
View web version
to access the vulnerable XSS URL. - Copy that vulnerable XSS URL and send it to the victim.
Second XSS
The second XSS was In another bug bounty program. Let’s start with finding the Self-XSS part.
Firstly I found an endpoint which is for contacting support https://example.com/contact?submitted=false
Yes, Exactly as you thought, we will inject the XSS payload in any field and we will submit the form.
After submitting the form with the XSS payload in the Other
filed, I got redirected tohttps://example.com/contact?submitted=true
(Thank you page) and no alert happened :(
Okay for some of you, you will stop here. but what caught my attention here was the parameter ?submitted=
as before submitting the form, its value was false
and after submitting the form its value became true
.
So I decided to reopen https://example.com/contact?submitted=false
and yeah. the JS code got executed and there was an alert there :)
Here as you can see I injected the XSS into the Other
field but all fields were vulnerable.
So let’s summarize how to get this self-XSS
- Open the Contact Support endpoint (
https://example.com/contact?submitted=false
) - Inject the XSS payload in any field and Submit the form
- You will be redirected to the Thank you Page at (
https://example.com/contact?submitted=true
) - Open the Contact Support endpoint again at (
https://example.com/contact?submitted=false
) - You will find the XSS alert :)
At that point, I said, “Oh shit, this Self-XSS requires too many steps to be reducible”. so I assigned the first task to myself which was to make this Self-XSS easier to reproduce.
If you look at the Self-XSS behavior you will find that when you enter JS code it got executed on the page when we click on Submit
but we didn’t see the XSS alert as we got redirected to another endpoint before the XSS got executed. So my main task here was to find a way to make the redirection not happen so we can see the XSS alert.
After some time I found a way to do so, which was to enter anything wrong in any field like entering xxxx in the Phone Number
field or leaving any Required Field missing in the form, and then submit the form. by doing that way the JS will render on the page and the redirection will not happen (because there will be something wrong/missing on the contact page) so the XSS will alert
Now let’s summarize the new shorter way to get the Self-XSS
- Open the contact support endpoint (
https://example.com/contact?submitted=false
) - Inject the XSS payload in any field BUT enter anything wrong or leave any required field missing in the form and then Submit the form.
- You will find the XSS alert :)
As you can see the Self-XSS now is much easier to exploit. at this point, I said “It’s time to make some money from that Self-XSS”
Turning this Self-XSS to good XSS was easy. I intercepted the request of submitting the form and it was a POST request. I changed the POST request to a GET request and it worked fine. the point here was to make sure that you leave some required parameters (fields) empty so the XSS works.
https://example.com/contact?projectTypes=Other&projectTypes=e"><img+src=x+onerror=alert(1)>&default-method=contactMe&FirstName=&LastName=&Email=&PhoneNumber=&CallTime=
As you can see the final PoC URL has many missing values for required parameters (fields) like FirstName
, LastName
, Email
& PhoneNumber
So the Final PoC was a GET URL like any normal reflected XSS.