Open Redirect in Axum with Firestore
Open Redirect in Axum with Firestore — how this specific combination creates or exposes the vulnerability
An open redirect in an Axum application that uses Firestore typically occurs when a route accepts a user-controlled URL or location parameter and passes it directly to an HTTP redirect without validation. Firestore is often used to store configuration or tenant-specific redirect mappings (for example, storing a list of allowed redirect URIs per organization). If the application retrieves a redirect target from Firestore based on an ID provided by the client and then uses that value in an Axum redirect response without strict allowlisting, the combination introduces a server-side open redirect vector.
Consider an endpoint like /auth/callback?provider=example&redirect_id=123. The handler might look up redirect_id in Firestore, retrieve a stored URL such as https://trusted.example.com/app, and then call redirect(StatusCode::SEE_OTHER, retrieved_url). If the stored URL can be maliciously controlled (for example, an attacker can write a malicious URL into Firestore via a compromised admin account or through another injection flaw), the redirect becomes unsafe. Even when Firestore enforces rules, the application logic itself must enforce allowlisting; relying solely on backend rules is insufficient because a rule misconfiguration or a compromised credential can expose redirect targets.
Attackers exploit this by tricking users into visiting a crafted link that includes a malicious redirect target or ID. If the application uses an ID that maps to a user-modifiable Firestore document, an attacker might first create or update a document containing a phishing URL. Later, when a victim follows a legitimate-looking link, the Axum handler performs an open redirect to the attacker-controlled location. This can lead to phishing campaigns, session fixation, or abuse of OAuth flows where the redirect URI is a critical security boundary. The risk is amplified when the Firestore data contains URLs that are assumed to be static but are actually writable by broader roles.
Because middleBrick scans the unauthenticated attack surface and tests inputs that influence Firestore document references and redirect behavior, it can detect patterns where redirect parameters are not strictly validated against an allowlist. Findings include missing validation on redirect IDs, overly permissive Firestore security rules that permit write access to redirect configuration, and responses that reflect stored URLs without canonicalization or strict schema checks.
Firestore-Specific Remediation in Axum — concrete code fixes
To remediate open redirect risks in Axum when using Firestore, enforce strict allowlisting on redirect targets and validate document references independently of Firestore rules. Do not trust Firestore security rules alone to enforce safe redirect behavior; treat all data from Firestore as potentially attacker-influenced and validate it in application logic.
Use an allowlist of known-safe redirect targets keyed by an opaque identifier rather than storing arbitrary URLs. For example, define a server-side mapping of allowed redirect paths and reference them by constant keys stored in Firestore as enum-like strings. When a client provides a redirect identifier, look up the allowed path on the server and construct the full URL internally instead of returning a client-supplied URL.
Example Axum handler with safe Firestore-backed redirect resolution:
use axum::{routing::get, Router, http::StatusCode, response::Redirect};
use firestore::FirestoreDb;
use serde::Deserialize;
// Define allowed redirect kinds; do not accept raw URLs from the client.
#[derive(Debug, Deserialize)]
struct RedirectConfig {
kind: String, // e.g., "onboarding", "terms_accept", "org_home"
// Do not store raw URLs here; map kinds to server-side constants.
}
async fn get_redirect_target(db: &FirestoreDb, kind: &str) -> Option {
// Fetch a config document by kind; ensure security rules limit reads to allowed collections.
let doc_ref = db.collection(&format!("redirect_configs/{}", kind));
// Assume a helper that retrieves a document; implement robust error handling and timeouts.
doc_ref.get_document::().await.ok().and_then(|c| {
match c.kind.as_str() {
"onboarding" => Some("https://app.example.com/onboarding".to_string()),
"terms_accept" => Some("https://app.example.com/terms".to_string()),
"org_home" => Some("https://app.example.com/organization".to_string()),
_ => None,
}
})
}
async fn callback_handler(
Query(params): Query>,
db: Extension,
) -> Result<Redirect, (StatusCode, String)> {
let kind = params.get("kind").ok_or((StatusCode::BAD_REQUEST, "missing kind"))?;
let target = get_redirect_target(&db, kind).ok_or((StatusCode::BAD_REQUEST, "invalid kind"))?;
Ok(Redirect::temporary(target))
}
fn app() -> Router {
Router::new()
.route("/auth/callback", get(callback_handler))
}
This approach ensures the redirect destination is determined server-side and not directly reflected from Firestore documents that could be tampered with. If you must store full URLs, enforce strict validation: parse the URL, reject non-whitelisted hosts and schemes, canonicalize the path, and reject URLs containing redirects, credentials, or unexpected ports. Combine this with Firestore security rules that tightly limit which identities can write redirect configuration documents and audit read access patterns using middleBrick scans to detect misconfigurations.