Decoding JWT Vulnerabilities: A Deep Dive into JWT Security Risks And Mitigation
JSON Web Tokens (JWTs) have become a popular method for securely transmitting information between parties as JSON objects. They are compact, self-contained, and can be easily verified, making them ideal for authentication and information exchange in web applications. They can theoretically contain any kind of data but are most commonly used to send information ("claims") about users as part of authentication, session handling, and access control mechanisms.
JWTs consist of three parts separated by dots: the header, the payload, and the signature. They follow the format: header.payload.signature
Header: Contains metadata about the type of token and the cryptographic algorithms used to secure it. It typically looks like this:
{
"alg": "HS256",
"typ": "JWT"
}
Payload: Contains the claims, which are statements about an entity (typically the user) and additional data. Claims are classified as reserved, public, or private, and they can include information such as user ID, roles, and expiration time. Example:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature: Created by encoding the header and payload using a specified algorithm (e.g., HS256, RS256) and a secret key known only to the server. It ensures the integrity of the token and verifies that it hasn't been tampered with.
Impact of JWT Attacks
The impact of JWT attacks is usually severe. If an attacker is able to create their own valid tokens with arbitrary values, they may be able to escalate their own privileges or impersonate other users, taking full control of their accounts.
How do vulnerabilities to JWT attacks arise?
- Weak Key Management: If the secret key used to sign JWTs is weak or is not securely managed, it becomes susceptible to brute-force attacks. Weak keys can be guessed or cracked using computational power.
- Algorithm Vulnerabilities: Choosing weak cryptographic algorithms or misconfiguring them can compromise the security of JWTs.
- Insufficient Validation: Failing to validate JWTs properly on the server side can lead to various vulnerabilities. Lack of input validation can result in injection attacks where malicious payloads are inserted into JWTs.
- Exposure of Sensitive Information: Including sensitive information in the JWT payload without proper encryption or protection can expose it to unauthorised access or tampering.
- Lack of Token Expiry and Revocation Mechanisms: Not implementing expiration or revocation mechanisms for JWTs allows them to remain valid indefinitely, increasing the window of opportunity for attackers to misuse them.
- Failure to Enforce Access Controls: Inadequate enforcement of access controls based on JWT claims can lead to privilege escalation or unauthorised access to sensitive resources.
- Third-Party Vulnerabilities: Dependencies on third-party libraries or services for JWT implementation may introduce vulnerabilities if those libraries have security flaws or if they are not kept up-to-date.
There are several types of attacks that can target JSON Web Tokens (JWTs), exploiting vulnerabilities in their implementation, transmission, or validation. Here are some common types of JWT attacks:
Accepting arbitrary signatures
JWT libraries typically provide one method for verifying tokens and another that just decodes them. For example, the Node.js library jsonwebtoken has verify() and decode().
Occasionally, developers confuse these two methods and only pass incoming tokens to the decode() method. This effectively means that the application doesn't verify the signature at all.
JWT Authentication Bypass via Unverified Signature using jwt_tool
Mitigation
The jsonwebtoken library is typically used in Node.js environments for working with JWTs. To verify a JWT using the jsonwebtoken library, you can use the verify method. Below is an example code snippet demonstrating how to verify a JWT using jsonwebtoken in Node.js:
In this code snippet:
- The jwt.verify method is used to verify the JWT signature. It takes three parameters: the JWT token to verify, the secret key used to sign the token, and a callback function that will be called with the decoded token or an error if verification fails.
- Replace the jwtToken variable with your actual JWT token and the secretKey variable with your actual secret key.
- If the JWT signature is valid, the callback function will be called with the decoded token, which contains the claims from the JWT payload. Otherwise, it will be called with an error object describing the verification failure.
Using the jsonwebtoken library's verify method ensures that the JWT signature is properly verified using the provided secret key, helping to prevent the acceptance of JWTs with arbitrary signatures.
Accepting tokens with no signature
Among other things, the JWT header contains an alg parameter. This tells the server which algorithm was used to sign the token and, therefore, which algorithm it needs to use when verifying the signature.
JWTs can be signed using a range of different algorithms, but can also be left unsigned. In this case, the alg parameter is set to none, which indicates a so-called "unsecured JWT".
Performing alg:none attack with sub:administrator claim using jwt_tool
Mitigation
To mitigate the "alg:none" attack in JSON Web Tokens (JWTs), you need to ensure that your application rejects JWTs with the "none" algorithm and only accepts tokens with secure signing algorithms. Here's a code snippet demonstrating how to implement this mitigation using the jsonwebtoken library in Node.js:
In this code snippet:
- The jwt.verify method is used to verify the JWT signature. However, an additional option { algorithms: [...] } is provided to specify the list of acceptable algorithms. In this case, only HMAC algorithms (HS256, HS384, HS512) are accepted, and the none algorithm is explicitly excluded.
- Replace the jwtToken variable with your actual JWT token and the secretKey variable with your actual secret key.
- If the JWT token uses the "none" algorithm, the verification will fail, and the callback function will be called with an error object describing the verification failure. You should handle this verification failure appropriately (e.g., logging, rejecting the token).
- If the JWT signature is valid and uses one of the accepted algorithms, the callback function will be called with the decoded token, which contains the claims from the JWT payload. You can then proceed with using the decoded token in your application.
Brute-forcing secret keys
Some signing algorithms, such as HS256 (HMAC + SHA-256), use an arbitrary, standalone string as the secret key. Just like a password, it's crucial that this secret can't be easily guessed or brute-forced by an attacker. Otherwise, they may be able to create JWTs with any header and payload values they like, and then use the key to re-sign the token with a valid signature.
Example:
Performing JWT secret key brute-forcing with jwt_tool
Mitigation
In this snippet, crypto.randomBytes(32).toString('hex') is used to generate a 256-bit (32-byte) cryptographically secure random string, which is then converted to a hexadecimal format. This ensures that the secret key used for JWT signing is strong and unpredictable.
Remember to securely store this secret key and avoid hardcoding it in your codebase or exposing it publicly.
Injecting self-signed JWTs via the jwk parameter
The JSON Web Signature (JWS) specification describes an optional jwk header parameter, which servers can use to embed their public key directly within the token itself in JWK format. Ideally, servers should only use a limited whitelist of public keys to verify JWT signatures. However, misconfigured servers sometimes use any key that's embedded in the jwk parameter.
JWT Authentication Bypass via jwk Header Injection using jwt_tool
How to prevent JWT attacks
Preventing JWT attacks requires a combination of secure implementation practices, adherence to cryptographic standards, and robust validation mechanisms. Here are some strategies to help mitigate JWT vulnerabilities:
- Use Strong Encryption and Signing Algorithms: Choose strong cryptographic algorithms for signing and encrypting JWTs, such as HMAC with SHA-256 or RSA with at least 2048-bit key length. Avoid using weak or deprecated algorithms like MD5 or SHA-1.
- Keep Secret Keys Secure: Safeguard the secret keys used for JWT signing and encryption. Store keys securely, restrict access to authorized personnel only, and avoid hardcoding keys in source code or configuration files.
- Validate JWT Signatures: Always validate the signature of incoming JWTs to ensure their integrity and authenticity. Verify the signature using the appropriate cryptographic algorithm and secret key before trusting the contents of the token.
- Implement Token Expiry: Set reasonable expiration times for JWTs to limit their lifespan and reduce the risk of unauthorized access due to token theft or misuse. Invalidate expired tokens promptly and require clients to obtain new tokens when necessary.
- Use Short-Lived Tokens: Consider using short-lived JWTs with a limited validity period (e.g., minutes or hours) to minimize the window of opportunity for attackers to exploit stolen tokens.
- Implement Token Revocation: Implement mechanisms for token revocation or invalidation in case of compromise or logout events. Maintain a blacklist or revocation list of revoked tokens to prevent their reuse.
- Include Audience (aud) Claim: Specify the intended audience of the JWT (e.g., the API or service) using the "aud" claim. Verify that the audience claim matches the expected value during token validation to prevent token misuse.
- Validate Token Claims: Validate all relevant claims within the JWT payload, such as the issuer (iss), subject (sub), and any custom claims, to ensure they meet the expected criteria. Reject tokens with invalid or unexpected claims to prevent token manipulation.
- Protect Token Transmission: Transmit JWTs securely over HTTPS to prevent eavesdropping and man-in-the-middle attacks. Avoid sending tokens over unencrypted channels or including them in URLs, query parameters, or HTTP headers visible to external parties.
- Enforce Access Controls: Enforce fine-grained access controls based on JWT claims to restrict users' access to resources and functionalities within the application. Implement role-based access control (RBAC) or attribute-based access control (ABAC) mechanisms as needed.
- Implement Rate Limiting: Enforce rate limiting on token issuance and authentication endpoints to prevent brute force attacks and token abuse.
- Regular Security Audits and Updates: Conduct regular security audits of JWT implementation, including code reviews, penetration testing, and vulnerability assessments. Stay informed about security updates and patches for JWT libraries and dependencies.
By implementing these preventive measures, developers can enhance the security of their JWT-based authentication systems and reduce the risk of JWT-related attacks.
Further Learning
https://www.youtube.com/watch?v=zWVRHK3ykfo
https://portswigger.net/web-security/jwt
https://dazzyddos.github.io/posts/JWT_MindMap/