Skip to main content

Lack of data validation - Token

Need

Secure data validation for JWT access tokens

Context

  • Usage of Python 3.0 for building applications and scripts
  • Usage of JWT for secure authentication and authorization
  • Usage of Django for building web applications
  • Usage of rest_framework for building RESTful APIs in a Django project

Description

Non compliant code

import jwt
from django.http import JsonResponse
from rest_framework.decorators import api_view

@api_view(['POST'])
def login(request):
token = request.META.get('HTTP_AUTHORIZATION')
try:
payload = jwt.decode(token, options={"verify_signature": False})
user = User.objects.get(username=payload['username'])
if user.password == payload['password']:
return JsonResponse({'message': 'Login successful'})
else:
return JsonResponse({'message': 'Invalid credentials'}, status=400)
except jwt.ExpiredSignatureError:
return JsonResponse({'message': 'Token expired'}, status=401)
except jwt.InvalidTokenError:
return JsonResponse({'message': 'Invalid token'}, status=401)

This code is vulnerable due to the lack of validation of the JWT token signature. The jwt.decode() function is used with the "verify_signature": False option, which tells the function to not verify the signature of the token. This allows any token to be accepted, regardless of whether the signature is valid or not.

In the code, the token is extracted from the 'Authorization' header of the request. This token is then decoded without verifying the signature. If the decoding is successful, the payload of the token is used to retrieve the user from the database. If the user exists and the password in the token payload matches the user's password, a successful login message is returned. If the password does not match, an 'Invalid credentials' message is returned.

The problem with this code is that because the signature is not being verified, an attacker could modify the token, change the 'username' and 'password' fields in the payload, and then use this modified token to authenticate as any user. This is a serious security vulnerability as it allows for unauthorized access to the system.

Moreover, even removing the signature from the token works due to the "verify_signature": False option, making the system more vulnerable.

To fix this vulnerability, the JWT token signature should be verified when the token is decoded. The secret key used to sign the token should be stored securely on the server and used to verify the signature.

Steps

  • First, you need to ensure that the JWT token is verified. This can be done by setting the 'verify_signature' option to True when decoding the JWT token.
  • Second, you should use a secret key to decode the JWT token. This secret key should be stored securely and should not be exposed. The secret key ensures that the token has not been tampered with.
  • Third, you should handle the case where the token is invalid or has been tampered with. This can be done by catching the jwt.InvalidTokenError exception and returning an appropriate response.
  • Lastly, it's not safe to store and compare passwords in plain text. You should hash the password and compare the hashed values instead.

Compliant code

import jwt
from django.http import JsonResponse
from django.contrib.auth.hashers import check_password
from rest_framework.decorators import api_view

SECRET_KEY = 'YOUR_SECRET_KEY' # Replace with your secret key

@api_view(['POST'])
def login(request):
token = request.META.get('HTTP_AUTHORIZATION')
try:
payload = jwt.decode(token, key=SECRET_KEY, algorithms=["HS256"])
user = User.objects.get(username=payload['username'])
if check_password(payload['password'], user.password):
return JsonResponse({'message': 'Login successful'})
else:
return JsonResponse({'message': 'Invalid credentials'}, status=400)
except jwt.ExpiredSignatureError:
return JsonResponse({'message': 'Token expired'}, status=401)
except jwt.InvalidTokenError:
return JsonResponse({'message': 'Invalid token'}, status=401)

The updated code now includes a secret key to decode the JWT token. This secret key is used to verify the signature of the JWT token. If the token has been tampered with, the jwt.decode() function will raise a jwt.InvalidTokenError exception. This exception is caught and an appropriate response is returned.

The jwt.decode() function now also specifies the algorithm used to sign the token. This is important as it prevents potential attacks where an attacker might try to change the algorithm used to a weaker one.

The password comparison has also been updated. Instead of comparing the plain text passwords, the check_password() function from Django's auth module is used. This function takes the plain text password and the hashed password, hashes the plain text password and then compares the hashed values. This is a safer way to compare passwords as it doesn't involve storing or comparing passwords in plain text.

References