HIGH axumssrf blind

Ssrf Blind in Axum

How Blind SSRF Manifests in Axum

Server-Side Request Forgery (SSRF) is a vulnerability where an attacker tricks a server into making requests to unintended locations. In the context of APIs, this often occurs when user-supplied input (such as a URL parameter) is used to construct an outgoing HTTP request without proper validation. Blind SSRF refers to scenarios where the attacker does not directly receive the response from the targeted internal resource but can infer information through side channels like response timing, error messages, or out-of-band interactions.

Axum, a popular asynchronous web framework for Rust, is particularly susceptible to SSRF when developers use extractors to obtain user input and then pass that input directly to HTTP clients like reqwest. Consider the following vulnerable endpoint:

use axum::{extract::Query, response::Html, routing::get, Router}; use reqwest::Client; use serde::Deserialize; #[derive(Deserialize)] struct ProxyParams { url: String, } async fn proxy_handler(Query(params): Query) -> Html { let client = Client::new(); match client.get(¶ms.url).send().await { Ok(resp) => Html(format!("Status: {}", resp.status())), Err(e) => Html(format!("Error: {}", e)), } } let app = Router::new().route("/proxy", get(proxy_handler));

This endpoint accepts a url query parameter and fetches that URL. If the application only returns the HTTP status or an error message, it becomes a blind SSRF vector. An attacker can probe internal networks by providing URLs like http://127.0.0.1:6379/ (Redis) or http://169.254.169.254/latest/meta-data/ (AWS metadata). By measuring response times or observing error patterns, the attacker can infer whether the internal service exists, its responsiveness, and potentially extract sensitive data indirectly. For example, a slower response might indicate that the target service is processing the request, while a quick error could mean the port is closed.

Axum's asynchronous nature does not mitigate SSRF; in fact, the non-blocking I/O model can make timing-based inference more reliable. Additionally, Axum's flexible extractor system encourages patterns where user input is directly passed to downstream services, increasing the attack surface if not carefully validated.

Axum-Specific Detection

Detecting SSRF vulnerabilities in Axum applications involves both manual code review and dynamic testing. During code review, look for any handler that extracts user-controlled input (via Query, Json, or other extractors) and uses it to initiate an HTTP request, especially with reqwest or similar clients. Pay special attention to places where the URL is constructed from multiple parameters or where the scheme and host are not strictly validated.

Dynamic testing requires sending crafted SSRF payloads to the API and observing the responses. For blind SSRF, you can monitor response times: a significant delay when targeting 127.0.0.1 versus an external IP might indicate that the server is attempting to connect to localhost. Error messages may also leak information, such as "Connection refused" or "Timeout", revealing the existence of a service.

Scanning with middleBrick automates this process. middleBrick's SSRF check sends a series of probes targeting common internal resources (e.g., http://127.0.0.1/, http://[::1]/, cloud metadata endpoints) and analyzes the API's responses for signs of SSRF. It measures response times, inspects status codes and body content, and looks for anomalies that suggest the server is making unintended requests. The scan runs in seconds and produces a detailed report with a risk score and actionable remediation guidance, helping you identify blind SSRF vulnerabilities without manual effort.

Axum-Specific Remediation

Remediating SSRF in Axum requires strict validation of any user-supplied URLs before they are used in outgoing requests. The recommended approach is to create a custom extractor that parses the URL, enforces allowed schemes (typically only http and https), and blocks private IP ranges and localhost. This extractor can then be used in place of a plain String or Url in your handlers.

Below is an example of a robust custom extractor using the url crate and a simple private IP check. In production, you should use a dedicated library like ipnetwork or an async DNS resolver such as trust-dns-resolver to properly resolve hostnames to IP addresses and check against all private ranges (including IPv6).

use axum::{extract::{FromRequest, RequestParts}, http::StatusCode, response::Response}; use url::Url; #[derive(Debug)] struct ValidatedUrl(Url); impl FromRequest for ValidatedUrl where S: Send + Sync, { type Rejection = (StatusCode, String); async fn from_request(req: &mut RequestParts) -> Result { // Extract the 'url' query parameter (adjust for your use case) let query = req.uri().query().ok_or(( StatusCode::BAD_REQUEST, "Missing query string".to_string(), ))?; let mut url_str = None; for pair in url::form_urlencoded::parse(query.as_bytes()) { if pair.0 == "url" { url_str = Some(pair.1); break; } } let url_str = url_str.ok_or(( StatusCode::BAD_REQUEST, "Missing 'url' parameter".to_string(), ))?; let url = Url::parse(&url_str).map_err(|e| { ( StatusCode::BAD_REQUEST, format!("Invalid URL: {}", e), ) })?; // Allow only HTTP/HTTPS if !["http", "https"].contains(&url.scheme()) { return Err(( StatusCode::BAD_REQUEST, "Only http and https schemes are allowed".to_string(), )); } // Simple check for private IPs in the host string. // WARNING: This is not comprehensive. Use a proper resolver in production. if let Some(host) = url.host_str() { if is_private_ip_or_hostname(host) { return Err(( StatusCode::BAD_REQUEST, "Access to private IPs or localhost is denied".to_string(), )); } } Ok(ValidatedUrl(url)) } } fn is_private_ip_or_hostname(host: &str) -> bool { // Check for common private IP prefixes and localhost // This list is incomplete; see RFC 1918 and RFC 4193 for full ranges. let private_prefixes = [ "127.", "10.", "192.168.", "172.16.", "172.17.", "172.18.", "172.19.", "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.", "172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31.", "::1", "localhost", ]; private_prefixes.iter().any(|&prefix| host.starts_with(prefix)) }

You can then use this extractor in your route:

async fn proxy_handler(ValidatedUrl(url): ValidatedUrl) -> impl IntoResponse { let client = reqwest::Client::new(); let resp = client.get(url).send().await?; // Process response safely // ... }

For broader coverage, you can also implement Axum middleware that validates all incoming requests containing URL parameters. However, custom extractors are typically more straightforward and type-safe. Remember to also validate the port if your use case restricts ports, and consider using a safelist of allowed domains when possible.

Finally, keep your dependencies updated and perform regular security scans with tools like middleBrick to catch any regressions. middleBrick's continuous monitoring (available in Pro and Enterprise plans) can alert you if new SSRF vulnerabilities are introduced.

Frequently Asked Questions

What is blind SSRF and why is it particularly dangerous in Axum applications?
Blind SSRF is a variant where the attacker cannot directly see the response from the targeted internal resource but can infer information through side channels like response timing, error messages, or out-of-band interactions. In Axum applications, this can be exploited to probe internal networks, access cloud metadata endpoints, or interact with internal services, potentially leading to credential theft or lateral movement. Even without direct data exfiltration, blind SSRF provides valuable reconnaissance for further attacks.
How does middleBrick detect blind SSRF without having credentials or access to the internal network?
middleBrick uses black-box scanning techniques, sending requests with SSRF payloads that target common internal resources (like 127.0.0.1, 169.254.169.254) and analyzing the API's responses for anomalies. For blind SSRF, it looks for differences in response times, status codes, or error messages that indicate the server is making unintended requests. It may also use out-of-band detection methods if configured with a collaborator server, but its primary approach is through careful observation of the API's behavior under test conditions.