Open Redirect in Axum with Cockroachdb
Open Redirect in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability
An Open Redirect in an Axum application that uses Cockroachdb typically arises when a handler accepts a user-supplied URL or location parameter and performs an HTTP redirect without strict validation. If the handler queries Cockroachdb to determine a target—for example, looking up a tenant or campaign record by an identifier passed in the URL—and then redirects using a value stored in or derived from Cockroachdb, the trust boundary between data and redirection logic becomes vulnerable.
Consider an Axum handler that reads a redirect_key from the path, fetches a redirect record from Cockroachdb, and uses the stored destination directly:
// WARNING: illustrative pattern that can lead to open redirect if destination is untrusted
async fn redirect_handler(
Path(key): Path,
pool: Extension
) -> Result {
let row = pool.query_one(
"SELECT destination FROM redirects WHERE key = $1",
&[&key]
).await?;
let destination: String = row.get(0);
// If destination is attacker-controlled, this is an open redirect
Ok(Redirect::permanent(&destination))
}
If the Cockroachdb table allows arbitrary destinations (for example, due to insufficient constraints or a compromised row), an attacker can craft a link like https://api.example.com/redirect/offer1 that leads to a malicious site. Because the redirect decision is based on data stored in Cockroachdb, the attack appears to be legitimate content from your domain, increasing phishing risk. The same risk exists if query parameters are used to build a redirect URL and Cockroachdb is used to validate or select a target without canonicalizing and whitelisting allowed hosts.
In a broader scan, middleBrick would classify this as a BFLA/Privilege Escalation or Property Authorization issue when redirects depend on data that should be constrained, and it would note the absence of strict allowlists for redirect targets. The scanner also checks whether the endpoint is effectively unauthenticated and whether inputs are validated, highlighting the missing host allowlist and normalization of the destination URI.
Cockroachdb-Specific Remediation in Axum — concrete code fixes
Remediation centers on ensuring redirect targets are not directly controlled by external data stored in Cockroachdb and on strict validation before any redirect. Prefer using an internal mapping (e.g., a short-lived token or enumerated key) rather than storing arbitrary redirect URLs in Cockroachdb. If you must store destinations, enforce a strict allowlist of permitted hosts and canonicalize inputs before lookup.
Safe Axum handler using an allowlist approach:
use axum::{routing::get, Router, http::StatusCode};
use axum::extract::Path;
use sqlx::postgres::PgPoolOptions;
use url::Url;
async fn safe_redirect_handler(
Path(key): Path,
pool: axum::extract::State
) -> Result {
// Map keys to predefined destinations; no arbitrary URLs stored in Cockroachdb
let destination = match key.as_str() {
"home" => "https://example.com",
"docs" => "https://docs.example.com",
"pricing" => "https://example.com/pricing",
_ => return Err((StatusCode::BAD_REQUEST, "Invalid key".into())),
};
// Optional: verify destination host against an allowlist even if stored safely
let parsed = Url::parse(destination).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Invalid URL".into()))?;
if !["example.com", "docs.example.com"].contains(&parsed.host_str().unwrap_or("")) {
return Err((StatusCode::BAD_REQUEST, "Disallowed host".into()));
}
Ok(axum::http::response::Redirect::new(destination))
}
// SQL schema suggestion for allowed mappings (no free-form destination column)
// CREATE TABLE redirects (
// key TEXT PRIMARY KEY CHECK (key IN ('home', 'docs', 'pricing')),
// kind TEXT GENERATED ALWAYS AS ('internal') STORED
// );
If you need dynamic destinations, store normalized, constrained URIs in Cockroachdb and validate against an allowlist at runtime:
async fn normalized_redirect_handler(
Path(key): Path,
pool: axum::extract::State
) -> Result {
row: sqlx::query!(
"SELECT destination FROM redirects WHERE key = $1",
&key
)
.fetch_optional(pool.as_ref())
.await
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB error"))?;
let row = row.ok_or_else(|| (StatusCode::NOT_FOUND, "Not found"))?;
let dest = row.destination;
let parsed = Url::parse(&dest).map_err(|_| (StatusCode::BAD_REQUEST, "Invalid destination"))?;
let host = parsed.host_str().ok_or_else(|| (StatusCode::BAD_REQUEST, "No host"))?;
let allowed: std::collections::HashSet<&str> = ["example.com", "cdn.example.com"].into_iter().collect();
if !allowed.contains(host) {
return Err((StatusCode::BAD_REQUEST, "Host not allowed".into()));
}
// Ensure no path/query injection; rebuild a clean URL if needed
let clean = format!("{}://{}", parsed.scheme(), host);
Ok(axum::http::response::Redirect::new(clean))
}
These patterns ensure that even when Cockroachdb is the source of truth for mapping, the application never performs an unrestricted redirect to attacker-controlled locations. middleBrick will recognize these mitigations by observing strict input validation, host allowlists, and the absence of arbitrary destination fields in scan findings.
Frequently Asked Questions
How can I test my Axum + Cockroachdb redirect safely before deployment?
middlebrick scan https://your-api.example.com/redirect/home. The scan will report whether the redirect behavior exposes open redirect risks based on runtime tests, without requiring credentials or code changes.