1013 words
5 minutes
A Brief Look at JWT Security

JWT was introduced to avoid frequent database lookups for maintaining HTTP session state. Unlike Redis, JWT stores most (or all) session information inside the token itself. By parsing the JWT, the application can retrieve session state directly — but this also introduces security risks.

Definition#

JSON Web Token (JWT) is currently one of the most popular cross-domain authentication solutions, and is an authentication/authorization mechanism. JWT is an open standard based on JSON for transmitting claims between parties in a networked environment. The token is designed to be compact and safe, and is especially suitable for Single Sign-On (SSO) in distributed sites. JWT claims are typically used to transmit authenticated user identity information between an identity provider and a service provider, so that resources can be retrieved from a resource server. It can also carry additional claims required by business logic. The token can be used directly for authentication and can also be encrypted.

JWT Structure#

A JWT consists of three parts separated by dots (.): header, payload, and signature.

Example JWT: JWTeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MZiW2KkIRI6GhKsu16Me7-3IpS4nBw1W47CW67QAqS0

As mentioned above, JWT has three dot-separated segments, and uses base64url encoding.

base64url replaces + with -, / with _, and removes = padding to avoid ambiguity.

Decoded segments:

  • {"alg":"HS256","typ":"JWT"}
  • {"sub":"1234567890","name":"John Doe","iat":1516239022}
  • Signature

image-20231122014746811

The header fields are easy to understand:

  • typ specifies the type (always JWT)
  • alg specifies the signing algorithm (e.g., HS256)

Sometimes a jwk field may also appear (and it can become a security issue).

Payload#

The payload contains three types of claims: registered claims, public claims, and private claims.

Registered claims:

  • iss: issuer
  • sub: subject
  • aud: audience
  • exp: expiration time (must be later than issue time)
  • nbf: not before
  • iat: issued at
  • jti: unique token id, often used as a one-time token identifier to avoid replay attacks

Public claims: can include any information, usually user-related data or other required business data. Sensitive information is not recommended, because this part is client-decodable.

Private claims: defined between producer and consumer. Sensitive information is also not recommended, because base64 is reversible (so it is essentially plaintext).

Signature#

The signature is the last part of the JWT. It’s generated as shown in the highlighted part in the diagram above. The secret is the key used for signing. The resulting bytes are then base64url-encoded, forming the signature segment.

Security Issues#

Sensitive Data Exposure#

Because the first two parts (header and payload) are not encrypted, anyone can decode them with base64url and read the contents. Whether sensitive data is exposed depends on what developers put into the token.

none Algorithm#

This should rarely happen nowadays. The root cause is allowing none as the signing algorithm. If alg=none, an attacker can base64url-encode the header and payload, join them with ., and bypass signature verification.

Signature Not Verified#

Before fusionauth-jwt 1.3.0, the component could still pass validation even if the signature part of the JWT was removed.

Improper Error Handling#

In CVE-2019-7644, if you send an incorrect signature, the server tells you the signature is wrong and even returns a correct signature (very “helpful”…).

Key Cracking#

You might be looking for:

Algorithm Confusion#

In CVE-2016-10555 (jwt-simple < 0.3.0), if you change the header algorithm from RS256 to HS256, the library switches to symmetric crypto and uses the RS256 public key as the HMAC secret.

Forged Keys (jwk)#

In CVE-2018-0114, the jwk field in the header can be abused to supply a forged public key (generated by the attacker). The attacker signs with the corresponding private key, and the server verifies using the attacker-provided public key — bypassing authentication.

Hardcoded Secrets#

Especially in open source projects: if public/private keys are hardcoded and developers ship default keys, the authentication mechanism is effectively broken.

Header Injection Risks (kid)#

A common risk is the kid (Key ID) field in the JWT header:

  • kid is often used to locate a key file on the filesystem. If not filtered, it can lead to path traversal, allowing the attacker to select arbitrary files as the verification key.
  • If kid is used to query keys from a database, SQL injection may allow bypassing JWT validation.

If kid is vulnerable to SQL injection, an attacker can return arbitrary values:

SQLi in “kid”: “aaaaaaa' UNION SELECT 'key';--”
// Use the string “key” to validate the token

In this example, the application returns key, then uses it as the verification key.

  • If kid is concatenated into a command for reading files, it can also lead to command injection (just like other web command-injection cases).

Recent Cases#

A quick Google search shows many JWT-related issues, for example:

  • Hardcoded keys: CVE-2023-5074 and CVE-2023-33236 allow forging arbitrary JWTs using hardcoded secrets.
  • Error-based issues: CVE-2023-40171 is similar to the “improper error handling” case above — sending malformed data results in a correctly signed JWT being returned.
  • Improper verification: CVE-2023-4696 is a “signature not verified” case — modify plaintext fields in the payload and bypass authorization checks.
  • Insufficient input filtering: CVE-2022-23529 allows RCE by overwriting toString via JWT (the author claims it’s not RCE, only local; screenshot below):

image-20231122014756560

IAST Detection Ideas#

I spent a day thinking about IAST detection for JWT issues but didn’t find a universal approach. I also didn’t see anything particularly helpful in DongTai’s code. Here are my notes/thoughts:

JWT Generation Points#

Hook: where the JWT is generated, usually after successful authentication.

Approach: insert checks for the signing algorithm (avoid none), ensure key strength and safe key management. Record generated JWTs for later auditing.

JWT Parsing & Verification Points#

Hook: where JWTs are parsed/validated, usually in API access control or session management.

Approach: insert validation checks to ensure signatures are verified, preventing forgery or tampering. Also validate standard claims like exp, sub, etc.

Error Handling & Logging#

Hook: where JWT-related exceptions are handled (signature validation failure, expiration, etc.).

Approach: enhance error handling and log all failed verification attempts, including token info and request context for security analysis.

Sensitive Data Exposure Checks#

Hook: where the payload is constructed.

Approach: ensure the payload does not contain sensitive data (passwords, personal identifiers, etc.). Insert checks to warn on potentially sensitive claims.

Conclusion#

This post is meant as a high-level overview, so I didn’t debug the underlying code in depth — I just wanted a set of reminders that I can quickly recall later. Many JWT issues are strongly tied to how carefully libraries/components are implemented. For users of JWT, what you store in the token, what transport you use, and what filtering/validation you perform on receipt all matter.

There’s still a lot to learn about JWT. Hopefully this gives readers a rough mental model of common JWT security pitfalls.

References:

A Brief Look at JWT Security
https://springkill.github.io/en/posts/浅谈jwt安全/
Author
SpringKill
Published at
2023-11-22
License
CC BY-NC-SA 4.0