Insecure or unset HTTP headers - CORS - Python

Insecure or unset HTTP headers - CORS - Python

Need

Enforcement of secure and properly configured HTTP headers for Cross-Origin Resource Sharing (CORS)

Context

  • Usage of Python 3 for building and running Python applications
  • Usage of Django for building web applications

Description

Non compliant code

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

@csrf_exempt
def insecure_cors(request):
    response = HttpResponse("This is a response from a Django server.")
    response['Access-Control-Allow-Origin'] = '*'
    return response
        
        

The above Python code is a simple Django view function named insecure_cors. This function is designed to handle HTTP requests and send responses.

The vulnerability in this code lies in the line:

                response['Access-Control-Allow-Origin'] = '*'

            


This line sets the Access-Control-Allow-Origin HTTP header to '*', which means that any domain is allowed to access the resources on this server. This is a Cross-Origin Resource Sharing (CORS) policy, and the wildcard '*' is a security risk because it allows any website to make requests to this server and receive responses.

In a real-world scenario, this could allow malicious websites to make requests on behalf of the user to this server, potentially leading to data theft or other security breaches. Therefore, it's crucial to restrict the Access-Control-Allow-Origin header to trusted domains only.

The @csrf_exempt decorator is also a potential security risk as it disables Django's built-in Cross-Site Request Forgery protection for this view. This could make the application vulnerable to CSRF attacks where an attacker tricks a victim into performing an action that the victim did not intend to do.

Steps

  • Identify the specific domains that should be allowed to access the resources instead of using a wildcard '*'.
  • Replace the wildcard '*' in the 'Access-Control-Allow-Origin' header with the list of specific, trusted domains.
  • Ensure that the list of allowed domains is maintained and updated as necessary.
  • Consider using a dynamic method to set the 'Access-Control-Allow-Origin' header based on the 'Origin' header in the request, if the list of allowed domains is large or changes frequently.
  • Test the application to ensure that only the specified domains can access the resources.

Compliant code

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

ALLOWED_ORIGINS = ["http://trusteddomain1.com", "http://trusteddomain2.com"]  # Add your trusted domains here

@csrf_exempt
def secure_cors(request):
    origin = request.META.get('HTTP_ORIGIN')
    if origin in ALLOWED_ORIGINS:
        response = HttpResponse("This is a response from a Django server.")
        response['Access-Control-Allow-Origin'] = origin
    else:
        response = HttpResponse("Not allowed", status=403)
    return response
        
        

The updated code now includes a list of allowed origins ( ALLOWED_ORIGINS). This list should contain all the domains that are trusted and should be allowed to access the resources. The wildcard '*' has been replaced with this list.

In the secure_cors function, the origin of the request is retrieved from the HTTP_ORIGIN header. If this origin is in the list of allowed origins, the Access-Control-Allow-Origin header in the response is set to this origin. This means that only requests from the specified, trusted domains will be allowed.

If the origin of the request is not in the list of allowed origins, a response with a status code of 403 (Forbidden) is returned. This means that requests from untrusted domains will not be allowed.

This approach ensures that only specific, trusted domains can access the resources, thereby fixing the vulnerability. It also provides a way to easily update the list of allowed domains as necessary.

References