Open Redirect Chain in Axum
How Open Redirect Chain Manifests in Axum
Open redirect vulnerabilities in Axum often arise from improper handling of redirect parameters in route handlers, particularly when user-supplied input is directly used in Redirect::to() or Response::builder().header(header::LOCATION, ...) without validation. An open redirect chain occurs when an attacker chains multiple redirects through trusted domains to bypass security controls, ultimately leading to a malicious endpoint. In Axum, this commonly appears in authentication flows where return_to or redirect query parameters are accepted and reflected in redirect responses.
Consider an Axum route that handles OAuth callbacks or login redirects:
use axum::response::Redirect;
use axum::extract::Query;
async fn login_redirect(Query(params): Query>) -> Redirect {
let return_to = params.get("return_to").unwrap_or(&"/".to_string());
Redirect::to(&format!("https://trusted-login.example.com/oauth?return_to={}", return_to))
}
If the return_to parameter is not validated, an attacker can supply a value like https://evil.com. After login, the trusted login service redirects to https://evil.com. However, a chain emerges when the attacker uses an intermediate open redirect on a trusted domain (e.g., a marketing subdomain) that forwards to the malicious site. For example:
- Attacker crafts URL:
https://app.example.com/login?return_to=https://marketing.example.com/redirect?url=https://evil.com/phish - Axum app redirects to trusted login service with
return_to=https://marketing.example.com/redirect?url=https://evil.com/phish - Login service redirects user to the marketing subdomain’s redirect endpoint
- Marketing endpoint, vulnerable to open redirect, forwards to
https://evil.com/phish - User lands on phishing site, believing they are still within the trusted ecosystem
This chain exploits trust in intermediate domains and can bypass security measures that only check the final redirect destination. Axum’s routing and extraction system does not inherently validate redirect URLs, making it critical to implement validation at the handler level.
Axum-Specific Detection
Detecting open redirect chains in Axum requires analyzing route handlers that generate HTTP redirects based on user input. middleBrick identifies this by scanning for patterns where query parameters, path segments, or headers are directly embedded into Location headers without validation. During its black-box scan, middleBrick injects payloads like https://evil.com, //evil.com, or \\evil.com\@trusted.com into parameters commonly used for redirects (e.g., return_to, redirect, next, url) and monitors responses for 3xx status codes with Location headers pointing to external domains.
For Axum applications, middleBrick pays special attention to routes that:
- Use
QueryorPathextractors to capture redirect-related parameters - Call
Redirect::to()or manually setheader::LOCATION - Are part of authentication, OAuth, or SSO flows
For example, given the route:
async fn oauth_callback(Query(params): Query>) -> impl IntoResponse {
let redirect_uri = params.get("redirect_uri").unwrap();
// Vulnerable: direct use without validation
(StatusCode::FOUND, [(header::LOCATION, redirect_uri.as_str())])
}
middleBrick would detect this by sending a request like /oauth/callback?redirect_uri=https://evil.com and observing a 302 Found response with Location: https://evil.com. To detect chains, it follows redirects and checks if intermediate hops involve trusted domains that themselves redirect to untrusted locations—indicating a potential redirect chain vector.
middleBrick’s LLM/AI security module does not directly apply here, but its core 12 checks include Input Validation and Data Exposure, which cover open redirect scenarios. The scanner does not require agents or configuration; simply providing the Axum API’s base URL triggers these tests.
Axum-Specific Remediation
Fixing open redirect vulnerabilities in Axum involves validating and sanitizing all user-supplied URLs used in redirect responses. Axum does not provide built-in URL validation, so developers must implement allow-list or strict validation logic using Rust’s standard library or trusted crates.
The recommended approach is to parse the user input as a URL, validate its components (scheme, host, port), and compare against an allow-list of trusted domains. For example:
use axum::response::Redirect;
use axum::extract::Query;
use url::Url;
async fn safe_login_redirect(Query(params): Query>) -> Result {
let return_to = params.get("return_to").ok_or_else(|| (StatusCode::BAD_REQUEST, "Missing return_to"))?;
let parsed = Url::parse(return_to)
.map_err(|_| (StatusCode::BAD_REQUEST, "Invalid URL"))?;
// Allow-list trusted domains
let trusted_hosts = ["app.example.com", "api.example.com", "login.example.com"];
if !trusted_hosts.contains(&parsed.host_str().unwrap_or("")) {
return Err((StatusCode::BAD_REQUEST, "Untrusted redirect domain"));
}
// Optional: enforce HTTPS
if parsed.scheme() != "https" {
return Err((StatusCode::BAD_REQUEST, "Only HTTPS redirects allowed"));
}
Ok(Redirect::to(return_to))
}
// In your route:
// app.route("/login-redirect", get(safe_login_redirect));
This code uses the url crate to safely parse and validate the URL. It checks the host against an allow-list and enforces HTTPS. If validation fails, it returns a 400 Bad Request instead of redirecting.
For applications requiring dynamic trusted domains (e.g., tenant-specific subdomains), consider validating against a pattern or database:
// Example: allow subdomains of example.com
if let Some(host) = parsed.host_str() {
if !host.ends_with(".example.com") && host != "example.com" {
return Err((StatusCode::BAD_REQUEST, "Untrusted domain"));
}
}
Avoid denylist approaches (e.g., blocking evil.com) as they are easily bypassed via encoding, alternate IP representations, or subdomain tricks. Never trust user input for redirect destinations without validation. After fixing, rescan with middleBrick to confirm the Location header no longer reflects untrusted inputs.
Frequently Asked Questions
Can middleBrick detect open redirect chains that involve multiple hops through different domains?
Is it safe to use Axum’s <code>Redirect::to()</code> with user input if I check that the URL starts with my domain?
https://example.com.evil.com, https://example.com\@evil.com, or https://example.com:80@evil.com. Always parse the URL fully and validate the host component against an allow-list using a trusted library like the url crate.