account takeover · critical

From Static Domain to Account Takeover

5 min read r29k

A short walk through how a static page that looked empty led to leaking activation links and full account takeover.

Initial recon

I was doing recon when I stumbled upon a domain that was completely static. It barely counted as a website: a single image, a disabled HTML form off to the left, and nothing else. There were no directories to brute force, nothing archived in the Wayback Machine, and nothing useful in search. It looked boring, and I almost moved on. But the disabled form caught my attention, so I opened the page source to read the input names. That is a habit of mine when hunting for XSS or other client-side issues, the raw HTML is always the first place I look.

Populating the disabled form

The form had only three inputs: Name, Country and Email. I wanted to see if the page would accept those values straight from the URL, so I opened this:

request via url
https://sub.domain.com/index.html?name=test&country=test&email=test

With the values pre-filled, a Submit button appeared beneath the form. I clicked it and got an error: Invalid Country Code. I had typed test into the country field, so at first I assumed the server just wanted a real country, and I tried a few names. Each one threw the same error. It was only when I actually read the message that it clicked: it said country code, not country. The field wanted a code, not a name. I entered US, and the flow finally moved on.

Intercepting validation with Burp

The next message was Invalid Email Address. To see why, I turned on Burp and watched the traffic. The flow first called /countryValidate and then /emailValidate. The email validator returned a plain JSON response:

/emailValidate response
{ "is_valid": false, "reason": "domain" }

So I ran a simple test. In Burp, I edited the validation response, flipped the value to "is_valid": true, dropped the reason field, and forwarded it to my browser. With the response now reporting that validation had passed, the browser carried on and submitted the registration request to /register. The error was gone and the request went through successfully. The only catch was that no confirmation code ever landed in my email.

Activation token leaked in the response

After forwarding the registration request, I watched the server response in Burp. It included an activation link, in plain text, right in the body:

/register response
{ "activation_link": "https://auth.domain.com/activate?token=eyJhbGci..." }

I opened the activation link in my browser and it logged me straight into the account, no email inbox required. That was the first time I saw the activation token being handed back to the client.

Escalation to existing account takeover

Then I had an idea: what if I tried to register with an email that already had an account on the site? I used my first test account's email, the one I had registered earlier with the same technique. I worked through the flow again, bypassing the country code and the validation step, and when I sent the final request and intercepted the response in Burp, the activation link was sitting right there in it. I opened it directly and was logged straight into my first account, just from knowing the email address. That was a full account takeover, with no interaction required from the victim.

impact

Anyone who can trigger this flow and knows a target email address can obtain a valid activation token and take over that account, with no interaction required from the victim.

How the static page connects to the main app

The main web application had its own, legitimate registration form. If you submit a registration there and it fails validation, the site redirects you to this static domain, where the flawed flow lives. So the "boring" static page is reachable from the main site simply by following an error path.

The full attack chain

  1. Spotted a static page that looked empty, with a disabled form.
  2. Populated the form through URL parameters, which revealed a Submit button.
  3. Passed country validation with a valid code (US).
  4. Intercepted the /emailValidate response in Burp and flipped it to valid.
  5. The client then submitted the registration to /register.
  6. The server returned an activation link directly in the response body.
  7. Opening that link logged straight into the new account.
  8. Repeating with an already-registered email leaked that account's token, granting full access.

Key takeaway

It took about an hour from first spotting the static page to leaking an activation link. The lesson is simple: do not ignore static domains. Read the source, try simple URL parameters, and inspect the network calls. A little persistence and some muscle memory for these steps will surface real issues hiding behind pages that look boring at first glance.


If you want to talk or give feedback, DM me on Twitter @R29k_. Thanks for reading, see you in the next writeup.

more writeups
Account Takeover via Chained IDORs Privilege Escalation via Stored XSS From Finding AWS S3 Bucket to Sensitive Data Exposure
← all writeups