One Endpoint, Two Account Takeovers

Hello everyone, last year I found an interesting account takeover on a bug bounty program named Namava, they were rather quick patching the vulnerability, but I was also able to break the patch with the help of a friend.
In this blog post, we are going to discuss the steps from the beginning until the end.

First Account Takeover

Namava is an online TV shows platform like Netflix, it has wide scope (*) and quite interesting functionalities to test for. As always, I started doing basic recon on the target and found the interesting subdomain tv.namava.ir .
This web application was designed to ease the user’s login process. so instead of using TVs to log in, they can use their web application.
Users are given a QR code after clicking the “login by code” button. Here was the exact flow:

  1. The user use the “login by code” feature, to get a QR code and a token from tv.namava.ir.
  2. The user’s browser sends the request containing the previously generated token to the TV application backend each 10 seconds to check the token status.
  3. If the user who has already logged in to the main website scans the QR code and the login link will be opened on their browser.
  4. The application backend (namava.ir) verifies the token in a hidden channel with an API call to tv.namava.ir, if the token is correct, then the token's status will be changed to verified.
  5. Since the token has been verified, the user will be logged in with the subsequent request.

Here are the screenshots regarding the generated HTTP requests to make the above steps more clear. The first step, login by token feature:

Here is an example API call to validate the token:

The response, if the token is not verified (QR code is not scanned):

The issued authentication token after opening the QR code link:

The flow mentioned is vulnerable by design. You may ask why? In general, one-click login without the user’s confirmation is not safe. The confirmation terms refer to an action requiring a user’s interaction. So what’s the attacking scenario here?

  1. The attacker opens the website and clicks on the “login by code” button.
  2. The attacker scans the QR code and extracts the login link containing the token parameter.
  3. The attacker makes a hidden iframe sourced to the login link.
  4. If the victim visits the URL while logged in to namava.ir, then the token will be verified and the attacker gets access to the victim’s account.

If the victim is lured to open the attacker’s website while logged in to the Namava application, the account takeover will be possible.

The exploit code is as follows (the link is generated in tv.namava.ir):

<!DOCTYPE html>
<html>
  <head>
    <title>Test</title>
  </head>
  <body>
    <h1>Hello!</h1>
    <iframe 
     src="https://www.namava.ir/a/nn4zqq" style="width: 0; height: 0; border: 0; border: none;">
    </iframe>
  </body>
</html>

This was enough proof of concept for the Namava team to triage the vulnerability and pay the bounty. They also patched the vulnerability rather quickly but it wasn’t a proper patch for this issue.

Bypassing the Patch – Second Account Takeover

After approximately two weeks, when I came back to the program to verify the patch. they’ve implemented a confirmation button (Arrival on TV) after opening the login link.

After clicking on the “Arrival on TV” button, there was an HTTP request to namava.ir to confirm that the request wasn’t forged (CSRF). The attacking scenario is to check CSRF bypassing methods. The confirmation request:

There was no CSRF token on the request, although it wasn’t possible to accomplish CSRF as there were several other headers such as X-Application-Type and X-Auth-Token that makes the browser send a preflight request as the request is not simple.

Their protections against the CSRF were to implement custom headers (X-Auth-Token and X-Application-Type) and the JSON content type leading to a preflight request.

When it comes to cross-site requests, there are several rules applied by the browsers, one of them is the preflight request (OPTIONS Request), and if the CORS configurations allow it, then the original request will be sent. If you are unfamiliar with this concept I’d suggest you read this link from Mozilla’s website, then this post which explains cross-site requests and browser security features.

I tried several tests, one of which was to remove the X-Application-Type and X-Auth-Token headers, to check if the user’s authentication is kept by the cookies. I was amazed that this method worked, so I was one step closer to change the request to a simple request.
The other issue was with the JSON content type. I changed the Content-type header to application/x-www-form-urlencoded, and it worked! so the request was then a simple request with no preflights requests needed.

The response:

The request was vulnerable to CSRF. The exploit code:

<html>
<body>
  <form action="https://www.namava.ir/api/v1.0/accounts/login/by-remote/verify" method="POST" target="namava">
  <input type="hidden" name="fastLoginCode" value="n27494" />
  </form>		
<iframe name="namava"></iframe>
<script>
  document.forms[0].submit();
  let iframe=document.getElementsByTagName("iframe")[0];
  iframe.style.display="none";
</script>
</body>
</html>

However, another protection was not discovered yet until then. After the exploit code was executed, the server responded:

The server was checking the Origin header and it didn’t accept external origins. The HTTP request was sent with the help of exploit code:

It seemed the flaw was un-exploitable, after digging a bit more revealed that the server was accepting a null origin as a valid value.

The null value as origin header may lead to different security flaws, if you are unfamiliar with the concept please visit the Portswigger’s academy labs on this topic.

The response:

The final exploit code:

<html>
  <iframe src="data:text/html;base64,PHNjcmlwdD4KICAgICAgZnVuY3Rpb24gc3VibWl0UmVxdWVzdCgpCiAgICAgIHsKICAgICAgICB2YXIgeGhyID0gbmV3IFhNTEh0dHBSZXF1ZXN0KCk7CiAgICAgICAgeGhyLm9wZW4oIlBPU1QiLCAiaHR0cHM6Ly93d3cubmFtYXZhLmlyL2FwaS92MS4wL2FjY291bnRzL2xvZ2luL2J5LXJlbW90ZS92ZXJpZnkiLCB0cnVlKTsKICAgICAgICB4aHIuc2V0UmVxdWVzdEhlYWRlcigiQ29udGVudC1UeXBlIiwgImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIpOwogICAgICAgIHhoci53aXRoQ3JlZGVudGlhbHMgPSB0cnVlOwogICAgICAgIHhoci5zZW5kKCJmYXN0TG9naW5Db2RlPTdyNTdteiIpOwogICAgICB9CiAgICAgIHN1Ym1pdFJlcXVlc3QoKQo8L3NjcmlwdD4="></iframe>
  <script>
    let iframe = document.getElementsByTagName("iframe")[0];
    iframe.style.display = "none";
  </script>
</html>
<h1>Hello</h1>

Conclusion

When it comes to CSRF, there are several protections and bypassing methods, I will discuss each:

Protections

  • The best protection is using CSRF tokens.
  • If custom HTTP headers are used with the purpose of CSRF protection, then they should be compulsory.
  • The common security practices should be implemented on the Content-type header. The developers usually use libraries that supports different content types which makes this bypassing method possible.
  • The origin header should be correctly whitelisted, the null value is commonly forgotten by developers.

Bypasses

  • Eliminating custom headers, they could be removable with no change on the website.
  • Changing the Content-type header to common simple requests header.
  • Using various bypassing methods for the origin header.

I’m hoping that you liked this write-up. leave me your feedback on the topics you would like to read more about.
Thanks for reading!

3 Replies to “One Endpoint, Two Account Takeovers”

  1. hey 🙂
    “One-Click login without the user’s Confirmation is not Safe”
    This is yours??!! a Masterpiece!
    Can you introduce books about this subject? like account takeover. Except WA Hackers HandBooK!

    generally…that was perfect!

Leave a Reply

Your email address will not be published. Required fields are marked *