Asymmetric denial of service - Content length - Python

Asymmetric denial of service - Content length - Python

Need

Prevention of Asymmetric Denial of Service attacks targeting the Content-Length field

Context

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

Description

Non compliant code

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

@csrf_exempt
def receive_data(request):
    if request.method == 'POST':
        data = request.body
        return HttpResponse('Data received')
    else:
        return HttpResponse('Invalid method')
        
        

In the code above, we have a Django view function receive_data() which is designed to receive POST requests. This function reads the request body using request.body without checking the Content-Length of the request.

This means that if an attacker sends a POST request with a very high Content-Length and a small payload, the server will still try to read the request body until it has received the amount of data specified in the Content-Length. This can result in the server maintaining an open connection for a long time, waiting for the rest of the data to arrive. This is a potential Denial of Service (DoS) attack, as it can consume server resources and potentially block other legitimate requests.

Steps

  • Implement a middleware to limit the size of the request body. This middleware should check the 'Content-Length' header of the incoming request and reject it if it exceeds a certain limit.
  • Use Django's built-in data upload handlers to limit the size of the uploaded data. This can be done by setting the 'DATA_UPLOAD_MAX_MEMORY_SIZE' setting in your Django settings file.
  • Consider using a reverse proxy or a load balancer that can handle such attacks before they reach your application. These tools can limit the size of the request body and can also limit the rate of incoming requests.
  • Implement a timeout for incoming requests. If a request does not complete within a certain time limit, it should be terminated. This can prevent an attacker from keeping a connection open indefinitely by sending data slowly.

Compliant code

        from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings

class ContentLengthMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if 'CONTENT_LENGTH' in request.META:
            if int(request.META['CONTENT_LENGTH']) > settings.DATA_UPLOAD_MAX_MEMORY_SIZE:
                return HttpResponseBadRequest('Request body too large')
        return self.get_response(request)

@csrf_exempt
def receive_data(request):
    if request.method == 'POST':
        data = request.body
        return HttpResponse('Data received')
    else:
        return HttpResponse('Invalid method')
        
        

The above code introduces a middleware, ContentLengthMiddleware, which checks the 'Content-Length' header of every incoming request. If the size of the request body, as specified by the 'Content-Length' header, exceeds the limit defined in the Django settings file ( DATA_UPLOAD_MAX_MEMORY_SIZE), the middleware returns a 400 Bad Request response. This effectively prevents an attacker from overwhelming the server with a large amount of data.

The DATA_UPLOAD_MAX_MEMORY_SIZE setting in the Django settings file is used to limit the size of the uploaded data. In this case, it is set to 2.5MB. You can adjust this value according to your needs.

Please note that this is just one part of the solution. To fully protect your application from Asymmetric Denial of Service attacks, you should also consider using a reverse proxy or a load balancer that can limit the size and rate of incoming requests, and implementing a timeout for incoming requests.

References