Skip to main content

Uncontrolled external site redirect - Host Header Injection

Need

Prevention of unauthorized external site redirects

Context

  • Usage of TypeScript for statically typed JavaScript development
  • Usage of Express for building web applications and APIs

Description

Non compliant code

import express from 'express';

const app = express();

app.get('/', (req, res) => {
const { host } = req.headers;
const redirectUrl = `https://${host}/redirected`;
res.redirect(301, redirectUrl);
});

app.get('/redirected', (req, res) => {
res.send('You have been redirected!');
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

The vulnerability in this code is an uncontrolled external site redirect, also known as Host Header Injection. This vulnerability occurs when the server processes requests with the Host header set and redirects clients to any destination specified in the Host header.

In the vulnerable code, the server receives a GET request to the root path ("/") and extracts the value of the Host header from the request headers. It then constructs a redirect URL using the extracted host value and redirects the client to that URL using the res.redirect function. The redirect is performed with a 301 status code, indicating a permanent redirect.

The problem with this code is that it blindly trusts the value of the Host header provided by the client. An attacker can manipulate the Host header to redirect the client to an arbitrary external site. This can lead to various security issues.

The impact of this vulnerability includes the ability for an attacker to enumerate the internal network by performing trial and error redirects. They can try different internal IP addresses or hostnames to see if they are valid and accessible. Additionally, this vulnerability can be used to perform a subset of Server Side Request Forgery (SSRF) attacks, where an attacker can make the server send requests to internal resources or other vulnerable systems.

To mitigate this vulnerability, the server should validate and sanitize the Host header value before using it in the redirect. It should only allow redirection to trusted and pre-defined destinations, rather than blindly redirecting based on the client-provided Host header.

Steps

  • Validate the 'host' header value to ensure it is a trusted domain.
  • If the 'host' header value is not trusted, do not perform the redirect.
  • Instead, return an error response or redirect to a safe and predefined URL.
  • Implement a whitelist of trusted domains and compare the 'host' header value against this whitelist.
  • If the 'host' header value is not in the whitelist, do not perform the redirect.
  • Update the code to use a predefined and safe redirect URL instead of relying on the 'host' header value.
  • Remove the '/redirected' route if it is not necessary for the application functionality.
  • Test the updated code thoroughly to ensure it functions as expected.

Compliant code

import express from 'express';

const app = express();

const trustedDomains = ['example.com', 'trusteddomain.com']; // Add your trusted domains here

app.get('/', (req, res) => {
const { host } = req.headers;

if (!trustedDomains.includes(host)) {
res.status(403).send('Forbidden');
return;
}

const redirectUrl = 'https://example.com/redirected'; // Replace with your safe redirect URL
res.redirect(301, redirectUrl);
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

The fixed code is written in TypeScript using the Express framework. It addresses the vulnerability by implementing a server-side check to prevent uncontrolled external site redirects.

Here's an explanation of the code:

  1. We import the Express module and create an instance of the Express application.
import express from 'express';
const app = express();
  1. We define an array called trustedDomains that contains the list of domains we consider safe for redirection. You should add your trusted domains to this array.
const trustedDomains = ['example.com', 'trusteddomain.com'];
  1. We define a route handler for the root path ("/") using the app.get() method. This handler will be executed when a GET request is made to the root path.
app.get('/', (req, res) => {
// Code for handling the request goes here
});
  1. Inside the route handler, we extract the host header from the request headers.
const { host } = req.headers;
  1. We check if the extracted host value is included in the trustedDomains array. If it is not, we send a 403 Forbidden response and return from the handler.
if (!trustedDomains.includes(host)) {
res.status(403).send('Forbidden');
return;
}
  1. If the host value is found in the trustedDomains array, we define a redirectUrl variable with the safe URL to redirect to. Replace 'https://example.com/redirected' with your desired safe redirect URL.
const redirectUrl = 'https://example.com/redirected';
  1. Finally, we use the res.redirect() method to perform a 301 redirect to the redirectUrl.
res.redirect(301, redirectUrl);
  1. We start the Express server and listen on port 3000.
app.listen(3000, () => {
console.log('Server is running on port 3000');
});

By implementing these checks, the fixed code ensures that only requests with trusted domains in the host header will be allowed to proceed with the redirect.

References