Skip to main content

Uncontrolled external site redirect - Host Header Injection

Need

Prevention of unauthorized external site redirects

Context

  • Usage of Python 3 for writing and executing Python code
  • Usage of Django for building web applications in Python

Description

Non compliant code

from django.http import HttpResponseRedirect
from django.shortcuts import render

def redirect(request):
next_page = request.META.get('HTTP_HOST')
if next_page:
return HttpResponseRedirect(next_page)

return render(request, 'base.html')

The above Python code represents a Django view function that redirects to a URL specified by the 'HTTP_HOST' header field in the HTTP request. This code is vulnerable to Host Header Injection attacks.

The main issue lies in this line:

next_page = request.META.get('HTTP_HOST')

Here, the code is getting the host header from the incoming HTTP request. A malicious user can manipulate the 'HTTP_HOST' header field in the HTTP request to any URL of their choice.

The subsequent line:

return HttpResponseRedirect(next_page)

Redirects the user to the URL specified in the 'HTTP_HOST' header field. This means that a malicious user can cause the application to redirect users to any external site, which can be used for phishing attacks, spreading malware, or other malicious activities. This is known as an uncontrolled external site redirect vulnerability, or a Host Header Injection vulnerability.

Steps

  • Validate the 'next_page' variable to ensure it only contains relative paths and not absolute URLs.
  • Do not use the 'HTTP_HOST' header to determine the redirect location as it can be manipulated by an attacker.
  • Use a whitelist of allowed hosts and only redirect to these hosts.
  • Consider using Django's built-in 'is_safe_url()' function to check if the URL is safe before redirecting.

Compliant code

from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.http import is_safe_url

ALLOWED_HOSTS = ['www.example.com', 'example.com'] # Add your allowed hosts here

def redirect(request):
next_page = request.GET.get('next', '/')
url_is_safe = is_safe_url(
url=next_page,
allowed_hosts=ALLOWED_HOSTS,
require_https=request.is_secure(),
)
if url_is_safe:
return HttpResponseRedirect(next_page)

return render(request, 'base.html')

The updated code now uses Django's built-in is_safe_url() function to validate the next_page variable. This function checks if the URL is safe for redirection, i.e., it is either a relative URL or an absolute URL that matches one of the allowed hosts.

The allowed_hosts parameter of is_safe_url() is set to a list of allowed hosts. This is a whitelist approach where only the hosts specified in this list are considered safe for redirection. You should replace the ALLOWED_HOSTS list with your own list of allowed hosts.

The require_https parameter of is_safe_url() is set to request.is_secure(). This means that if the request is made over HTTPS, then only HTTPS URLs are considered safe.

The next_page variable is now obtained from request.GET.get('next', '/') instead of request.META.get('HTTP_HOST'). This means that the next page is now specified as a GET parameter instead of being taken from the Host header. This prevents Host header injection attacks as the Host header is no longer used to determine the redirect location.

If the next_page URL is safe, the client is redirected to this URL. Otherwise, the client is redirected to the base page.

References