Insecurely generated token - OTP - Python

Insecurely generated token - OTP - Python

Need

Secure generation and transmission of OTP tokens

Context

  • Usage of Python 3.0+ for developing Python applications
  • Usage of Django for building web applications

Description

Non compliant code

        from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def generate_otp(request):
    if request.method == 'POST':
        phone = request.POST.get('phone')
        otp = generate_random_otp() # This function generates a random OTP
        send_otp(phone, otp) # This function sends the OTP to the provided phone number
        return JsonResponse({'otp': otp})
    else:
        return JsonResponse({'error': 'Invalid request'})

def generate_random_otp():
    import random
    return random.randint(100000, 999999)

def send_otp(phone, otp):
    # Here goes the code to send the OTP to the phone number
    pass
        
        

The above code represents a Django view that generates an OTP (One Time Password) and sends it to a phone number provided in a POST request. The OTP is generated on the client-side and sent to the server. This is done in the generate_otp function.

The generate_random_otp function generates a random 6-digit OTP. This OTP is then sent to the phone number using the send_otp function.

The major vulnerability in this code is that the generated OTP is also returned as a response to the client in the JSON response {'otp': otp}. This means that the OTP is exposed in the communication between the client and the server. An attacker can intercept this communication and gain access to the OTP without needing access to the phone number. This allows the attacker to continue the application flow as if they had received the OTP on the phone number.

This is a significant security risk as it bypasses the two-factor authentication that the OTP is supposed to provide. The attacker does not need to have access to the second factor (the phone number) to authenticate themselves. This vulnerability could lead to unauthorized access to the application.

Steps

  • Remove the OTP from the JsonResponse in the generate_otp function. The OTP should not be sent back to the client.
  • Generate the OTP server-side and store it in a secure way, such as in a secure session or database, associated with the user's session or account.
  • When the user submits the OTP, compare it with the stored OTP on the server side. If they match, the user is authenticated.
  • Ensure that the communication between the client and the server is secure, for example by using HTTPS, to prevent interception of the OTP.
  • Consider implementing a time limit for the OTP, after which it becomes invalid. This can help to mitigate the risk of an attacker using a stolen OTP.
  • Consider using a more secure method of generating the OTP, such as a cryptographically secure random number generator.

Compliant code

        from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.sessions.models import Session
import random
import time

@csrf_exempt
def generate_otp(request):
    if request.method == 'POST':
        phone = request.POST.get('phone')
        otp = generate_random_otp() # This function generates a random OTP
        store_otp_in_session(request, otp) # This function stores the OTP in the user's session
        send_otp(phone, otp) # This function sends the OTP to the provided phone number
        return JsonResponse({'success': 'OTP sent'})
    else:
        return JsonResponse({'error': 'Invalid request'})

def generate_random_otp():
    return random.SystemRandom().randint(100000, 999999)

def store_otp_in_session(request, otp):
    request.session['otp'] = otp
    request.session['otp_created_at'] = time.time()

def send_otp(phone, otp):
    # Here goes the code to send the OTP to the phone number
    pass

@csrf_exempt
def verify_otp(request):
    if request.method == 'POST':
        otp = request.POST.get('otp')
        if 'otp' in request.session and 'otp_created_at' in request.session:
            if time.time() - request.session['otp_created_at'] < 300: # OTP is valid for 5 minutes
                if otp == request.session['otp']:
                    return JsonResponse({'success': 'OTP verified'})
                else:
                    return JsonResponse({'error': 'Invalid OTP'})
            else:
                return JsonResponse({'error': 'OTP expired'})
        else:
            return JsonResponse({'error': 'No OTP found'})
    else:
        return JsonResponse({'error': 'Invalid request'})
        
        

The OTP is now generated server-side using a cryptographically secure random number generator, which provides a higher level of security than the previous method. The OTP is stored in the user's session along with the time it was created. This allows us to implement a time limit for the OTP, after which it becomes invalid. The OTP is sent to the user's phone number, but it is not included in the response to the client. This prevents an attacker from intercepting the OTP by intercepting the response.

When the user submits the OTP, it is compared with the OTP stored in their session. If they match and the OTP has not expired, the user is authenticated. If they do not match or the OTP has expired, an error message is returned.

This code should be used in conjunction with secure communication between the client and the server, such as HTTPS, to prevent interception of the OTP during transmission.

References