logo

Database

Uncontrolled external site redirect

Need

Restriction of redirect targets to paths within the application

Context

• Usage of Rust 1.75+ for building application services

• Usage of the actix-web crate for handling HTTP requests

Description

1. Non compliant code

use actix_web::web::Redirect;
use actix_web::{get, web};
use serde::Deserialize;

#[derive(Deserialize)]
struct LoginQuery {
    next: String,
}...

The `login` handler below redirects the user to the value of the `next` query parameter with `Redirect::to`, without checking it. An attacker can send a link such as `https://app.example.com/login?next=https://app-example.evil.com/login` by email or chat. The link points to the real domain, so it looks trustworthy, but after logging in the user lands on a copy of the site controlled by the attacker, which asks for the password again or for payment details. Open redirects are also used to bypass URL allowlists in OAuth flows and to leak authorization codes. Building the target from request data with `format!`, or setting the `Location` header directly with `HttpResponse::Found().append_header`, has the same effect.

2. Steps

• Never pass request data directly to `Redirect::to` or to a `Location` header.

• Accept only relative paths that start with a single `/`, and reject values that start with `//` or `/\` or contain control characters.

• Map external destinations to identifiers that the server resolves against a fixed allowlist of URLs.

• Fall back to a default page when the requested target is not valid, instead of returning an error that reveals the check.

• Register exact redirect URIs for OAuth and single sign-on clients.

3. Secure code example

use actix_web::web::Redirect;
use actix_web::{get, web};
use serde::Deserialize;

const DEFAULT_TARGET: &str = "/dashboard";

#[derive(Deserialize)]
struct LoginQuery {...

The corrected handler passes the parameter through `safe_redirect_target` before redirecting. The function accepts only relative paths: the value must start with a single `/`. Values starting with `//` or `/\` are rejected because browsers treat them as protocol-relative URLs that point to another host, and control characters are rejected because some browsers strip them and reinterpret the rest of the value. Anything that fails the check, including absolute URLs, falls back to `/dashboard`. Because the target is always a path, the browser stays on the same origin. When the application must redirect to other domains, the parameter should carry an identifier that the server maps to a fixed URL, rather than the URL itself.