JSON Web Tokens (JWTs) are a widely used method for securely exchanging data in JSON format. Due to their ability to be digitally signed and verified, they are commonly used for authorization and authentication. However, their security depends entirely on proper implementation—when misconfigured, JWTs can introduce serious vulnerabilities.
This guide explores common JWT attacks and security flaws, providing a technical deep dive into how these weaknesses can be exploited and how to mitigate them.
The Structure of a JSON Web Token (JWT)
A JSON Web Token (JWT) is composed of three parts: a header, payload, and signature, all encoded using Base64URLand separated by dots. The format follows this structure:
HEADER.PAYLOAD.SIGNATURE
Here is an example of a real JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJuYW1lIjoiSm9obiBEb2UiLCJ1c2VyX25hbWUiOiJqb2huLmRvZSIsImlzX2FkbWluIjpmYWxzZX0.
fSppjHFaqlNcpK1Q8VudRD84YIuhqFfA67XkLam0_aY
Breaking Down the JWT Header
The header contains metadata that defines the token’s properties, including:
- The algorithm (alg) used for signing the token
- The token type (typ), which is typically set to “JWT”
Before encoding, the header looks like this:
{
"alg": "HS256",
"typ": "JWT"
}
This information tells the recipient how to verify the token, ensuring it has not been tampered with. In this case, HS256 (HMAC with SHA-256) is used as the signing algorithm.
The payload of a JSON Web Token (JWT) contains claims, which store information about the user or entity the application is verifying. These claims help determine the user’s identity and permissions.
For example, the following payload includes basic user details:
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false
}
Generating the JWT Signature
To ensure the token’s authenticity, a signature is created by:
- Encoding the header and payload using Base64URL.
- Concatenating them with a dot (.) separator.
- Signing the resulting string using either:
- A secret key (for symmetric encryption).
- A private key (for asymmetric encryption), depending on the algorithm specified in the header.
Since the header in this example specifies HS256 (HMAC with SHA-256), a symmetric algorithm, the signature is generated as follows:
HMACSHA256(
base64UrlEncode(header) + “.” +
base64UrlEncode(payload),
secret)
This operation produces the following signature:
fSppjHFaqlNcpK1Q8VudRD84YIuhqFfA67XkLam0_aY
The final JWT is formed by appending this signature to the Base64URL-encoded header and payload, separated by dots. This ensures the token’s integrity and allows the recipient to verify that it has not been altered.
Common Vulnerabilities in JSON Web Tokens
JSON Web Tokens (JWTs) were designed to be adaptable, allowing them to be used in a wide range of applications. However, this flexibility also introduces risks when they are not implemented correctly. Below are some common vulnerabilities that can arise when working with JWTs.
Failing to Verify the Signature
Many JWT libraries provide two separate functions:
- decode(): Converts the token from base64url encoding but does not verify the signature.
- verify(): Decodes the token and ensures the signature is valid.
If developers mistakenly use the decode() function without also calling verify(), the signature is never checked, allowing any token with a valid format to be accepted. In some cases, signature verification may also be disabled during testing and accidentally left that way in production. Such errors can lead to security issues like unauthorized account access or privilege escalation.
For example, consider this valid JWT:
{
"alg": "HS256",
"typ": "JWT"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false
}
If an application does not verify the signature, an attacker could modify the payload and submit a new token with an arbitrary signature, gaining elevated privileges:
{
"alg": "HS256",
"typ": "JWT"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": true
}
Without signature verification, the server would accept this manipulated token, granting administrative access to an unauthorized user. This highlights why properly validating JWT signatures is essential for security.
Allowing the None Algorithm
The JWT standard supports multiple algorithms for generating signatures, including:
- RSA
- HMAC
- Elliptic Curve
- None
The None algorithm indicates that the token is unsigned. If an application allows this algorithm, an attacker can modify an existing JWT by changing the algorithm to None and removing the signature. This effectively bypasses signature verification, allowing unauthorized access.
Consider the following expected JWT with a signature:
{
"alg": "HS256",
"typ": "JWT"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false
}.SIGNATURE
Once encoded and signed, the token appears as:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJuYW1lIjoiSm9obiBEb2UiLCJ1c2VyX25hbWUiOiJqb2huLmRvZSIsImlzX2FkbWluIjpmYWxzZX0.
fSppjHFaqlNcpK1Q8VudRD84YIuhqFfA67XkLam0_aY
If an application does not properly restrict the use of the None algorithm, an attacker can modify the header to specify “alg”: “none”, remove the signature, and submit a token that the system will still accept as valid. This vulnerability underscores the importance of explicitly disallowing the None algorithm in JWT implementations.
If an application allows None as the algorithm in a JWT, an attacker can modify a valid token by replacing the original algorithm with None and completely removing the signature. This effectively disables signature verification, allowing the attacker to alter the token’s payload without being detected.
For example, an attacker could modify a token like this:
{
"alg": "None",
"typ": "JWT"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": true
}.
Despite being unsigned, this altered token may still be accepted by the application:
eyJhbGciOiJOb25lIiwidHlwIjoiSldUIn0.
eyJuYW1lIjoiSm9obiBEb2UiLCJ1c2VyX25hbWUiOiJqb2huLmRvZSIsImlzX2FkbWluIjp0cnVlfQ.
To prevent this vulnerability, applications should explicitly reject tokens that use the None algorithm, regardless of case variations such as none, NONE, nOnE, or any other similar format in the algorithm field. Proper security measures should enforce the use of strong cryptographic algorithms and ensure that all JWTs are properly signed and verified.
Algorithm Confusion in JWT
JSON Web Tokens (JWTs) support both symmetric and asymmetric encryption algorithms, with different key usage requirements depending on the encryption type.
Algorithm Type | Signing Key | Verification Key |
Asymmetric (RSA) | Private key | Public key |
Symmetric (HMAC) | Shared secret | Shared secret |
With asymmetric encryption, an application signs tokens using a private key while making the public key available for verification. This ensures that anyone can validate the token’s authenticity without being able to forge new ones.
The algorithm confusion vulnerability occurs when an application fails to verify that the algorithm specified in an incoming JWT matches the one it expects. If an attacker modifies a token to switch from asymmetric (RSA) to symmetric (HMAC) encryption and the application does not enforce the expected algorithm, the system may mistakenly verify the token using the public key as a shared secret, allowing attackers to forge valid tokens. Proper security measures should ensure strict validation of both the algorithm and key type before processing JWTs.
Many JWT libraries provide a method for verifying the token’s signature. Depending on the encryption type, the method works as follows:
- verify(token, secret) is used when the token is signed with HMAC
- verify(token, publicKey) is used when the token is signed with RSA or a similar algorithm
However, in some implementations, this verification method does not automatically check whether the token was signed using the algorithm the application expects. Because of this, when using HMAC, the function treats the second argument as a shared secret, and when using RSA, it treats it as a public key.
If the application’s public key is accessible, an attacker can exploit this flaw by:
- Changing the algorithm in the token to HMAC
- Modifying the payload to achieve their intended outcome
- Signing the manipulated token using the public key found in the application
- Sending the altered JWT back to the application
Since the application expects RSA, but the attacker specifies HMAC, the verification method incorrectly treats the public key as a shared secret and performs verification as if the token were symmetrically signed. This allows the attacker to generate a valid signature using the public key, effectively forging a legitimate JWT without needing the private key.
To prevent this vulnerability, applications must explicitly verify that the algorithm specified in an incoming JWT matches the expected algorithm before passing it to the verification function. This ensures that attackers cannot exploit algorithm confusion to bypass authentication or escalate privileges.
The Risk of Weak Secrets in Symmetric Encryption
In symmetric encryption, the security of a cryptographic signature depends entirely on the strength of the secret key. If an application uses a weak or easily guessable secret, an attacker can perform a brute-force attack, systematically testing different secret values until they find one that produces a matching signature.
Once the attacker discovers the secret, they can generate their own valid signatures, allowing them to forge malicious tokens that the system will accept as legitimate. To prevent this type of attack, applications should always use strong, randomly generated secrets when implementing symmetric encryption.
Attacks Targeting JSON Web Tokens
Exploiting the Key ID (kid) Parameter
The JWT header includes a Key ID (kid) parameter, which is often used to locate the appropriate cryptographic key from a database or file system. When a token is received, the application retrieves the key specified in the kid parameter and uses it to verify the signature. However, if this parameter is vulnerable to injection, attackers can manipulate it to bypass signature verification or launch more severe attacks, including remote code execution (RCE), SQL injection (SQLi), or local file inclusion (LFI).
Consider the following valid JWT:
{
"alg": "HS256",
"typ": "JWT",
"kid": "key1"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false
}
If the kid parameter is not properly validated, an attacker could modify it to execute system commands. For example, injecting a command like this:
{
"alg": "HS256",
"typ": "JWT",
"kid": "key1|/usr/bin/uname"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false
}
If the application processes the kid parameter in an unsafe way, this could trigger remote code execution, potentially compromising the system. To prevent such attacks, applications must strictly validate and sanitize the kid parameter, ensuring it is treated only as a lookup key and not executed as part of a system command or database query.
Combining Key ID (kid) Injection with Directory Traversal to Bypass Signature Verification
When an application retrieves cryptographic keys from the filesystem using the kid parameter, it may be vulnerable to directory traversal attacks. An attacker can manipulate this parameter to force the application to use a file with a known or predictable value as the key for signature verification. If the attacker can identify a static file within the application that contains a predictable value, they can sign a malicious token using that file as the key.
For example, an attacker could modify the JWT to reference /dev/null, a file that always contains an empty value:
{
"alg": "HS256",
"typ": "JWT",
"kid": "../../../../../../dev/null"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": true
}
If the application processes the kid parameter without validation and allows traversal to /dev/null, it will treat an empty string as the signing key. This enables the attacker to create a forged JWT and sign it with an empty key, bypassing authentication entirely.
The same technique can be applied using any static file with a predictable value, such as CSS files or other known assets. To prevent this attack, applications must validate and restrict the kid parameter to allow only predefined key sources and block directory traversal attempts.
Exploiting Key ID (kid) Injection with SQL Injection to Bypass Signature Verification
When an application retrieves cryptographic keys from a database using the kid parameter, it may be vulnerable to SQL injection. If an attacker successfully injects a malicious SQL statement, they can manipulate the key value returned by the database and use it to generate a valid signature for a forged JWT.
Consider an application that fetches the signing key using the following SQL query:
SELECT key FROM keys WHERE key='key1'
If the query does not properly sanitize input, an attacker can modify the kid parameter to inject a UNION SELECTstatement, forcing the application to return a chosen key value:
{
"alg": "HS256",
"typ": "JWT",
"kid": "xxxx' UNION SELECT 'aaa"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": true
}
If the injection succeeds, the query executed by the application becomes:
SELECT key FROM keys WHERE key='xxxx' UNION SELECT 'aaa'
This forces the database to return aaa as the key value, allowing the attacker to create and sign a malicious JWT using aaa as the secret. Since the application treats this as a legitimate key, the forged token will pass verification.
To prevent this attack, applications should properly sanitize and validate the kid parameter before using it in database queries. Prepared statements and parameterized queries should always be used to prevent SQL injection vulnerabilities.
Exploiting the jku Header for JWT Attacks
The JWT header allows the use of the jku parameter, which specifies the JSON Web Key Set (JWKS) URL where the application can retrieve the public key used for signature verification. This mechanism allows the application to dynamically fetch the appropriate JSON Web Key (JWK), which contains the public key in JSON format.
For example, consider the following JWT, which includes a jku parameter pointing to an external key file:
{
"alg": "RS256",
"typ": "JWT",
"jku": "https://example.com/key.json"
}.
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false
}
The application retrieves the public key from the specified key.json file, which may contain a JSON Web Key (JWK) structured like this:
{
"kty": "RSA",
"n": "-4KIwb83vQMH0YrzE44HppWvyNYmyuznuZPKWFt3e0xmdi-WcgiQZ1TC...RMxYC9lr4ZDp-M0",
"e": "AQAB"
}
Once retrieved, the application verifies the JWT signature using the public key provided in the jku URL. If this parameter is not properly validated, an attacker could manipulate it to point to a malicious JWKS URL, allowing them to control which public key the application uses for verification. This can lead to unauthorized access if the attacker is able to generate a key pair, sign a token with their private key, and trick the application into validating it with their forged public key.
The application verifies the signature using the JSON Web Key retrieved based on the jku header value:
In a standard JWT verification process, the application retrieves the public key based on the jku header value. The process involves:
- The user sends a request containing a JWT.
- The web application extracts the jku parameter from the token header.
- The application fetches the JSON Web Key (JWK) from the URL specified in the jku parameter.
- The retrieved JWK is parsed.
- The application verifies the JWT signature using the fetched key.
- If the verification is successful, the application processes the request and responds accordingly.
Manipulating the jku Parameter for an Attack
An attacker can exploit this mechanism by modifying the jku value to point to a malicious JWK endpoint instead of the legitimate one. If the application does not properly validate the jku source, it will fetch the attacker-controlled JWK and use it for verification.
This allows the attacker to:
- Generate a forged JWT with their own private key.
- Modify the jku parameter in the token header to point to their own JWK file.
- Send the malicious JWT to the application.
- Trick the application into verifying the signature using the attacker’s public key.
Since the application mistakenly trusts the attacker’s JWK, it considers the malicious token valid, granting unauthorized access or elevated privileges. Proper validation and restrictions on the jku parameter are necessary to prevent such exploits.
To mitigate jku-based attacks, applications often implement URL filtering to restrict which domains can be used to fetch JSON Web Keys (JWKs). However, attackers can exploit various techniques to bypass these restrictions, including:
- Using misleading URLs: Some applications only check if the URL starts with a trusted domain, allowing attackers to use tricks like https://trusted@attacker.com/key.json, which may be misinterpreted as a legitimate request.
- Exploiting URL fragments: Injecting the # character can manipulate how URLs are parsed, leading the application to interpret the domain incorrectly.
- Abusing DNS hierarchy: Attackers may craft subdomains such as trusted.attacker.com, which might pass loose domain checks.
- Chaining with an open redirect: Redirecting from a trusted URL to a malicious JWK source.
- Injecting headers: Manipulating HTTP headers to modify request behavior.
- Leveraging server-side request forgery (SSRF): Exploiting SSRF vulnerabilities to force the application to fetch keys from unauthorized sources.
Strengthening jku Security
To effectively prevent such attacks, applications must strictly whitelist trusted hosts and implement rigorous URL validation. Beyond URL filtering, it is essential to eliminate other vulnerabilities that attackers could chain together to bypass security measures. By enforcing strict domain restrictions and addressing related security flaws, applications can reduce the risk of signature forgery through jku parameter manipulation.
Summary
JSON Web Tokens play a crucial role in authentication, particularly in web applications that use single sign-on (SSO). To reduce security risks associated with JWTs, developers should adhere to best practices and rely on well-established JWT libraries instead of creating custom implementations.
To further protect applications, it is essential to identify and address potential vulnerabilities before attackers can exploit them. Implementing a comprehensive vulnerability scanning solution helps detect security weaknesses, including JWT-related risks. A modern dynamic application security testing (DAST) tool like Invicti can uncover JWT vulnerabilities along with a wide range of other security flaws, making it an essential component of a robust application security strategy.
Get the latest content on web security
in your inbox each week.