HIGH ssrfaxum

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.com

This 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 IDNameSeverity
CWE-918Server-Side Request Forgery (SSRF) CRITICAL
CWE-441Unintended Proxy or Intermediary (Confused Deputy) HIGH

Frequently Asked Questions

How does middleBrick detect SSRF in Axum applications?
middleBrick uses black-box scanning to test Axum endpoints for SSRF by sending requests to known internal IP ranges, loopback addresses, and cloud metadata services. The scanner examines how your Axum application handles these requests and reports any successful connections to restricted resources. It also analyzes your OpenAPI spec if available and tests the actual runtime behavior of your endpoints.
Can middleBrick scan my local Axum development server?
Yes, middleBrick's CLI tool can scan any running Axum server, including local development instances. Simply run 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.