Turning Self-XSS to Exploitable XSS

Mahmoud Hamed
6 min readSep 25, 2023

--

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.

Signup Page with XSS payload 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 Forbiddenand 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 Emailand Password.

POST /login HTTP/2
Host: www.example.com

loginID=attacker@gmail.com&password=xxx123

The Response to that First Request was 200 OKand 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 firstNameValue (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 OKResponse. 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

  1. Create an account with an XSS payload in the First Name field.
  2. 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.
  3. 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.comto attacker+1@gmail.com

Change Email from Account Settings

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.

  1. Create an account with an XSS payload in the First Name field using an email that you own, (e.g.: attacker@gmail.com)
  2. 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.
  3. Go to the created Account Settings and change the email to another email that you also own (e.g.: attacker+1@gmail.com)
  4. You will receive a confirmation email on attacker@gmail.cominforming you that your email has been changed.
  5. Open the confirmation email and click on View web version to access the vulnerable XSS URL.
  6. 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

contacting support

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 Otherfiled, I got redirected tohttps://example.com/contact?submitted=true(Thank you page) and no alert happened :(

Thank you page

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 falseand after submitting the form its value became true.

So I decided to reopen https://example.com/contact?submitted=falseand yeah. the JS code got executed and there was an alert there :)

Here as you can see I injected the XSS into the Otherfield but all fields were vulnerable.

So let’s summarize how to get this self-XSS

  1. Open the Contact Support endpoint (https://example.com/contact?submitted=false)
  2. Inject the XSS payload in any field and Submit the form
  3. You will be redirected to the Thank you Page at (https://example.com/contact?submitted=true )
  4. Open the Contact Support endpoint again at (https://example.com/contact?submitted=false)
  5. 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 Submitbut 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 Numberfield 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

  1. Open the contact support endpoint (https://example.com/contact?submitted=false)
  2. 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.
  3. 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.

That was all for this write-up! If you have any questions or feedback, please don’t hesitate to DM me on LinkedIn or Twitter. See you in the next write-up!

--

--

Responses (5)