Ssrf Blind in Axum (Rust)
Ssrf Blind in Axum with Rust — how this specific combination creates or exposes the vulnerability
Server-Side Request Forgery (SSRF) in an Axum service written in Rust typically arises when user-controlled input is used to drive HTTP requests without strict validation or network isolation. In a blind SSRF scenario, the attacker can cause the server to make requests to arbitrary internal or external endpoints, but the application does not return response bodies or status codes that reveal the outcome. This makes detection difficult and allows actions such as metadata service enumeration (e.g., 169.254.169.254 on cloud environments), internal service probing, or SSRF-to-RCE via crafted requests to vulnerable internal protocols.
With Axum, a common pattern is to define a handler that receives a URL via query parameters or a JSON body and forwards it using a client like reqwest. If the handler does not validate the target host, port, or scheme, an attacker can supply internal addresses, RFC-reserved domains, or malicious schemes to trigger blind behavior. For example, a handler that trusts user input to construct a request can be exploited to reach the metadata service, internal management interfaces, or even local UNIX sockets if the runtime permits. The absence of network-level restrictions (such as egress policies or host whitelisting) amplifies the risk. Because Axum is built on hyper and relies on async runtime, outbound requests initiated via reqwest can follow redirects or resolve internal DNS in ways that bypass naive allowlists, enabling blind SSRF without clear error feedback to the caller.
In Rust, dependencies and type safety do not inherently prevent SSRF; they shift the burden to the developer to enforce strict allowlists and reject dangerous destinations. Blind SSRF is particularly dangerous in Axum APIs that run inside cloud environments, where metadata endpoints are reachable from within the instance. An attacker can chain SSRF with other weaknesses—such as insecure deserialization or unsafe consumption patterns—to escalate impact. The scanner’s checks for SSRF, combined with inventory management and unsafe consumption detection, help surface these patterns by correlating endpoint definitions in OpenAPI specs with runtime behavior that permits unrestricted outbound requests.
Rust-Specific Remediation in Axum — concrete code fixes
To remediate blind SSRF in Axum with Rust, enforce strict allowlisting of hosts and schemes, avoid dynamic request construction from raw user input, and validate destinations before initiating HTTP calls. Use typed extractors and structured configuration to limit outbound targets. Below are concrete, idiomatic examples that demonstrate secure patterns.
1. Validate and restrict targets with a typed extractor
Define a domain allowlist and parse user input into a verified destination. This prevents arbitrary URLs and ensures only approved hosts are reachable.
use axum::extract::Query;
use reqwest::Url;
use serde::Deserialize;
use std::net::IpAddr;
#[derive(Deserialize)]
pub struct ProxyQuery {
pub host: String,
pub port: u16,
pub path: String,
}
fn is_allowed_host(host: &str) -> bool {
const ALLOWED: &[&str] = &["127.0.0.1", "10.0.0.1", "api.internal.example.com"];
ALLOWED.contains(&host)
}
async fn proxy_handler(Query(params): Query<ProxyQuery>) -> Result<String, (axum::http::StatusCode, String)> {
if !is_allowed_host(¶ms.host) {
return Err((axum::http::StatusCode::BAD_REQUEST, "host not allowed".into()));
}
let url = format!("http://{}:{}{}", params.host, params.port, params.path);
let parsed = Url::parse(&url).map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "invalid URL"))?;
let client = reqwest::Client::new();
let resp = client.get(parsed).send().await.map_err(|e| (axum::http::StatusCode::BAD_GATEWAY, e.to_string()))?;
let body = resp.text().await.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(body)
}
2. Use a permit-only policy for IPs and reject private ranges
In Rust, you can programmatically block private and non-routable addresses to prevent SSRF to internal services. Combine this with explicit port restrictions.
use std::net::{IpAddr, Ipv4Addr};
fn is_public_address(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(v4) => !v4.is_private() && v4 != Ipv4Addr::new(127, 0, 0, 1),
IpAddr::V6(_) => false, // adjust policy as needed
}
}
// Example usage inside handler logic:
// let dest_ip = "169.254.169.254".parse().unwrap();
// if !is_public_address(dest_ip) { return Err(...); }
3. Enforce timeouts and disable redirects
Prevent slowloris-style abuse and unintended chaining by disabling automatic redirects and setting strict timeouts on the reqwest client.
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.timeout(std::time::Duration::from_secs(5))
.build()
.expect("client build");
4. Integrate with middleware for global rules
Use Axum middleware to reject requests that contain dangerous patterns in headers or paths before they reach handlers, reducing the chance of accidental bypasses.
use axum::{http::Request, middleware::Next, body::Body};
use std::future::Future;
pub async fn security_middleware(mut req: Request<B>, next: Next<B>) -> Result<axum::response::Response, (axum::http::StatusCode, String)>
where
B: Send + 'static,
{
// Example: block requests with internal-only host-like headers
if let Some(h) = req.headers().get("x-forwarded-host") {
if let Ok(val) = h.to_str() {
if val.contains("169.254") || val.ends_with(".internal") {
return Err((axum::http::StatusCode::FORBIDDEN, "forbidden host header".into()));
}
}
}
Ok(next.run(req).await)
}
By combining these Rust-specific practices—typed validation, IP allowlisting, redirect disabling, and middleware controls—you reduce the attack surface for blind SSRF in Axum services. The scanner’s checks for SSRF and inventory management can highlight endpoints that deviate from these patterns, supporting targeted remediation.