Insecurely generated token - JWT
Need
Secure token generation for user creation service
Context
- Usage of Python 3 for scripting and application development
- Usage of JWT for secure authentication and authorization
- Usage of Django for building web applications in Python
Description
Non compliant code
import jwt
from django.conf import settings
from django.contrib.auth.models import User
def create_token(user):
payload = {
'user_id': user.id,
'exp': datetime.utcnow() + timedelta(days=1)
}
token = jwt.encode(payload, 'weak_key', algorithm='HS256')
return token
def create_user(username, password):
user = User.objects.create_user(username=username, password=password)
token = create_token(user)
return token
In the code snippet above, we define two functions: create_token
and create_user
.
The create_user
function is responsible for creating a new user in the Django application. After creating the user, it calls the create_token
function to generate a JWT token for the newly created user.
The create_token
function generates a JWT token with a payload containing the user_id and an expiration date set to one day from the current date. The token is then encoded using the HS256 algorithm with a static key - 'weak_key'.
Here lies the vulnerability. The key used to sign the JWT token is a weak static key. This makes it easy for an attacker to guess or brute-force the key, and thus they can decode the token, modify its parameters such as the expiration date, and re-encode it.
With this modified token, an attacker could potentially perform unauthorized actions in the application, such as querying data or performing actions on behalf of other users. This is a serious security vulnerability as it can lead to data breaches and other security issues.
Steps
- Replace the weak key used for signing the JWT with a strong, securely generated key. This key should be long and complex enough to resist brute-force attacks.
- Store the key securely. It should not be hard-coded into the application, but instead stored in a secure and encrypted configuration file or environment variable.
- Implement key rotation. Regularly changing the key used to sign tokens can limit the impact if a key is compromised.
- Consider using a library or service that handles JWT creation and validation for you, as these often include additional security measures.
- Validate the 'exp' claim in the token to ensure it has not been tampered with. This can prevent attackers from extending the token's lifetime.
- Consider adding additional claims to the token, such as the issuer ('iss') and audience ('aud'), to further verify the token's authenticity.
Compliant code
import jwt
import os
from datetime import datetime, timedelta
from django.conf import settings
from django.contrib.auth.models import User
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
def generate_key():
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
return pem
def create_token(user, private_key):
payload = {
'user_id': user.id,
'exp': datetime.utcnow() + timedelta(days=1),
'iss': 'your_issuer',
'aud': 'your_audience',
}
token = jwt.encode(payload, private_key, algorithm='RS256')
return token
def create_user(username, password):
user = User.objects.create_user(username=username, password=password)
private_key = generate_key()
token = create_token(user, private_key)
return token
The updated code now generates a secure RSA private key for signing the JWT token. The RSA algorithm is more secure than the previously used HS256 algorithm and the key is generated dynamically for each token, which adds an extra layer of security.
The generate_key
function generates a 2048-bit RSA private key. This key size is currently recommended for most use cases and provides a good balance between security and performance.
The create_token
function now includes the 'iss' (issuer) and 'aud' (audience) claims in the token payload. These claims can be used to verify that the token was issued by the expected party and is being used by the expected audience.
The private key is passed to the create_token
function and used to sign the token. This ensures that the key is not hard-coded into the application and can be securely stored and managed separately.
The 'exp' (expiration) claim is still included in the token payload and will cause the token to be rejected if it is used after the specified date and time. This can prevent attackers from extending the token's lifetime by modifying this claim.
Finally, the create_user
function creates a new user and generates a token for that user using the secure private key.