Server-side request forgery (SSRF) - Python

Server-side request forgery (SSRF) - Python

Need

Prevention of unauthorized server-side requests to arbitrary domains

Context

  • Usage of Python 3 for general-purpose programming and scripting
  • Usage of Django for building web applications in Python
  • Usage of requests for making HTTP requests in Node.js

Description

Non compliant code

        from django.http import HttpResponse
import requests

def get_external_data(request):
    url = request.GET.get('url')
    response = requests.get(url)
    return HttpResponse(response.content)
        
        

In the above code, the get_external_data function is taking a URL from the request parameters and then using the requests.get() function to make a GET request to that URL. The response from this request is then returned to the user.

This is a classic case of Server-Side Request Forgery (SSRF) vulnerability. The application is taking an arbitrary URL from the user and making a request to that URL. This can be exploited by an attacker by providing a URL that points to an internal network resource that the server has access to but should not be exposed to the user. The server will fetch the content of that internal resource and send it back to the attacker.

For example, an attacker could provide a URL like http://localhost:8000/admin to access the Django admin panel or http://169.254.169.254/latest/meta-data/ to access the metadata of an AWS EC2 instance, if the server is hosted on AWS.

This is a serious vulnerability as it allows an attacker to bypass firewalls and access internal network resources, which could lead to data exposure, data loss, or even remote code execution if the internal resource has a vulnerability that can be exploited through its HTTP interface.

Steps

  • Validate the URL input to ensure it's from a trusted or safe source. This can be done by creating a whitelist of trusted URLs and checking if the input URL is in this list.
  • Avoid passing user input directly into the request function. Instead, use server-side logic to determine the appropriate URL.
  • Implement a server-side proxy that can make the external requests on behalf of the application. This way, the server can control and limit where requests are sent.
  • Use a library or a service that provides safe URL resolution and validation.
  • Consider using a Content Security Policy (CSP) to limit the domains that the application can interact with.

Compliant code

        from django.http import HttpResponse
import requests

# Define a list of trusted URLs
TRUSTED_URLS = ['http://trustedurl1.com', 'http://trustedurl2.com']

def get_external_data(request):
    url = request.GET.get('url')

    # Validate the URL
    if url not in TRUSTED_URLS:
        return HttpResponse('Invalid URL')

    response = requests.get(url)
    return HttpResponse(response.content)
        
        

The updated code includes a list of trusted URLs ( TRUSTED_URLS). When a request is made to get external data, the URL provided in the request is checked against this list. If the URL is not in the list of trusted URLs, the function returns an HTTP response with the message 'Invalid URL'.

This way, we ensure that the server only makes requests to URLs that are known to be safe. This prevents Server-Side Request Forgery (SSRF) attacks, where an attacker could induce the server into making requests to an arbitrary domain.

This is a simple and effective way to prevent SSRF attacks, but it's not the only one. Other measures could include using a server-side proxy to make external requests, using a library or service that provides safe URL resolution and validation, or implementing a Content Security Policy (CSP) to limit the domains that the application can interact with.

References