Open Redirect in Axum
How Open Redirect Manifests in Axum
Open Redirect vulnerabilities in Axum applications typically occur when user-controlled input is used to construct redirect URLs without proper validation. In Axum, this commonly appears in route handlers that accept query parameters for redirection, such as OAuth callbacks, login flows, or navigation helpers.
use axum::http::Uri;
use axum::response::{IntoResponse, Redirect};
use axum::routing::get;
use axum::Router;
async fn login(
Query(params): Query,
) -> impl IntoResponse {
// Vulnerable: user-controlled 'redirect_to' parameter
// is used directly without validation
Redirect::to(params.redirect_to.clone())
}
#[derive(serde::Deserialize)]
struct LoginParams {
redirect_to: String,
}
let app = Router::new()
.route("/login", get(login));
This pattern is dangerous because an attacker can craft a URL like /login?redirect_to=https://evil.com, causing the application to redirect users to a malicious site. The vulnerability becomes more severe when combined with authentication flows, where an attacker could redirect users after successful login to a phishing site that captures credentials.
Another common manifestation in Axum involves using Uri::from_parts or Uri::builder with user input:
use axum::http::Uri;
use axum::response::Redirect;
async fn callback(
Path(user_id): Path,
Query(params): Query,
) -> impl IntoResponse {
let redirect_uri = Uri::from_parts(params.redirect_uri.clone())
.expect("valid URI");
// Vulnerable: no validation of redirect_uri
Redirect::to(redirect_uri)
}
#[derive(serde::Deserialize)]
struct CallbackParams {
redirect_uri: String,
}
The danger here is that Uri::from_parts will accept any valid URI scheme, including javascript:, data:, or file: schemes, which can lead to XSS or other attacks beyond simple HTTP redirects.
Axum-Specific Detection
Detecting Open Redirect vulnerabilities in Axum applications requires examining route handlers that construct redirect responses from user input. The middleBrick API security scanner can automatically identify these issues by analyzing your Axum application's endpoints.
When middleBrick scans an Axum application, it looks for patterns where:
- Query parameters are used to construct redirect URLs without validation
- Path parameters containing URL-like strings are used in redirect responses
- Request body parameters are reflected in redirect locations
- Header values (like
Referer) are used to construct redirects
For example, middleBrick would flag the following vulnerable Axum route:
async fn oauth_callback(
Query(params): Query<OauthParams>,
) -> impl IntoResponse {
// middleBrick flags this as vulnerable
Redirect::to(params.state.clone())
}
#[derive(serde::Deserialize)]
struct OauthParams {
state: String,
}
The scanner tests these endpoints by sending requests with various URI schemes and external domains, then analyzing the responses to detect whether the application redirects to arbitrary locations. middleBrick's LLM/AI security module also checks for prompt injection vulnerabilities that could be chained with Open Redirect in AI-powered applications.
For local development and CI/CD integration, you can use the middleBrick CLI to scan your Axum application:
npx middlebrick scan http://localhost:3000/login
This command will analyze your running Axum server and provide a security score with specific findings about Open Redirect vulnerabilities and other API security issues.
Axum-Specific Remediation
Securing Axum applications against Open Redirect requires validating redirect targets against a whitelist of allowed destinations. Axum's type system and middleware can help enforce these security controls.
The most secure approach is to use an enum of allowed redirect targets:
use axum::http::Uri;
use axum::response::{IntoResponse, Redirect};
use axum::routing::get;
use axum::Router;
#[derive(Clone, PartialEq)]
enum AllowedRedirect {
Home,
Dashboard,
Profile,
}
impl AllowedRedirect {
fn to_uri(&self) -> Uri {
match self {
AllowedRedirect::Home => Uri::from_static("/"),
AllowedRedirect::Dashboard => Uri::from_static("/dashboard"),
AllowedRedirect::Profile => Uri::from_static("/profile"),
}
}
}
async fn login(
Query(params): Query<LoginParams>,
) -> impl IntoResponse {
let redirect_target = match params.redirect_to.as_str() {
"home" => AllowedRedirect::Home,
"dashboard" => AllowedRedirect::Dashboard,
"profile" => AllowedRedirect::Profile,
_ => AllowedRedirect::Home, // default to safe value
};
Redirect::to(redirect_target.to_uri())
}
#[derive(serde::Deserialize)]
struct LoginParams {
redirect_to: String,
}
This approach eliminates the vulnerability by removing arbitrary URL construction entirely. Users can only redirect to predefined, safe locations within your application.
For cases where external redirects are necessary (like OAuth flows), implement strict validation:
use axum::http::Uri;
use axum::response::Redirect;
use axum::routing::get;
use axum::Router;
async fn oauth_callback(
Query(params): Query<OauthParams>,
) -> Result<impl IntoResponse, StatusCode> {
let allowed_domains = ["https://yourapp.com", "https://yourotherapp.com"];
let redirect_uri = match Uri::from_str(¶ms.redirect_uri) {
Ok(uri) => uri,
Err(_) => return Err(StatusCode::BAD_REQUEST),
};
// Validate scheme and host
if redirect_uri.scheme_str() != Some("https") {
return Err(StatusCode::BAD_REQUEST);
}
if let Some(host) = redirect_uri.host() {
if !allowed_domains.contains(&host) {
return Err(StatusCode::BAD_REQUEST);
}
}
Ok(Redirect::to(redirect_uri))
}
#[derive(serde::Deserialize)]
struct OauthParams {
redirect_uri: String,
}
This validation ensures redirects only go to trusted domains and uses HTTPS, preventing open redirect attacks while maintaining necessary functionality.