This is a quick blog about a bug I found in a private bounty program on Bugcrowd. The reason for me writing about it is to increase awareness around these issues and implementation flaws so that fellow bug bounty hunters/people in Infosec/developers can use the information in this article for the betterment of security.
The bug I found was in an application’s implementation of the JSON Web Token (JWT) refresh token.
Usually, in response to a JWT authentication/refresh request you’ll get something that looks like this:
{"code":0,"data":{"access_token":"XXX.YYY.ZZZ","access_token_expiration":"Thursday, November 9th, 2017, 10:27:33 PM","refresh_token":"ABC123"}}
Here you have an access token and a refresh token. Both are very sensitive and should never be leaked… in an ideal world 🙂
The access token in this example expires on the 9th November 2017 at 10:27:33PM. Usually a (proactive/well designed) mobile/web/client application will use the refresh token to refresh/get a new access token before the expiry date. That request goes to an Authorization Server and looks a bit like this:
POST /auth/refresh HTTP/1.1 Host: auth.example.com Content-Type: application/json Authorization: Bearer XXX.YYY.ZZZ {"refresh_token":"ABC123"}
The current access token goes in the Authorization header and the refresh token in the POST body. In return you get a brand new access token, expiry date and refresh token. All good so far.
But what happens if the access token gets leaked or compromised? Well it shouldn’t matter because that would mean the refresh token would also have had to be leaked and be valid and not be used already (refresh tokens should be one-time use) and not be revoked.
All of this relies on the Authorization Server doing its job, properly.
In the case of the bug that I had found, the Authorization Server was not checking the refresh token <–> access token association. What this meant was that I could refresh someone else’s access token using my refresh token.
In theory this could have allowed an attacker to grab an old JWT access token (it doesn’t matter if it’s a day old or a year old – the token is cryptographically signed by the server so it would still be valid even if it has expired) and use a refresh token of a test account to get a brand new, valid access token for the victim account. The impact here is significant and it would be very difficult to revoke an access token in this scenario. Here’s what the JWT handbook has to say about refresh tokens:
“Refresh tokens, by virtue of being long-lived, must be protected from leaks. In the event of a leak,
blacklisting may be necessary in the server (short-lived access tokens force refresh tokens to be used
eventually, thus protecting the resource after it gets blacklisted and all access tokens are expired)”
In the above, misconfigured scenario… the remediation step of revoking a token makes no difference because an attacker can use any refresh token.
The fix? Make sure the Authorization Server validates the refresh token belongs to the user submitting the access token. An access token should never be refreshed without the corresponding, correct refresh token.
One reply on “JWT Refresh Token Manipulation”
Mikail, you are a rock. Personally, I have gotten a lot of information through the post. Great article thanks and keep up the great work!