JSON Token Blog Header

The JSON Web Token (JWT) is a proposed internet standard for securely transmitting information between a client and a server in a compact and self-contained way. Simply, they are serialized JSON objects that hold encoded information related to the identity and characteristics of a client. That information is known as “claims”. The claims can then be verified and trusted since they are digitally signed by the server using a cryptographic algorithm. This ensures that its identity, or any characteristics, are not tampered with after the token is issued. Additionally, tokens can be encrypted to provide secrecy between the two parties.

In this blog, we will talk about some key concepts of JWTs and present CVE-2023-46943: a vulnerability related to JWTs found during one of our research activities.

Token structure

A JWT consists of three Base64Url encoded parts separated by dots (.):

  • Header: Holds info about the cryptographic algorithm used to sign the token.
  • Payload: Holds the token data or “claims”.
  • Signature: The digital signature that ensures the integrity of the token.

In its final form, it looks something like “xxxxx.yyyyy.zzzzz”.

The following image taken from jwt.io helps provide a better understanding of the JWT structure:

 

How Does Implicit Authentication of JWTs Work?

JWTs are mostly used in stateless applications, where no session is created on the server-side. When a user performs authentication and logs in, a token is generated and returned to the client.

Implicit authentication means that the user is then responsible for securely storing and managing the JWT. So, for subsequent requests to protected resources of the server, the client sends the token in the Authorization header using the Bearer schema.

This looks something like:

Authorization: Bearer <token>

After that, the server verifies the JWT validity and authenticity by checking its digital signature and if it contains the expected claims, such as the user data and allowed permissions. If the token is valid, the server grants access to the protected resources accordingly.

The following diagram depicts this authentication flow:

Proof-of-Concept of CVE-2023-46943

CVE-2023-46943 was discovered during research conducted on open source software by Checkmarx’ research group.  It is a good example that shows how JWT works and how important it is to implement it securely.

This CVE deals with a weak implementation of the JWT’s HMAC secret. Since the security of the token depends heavily on the strength of the HMAC secret used, signing the tokens with a weak secret makes it easy for attackers to crack it. If they are successful, they can use the predictable secret to create valid JWTs, allowing them to access sensitive information and actions within the application.

Let’s see how this plays out:

First, the attacker saves a JWT issued by the app and brute-forces it with common tools.

As the image above demonstrates – this was successful and the HMAC secret used for signing the tokens is literally “secret” (to illustrate how weak this secret is, it can be cracked in less than a second).

Now it is simple for an attacker to craft a valid JWT that claims an admin identity. We have the secret to sign the token and we just need to add the necessary admin data to its payload, in this case, the key “user.isAdmin” equal to “true”.

At this point, given a valid JWT, access is still limited to all the admin-protected resources (because there is more validation in place). Plus, the token must contain additional user data in its payload, such as its uuid. However, it is already possible to query users’ information, which contains the data we need.

And that’s it. The missing admin user details are returned by the above response, enabling an attacker to build a perfectly valid JWT (by adding that data to the JWT payload and resigning the token) and perform any actions in the application as an administrator.

JWT Security Best Practices

This CVE is just one example of a common JWT issue: Weak Token Secret. This can be mitigated in two ways:

  • Sign the token with a strong and unique secret, generated by using a secure source of randomness – specifically Cryptographically Secure Pseudo-Random Number Generators (CSPRNG) functions. It is also important that the generated secret is long enough since this makes it harder to brute-force. Consider at least 64 characters.
  • Sign the token with a more secure encryption algorithm, such as RSA.

Another potential problem is that hardcoding keys in shared open-source code can lead to a potential backdoor for unauthorized access. As multiple parties can use the application, when keys are hardcoded in the shared code, everyone can use it by default because it works out of the box. This represents a significant security risk, especially if there is nothing else forcing the users to configure the password, because everyone is then vulnerable and susceptible to exploitation. In this case, attackers could have complete unauthorized access to every application implemented.

Other security considerations include:

  • Choose a strong signature algorithm and only allow that one during validation of the token.
    • This ensures attackers don’t tamper with the encryption algorithm and invalidates the “None” algorithm.
  • Encrypt the token for added security.
    • This ensures that the token payload is kept secret which reduces the possibility for attackers to know and tamper with its data.
  • Carefully consider the token expiration date.
    • It is one of its mandatory payload fields, and a JWT is only truly stateless if it has a short expiration date, so if it never expires or this field is not considered during validation, a valid stolen token will always work.

To learn more about Checkmarx’ approach to SCA (Software Composition Analysis) security, and overall application security testing, request a demo of our Checkmarx One™ Application Security Platform today. Or sign up for a 14-day free trial here.