JSON Web Token is a compact mechanism used for transferring claims between two parties. These are generally represented as JSON objects and can be signed to protect the integrity of the underlying message using a Message Authentication Code (MAC) and/or encrypted. The mechanism followed by JWTs is governed by the standard RFC7519. A JSON Web Token [JWT] consists of three parts; an encoded Header, an encoded Payload and the Signature as shown below:
The Header contains metadata, defines the type of token and the algorithm used for encryption of Payload.
The Payload contains the claims to routes and services in attribute/value key pairs. This is where the bulk of the data is. A claim generally defines the identity of the intended end user and some additional attributes depending on the type of the claim such as:
Reserved Claims: Attributes defined here are pre-defined and are useful for enhancing interoperability like issuer (iss- the issuing entity), sub (subject of claim), expiration time (exp- this describes the duration for which the token will be valid), audience (the intended audience) and others.
Public Claims: These are defined to be used publicly and are defined in IANA JSON Web Token Registry or as a URI.
Private Claims: These claims are specifically used between two parties for sharing information and not to be defined by others.
Finally, the signature is calculated by encrypting the base64UrlEncoded values of Header and Payload using a secret Key.
Although JWT supports both symmetric and asymmetric encryption, we will be referring to symmetric HMAC SHA 256[HS256] for all encryption purposes in the following discussion as the application in question uses it.
Here is the example of a valid JWT for a request. In the figure below the right side represents a JWT containing three parts i.e. Header, Payload and the formula for calculating the Signature. The resultant token is created from the concatenation of the encoded Header, Payload and Signature which is shown on the left side.
JWT is perhaps most commonly used for authentication purposes in applications. Upon successful authentication, the application shares an authorization Token with the user. This becomes the identity of the user and needs to be shared for accessing any application resource. Ideally, the token should be protected using a strong secret Key.
In this blog, we discuss a case where we exploited a poor implementation of JWT for a target application that used JWTs for user authentication and authorization.
When parsing the initial requests, we could identify that the site was using JWT for authentication
Next, in the response we were able to find the encoded token containing our user profile information as shown below:
Upon login, we could access the Tasks, document management and other functionalities meant for a normal user as shown in the figure below:
After this, we traced back a little and analysed the authorization token we received earlier. We used the functionality provided at jwt.io to verify the signature for a variety of different key values. However, in most cases we didn’t get a valid signature by this method.
At this juncture, we could not form a valid signature that would help us modify the Payload and generate a valid corresponding token. What was missing was the ‘secret key’ which is used to sign the JWTs. We wrote a simple python script to brute force the ‘secret key’ from a set of guessable and random key strings (stored locally in a file -secret.txt) as shown below:
We eventually succeeded in brute- forcing the right secret key which was used to sign the JWT as shown below:
To confirm that this was indeed the correct token, we verified whether this value resulted in our previous signature given that the value of the Payload and the Header were unchanged:
Now that we had the secret key, we created a new token and added "admin" in the role parameter in the next request we crafted. Figure below shows the new JWT with the modified role value. The new token was signed using the secret key obtained in the previous step as shown below:
We used the token, created in the above step, in the Authorization Header which was accepted on the server side. This resulted in the previous user (site visitor), getting higher (administrative) privileges. Figure below shows the crafted token being passed in the request to the server:
Going further we also accessed admin functionality such as, Manage User by providing the crafted token to the application as shown below:
It is interesting to note that it was possible to impersonate users, for the system in the above case, by modifying the Bearer token with JWT payload.
While the application uses the JWT mechanism to secure its resources, it could be compromised. This was the result of a weak implementation where the secret key used in signing the JWTs was not strong enough and could be brute-forced. In the above scenario, once the secret key is obtained, a malicious actor can escalate his privileges in the application to access the administrative features. This is equally valid for both horizontal and vertical privilege escalation, or simply enumerate User Ids to impersonate other users.
Configuring strong values for sensitive data such as passwords or secret keys and protecting access to the same can go a long way in saving the application and its users from such attacks.