Insecurely generated token - JWT - Python

Insecurely generated token - JWT - Python

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.

References