Ssrf in Axum
How SSRF Manifests in Axum
Server-Side Request Forgery (SSRF) in Axum applications typically occurs when user-controlled input is used to construct outbound HTTP requests without proper validation. In Axum's async/await architecture, this vulnerability can be particularly dangerous because the framework's ergonomic request handling makes it easy to inadvertently create SSRF vectors.
Consider an Axum endpoint that proxies requests to external services:
use axum::extract::Query;
use axum::http::Uri;
use axum_extra::headers::HeaderMapExt;
use reqwest::Client;
async fn proxy_endpoint(
Query(params): Query<HashMap<String, String>>,
client: &Client,
) -> String {
let target_url = params.get("url").unwrap_or(&"http://example.com".to_string());
// SSRF vulnerability: user-controlled URL without validation
let uri = target_url.parse().unwrap();
let res = client.get(uri).send().await.unwrap();
res.text().await.unwrap()
}
let app = axum::Router::new()
.route("/proxy", axum::routing::get(proxy_endpoint))
.with_state(Client::new());This code is vulnerable because any user can request /proxy?url=http://internal-service:8080 and access internal infrastructure. Axum's extraction system makes it trivial to accept arbitrary query parameters, but this convenience becomes a security liability when those parameters control outbound requests.
Another common pattern in Axum applications is using user input for webhook callbacks or service integrations:
async fn webhook_handler(
Json(payload): Json<WebhookPayload>,
client: &Client,
) -> String {
let response = client
.post(payload.callback_url)
.json(&payload.data)
.send()
.await
.unwrap();
response.status().to_string()
}
#[derive(Deserialize)]
struct WebhookPayload {
callback_url: String,
data: serde_json::Value,
}Without URL validation, an attacker can specify internal IPs like http://localhost:9000 or cloud metadata endpoints like http://169.254.169.254/latest/meta-data/, potentially exposing sensitive configuration data.
Axum-Specific Detection
Detecting SSRF in Axum applications requires examining both the route handlers and the HTTP client usage patterns. middleBrick's black-box scanning approach is particularly effective for Axum APIs because it tests the actual runtime behavior without needing source code access.
middleBrick scans Axum endpoints for SSRF by attempting requests to known internal IP ranges and cloud metadata services. The scanner tests patterns like:
- Private IP ranges: 10.x.x.x, 172.16-31.x.x, 192.168.x.x
- Loopback addresses: 127.0.0.1, localhost
- Link-local addresses: 169.254.x.x (AWS/Azure metadata)
- Special-use addresses: 0.0.0.0, 255.255.255.255
For Axum applications using the axum_extra::headers or axum_extra::extract crates, middleBrick specifically looks for patterns where user input flows directly into HTTP client calls without sanitization.
middleBrick's API scanning also examines OpenAPI specifications generated by Axum applications. If your Axum app uses axum::http::Uri or similar parsing, the scanner cross-references the spec definitions with runtime testing to identify endpoints that accept URL parameters.
Beyond automated scanning, you can use middleBrick's CLI to test your Axum API locally:
npx middlebrick scan http://localhost:3000/api/proxy?url=http://example.comThis command tests the specified endpoint for SSRF vulnerabilities, returning a security score and detailed findings about any SSRF vectors discovered.
Axum-Specific Remediation
Securing Axum applications against SSRF requires implementing strict URL validation and using safe HTTP client patterns. Axum's type system and extraction framework provide several mechanisms for building secure request handlers.
The most effective approach is to validate and sanitize URLs before making outbound requests. Here's a secure pattern using Axum's extraction system:
use axum::extract::Query;
use axum::http::Uri;
use axum_extra::headers::HeaderMapExt;
use reqwest::Client;
use url::Url;
async fn secure_proxy(
Query(params): Query<HashMap<String, String>>,
client: &Client,
) -> Result<String, StatusCode> {
let target_url = params.get("url").unwrap_or(&"http://example.com".to_string());
// Validate URL scheme and host
let parsed_url = match Url::parse(target_url) {
Ok(url) if url.scheme().starts_with("http") => url,
_ => return Err(StatusCode::BAD_REQUEST),
};
// Block private IP ranges and localhost
if parsed_url.host_str().unwrap_or("").parse::().is_ok() {
let ip = parsed_url.host_str().unwrap().parse::().unwrap();
if ip.is_loopback() || ip.is_private() {
return Err(StatusCode::FORBIDDEN);
}
}
// Whitelist allowed domains if needed
let allowed_domains = ["example.com", "api.trusted-service.com"];
if !allowed_domains.contains(&parsed_url.host_str().unwrap()) {
return Err(StatusCode::FORBIDDEN);
}
let res = client
.get(parsed_url.as_str())
.send()
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
res.text().await.map_err(|_| StatusCode::BAD_GATEWAY)
}
let app = axum::Router::new()
.route("/secure-proxy", axum::routing::get(secure_proxy))
.with_state(Client::new()); For Axum applications that need to make requests to internal services, use a service discovery mechanism rather than accepting user-controlled URLs:
use axum::extract::Path;
use axum::http::Uri;
use axum_extra::headers::HeaderMapExt;
use reqwest::Client;
#[derive(Deserialize)]
struct ServiceEndpoint {
base_url: String,
}
async fn internal_service_call(
Path(service_id): Path<String>,
client: &Client,
config: &ServiceEndpoint,
) -> Result<String, StatusCode> {
// Only allow predefined internal services
let service_urls = hashmap! {
"user-service".to_string() => format!("{}/users", config.base_url),
"order-service".to_string() => format!("{}/orders", config.base_url),
};
let url = service_urls.get(&service_id).ok_or(StatusCode::NOT_FOUND)?;
let res = client
.get(url)
.send()
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
res.text().await.map_err(|_| StatusCode::BAD_GATEWAY)
}
let app = axum::Router::new()
.route("/internal/:service_id", axum::routing::get(internal_service_call))
.with_state(Client::new());middleBrick's continuous monitoring in the Pro tier can help validate that these fixes remain effective as your Axum application evolves. The scanner will periodically test your endpoints and alert you if SSRF vulnerabilities reappear.
Related CWEs: ssrf
| CWE ID | Name | Severity |
|---|---|---|
| CWE-918 | Server-Side Request Forgery (SSRF) | CRITICAL |
| CWE-441 | Unintended Proxy or Intermediary (Confused Deputy) | HIGH |
Frequently Asked Questions
How does middleBrick detect SSRF in Axum applications?
Can middleBrick scan my local Axum development server?
npx middlebrick scan http://localhost:3000 to test your API. The CLI provides the same comprehensive security analysis as the web dashboard, making it easy to catch SSRF vulnerabilities before deploying to production.