HIGH ssrfaxummutual tls

Ssrf in Axum with Mutual Tls

Ssrf in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability

Server-Side Request Forgery (SSRF) in an Axum service that uses Mutual TLS (mTLS) arises when the server acts as an HTTP client and makes outbound requests to user-supplied URLs while mTLS is enforced on inbound connections only. mTLS protects the server from unauthenticated clients by requiring client certificates, but it does not automatically protect outbound requests. If the application forwards attacker-controlled URLs to an HTTP client without network-level restrictions, the server can be tricked into initiating requests to internal services that are only reachable because the server itself holds a valid client certificate and trust material.

In Axum, this typically occurs when a route handler deserializes a URL from a request and passes it to a reqwest or hyper client. For example, an endpoint that accepts a target parameter to proxy or fetch data may resolve internal hostnames like http://metadata.local or http://169.169.169.254 (cloud metadata) because the server’s outbound mTLS credentials are trusted by those internal endpoints. Outbound mTLS is uncommon; most mTLS setups terminate at the service perimeter. Therefore, SSRF emerges from the mismatch: inbound authentication via mTLS does not equate to outbound authorization, and the server may implicitly trust its own requests.

Attack patterns enabled by this combination include metadata service exfiltration, internal service enumeration via private IPs, and protocol smuggling through trusted channels. Because the server presents a valid client certificate to internal endpoints, requests succeed where external clients would fail. MiddleBrick’s checks for SSRF and authentication map to OWASP API Top 10 A01:2023 and API Security Top 10, identifying risky trust boundaries where outbound paths are not explicitly constrained.

Mutual Tls-Specific Remediation in Axum — concrete code fixes

Remediation focuses on strict input validation, network segregation, and explicit outbound policies rather than relying on mTLS for request authorization. Below are concrete Axum examples using reqwest with a connector that limits egress.

1. Example: Axum handler with validated URL and restricted client

use axum::{routing::get, Router, Json};
use serde::Deserialize;
use reqwest::Url;
use std::net::IpAddr;

#[derive(Deserialize)]
struct FetchRequest {
    target: String,
}

async fn fetch_proxy(Json(payload): Json) -> Result {
    // Validate and parse URL
    let url = Url::parse(&payload.target).map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "Invalid URL".into()))?;

    // Allowlist hostnames/IPs
    let allowed_hosts = ["api.example.com", "10.0.0.1"];
    let host = url.host_str().ok_or((axum::http::StatusCode::BAD_REQUEST, "Missing host".into()))?;
    if !allowed_hosts.contains(&host) {
        return Err((axum::http::StatusCode::FORBIDDEN, "Host not allowed".into()));
    }

    // Block private IP ranges at the URL level
    if let Some(ip) = url.ip_host() {
        if ip.is_private() {
            return Err((axum::http::StatusCode::FORBIDDEN, "Private IP not allowed".into()));
        }
    }

    // Build a restricted client (no proxy, timeouts)
    let client = reqwest::Client::builder()
        .disable_redirects()
        .timeout(std::time::Duration::from_secs(5))
        .build()
        .map_err(|_| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Client build error".into()))?;

    let resp = client.get(url).send().await.map_err(|e| (axum::http::StatusCode::BAD_GATEWAY, e.to_string()))?;
    let text = resp.text().await.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
    Ok(text)
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/fetch", get(fetch_proxy));
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

The example enforces hostname allowlisting and blocks private IPs before constructing the request. This compensates for the absence of outbound mTLS controls. Note that mTLS certificates for the server remain necessary for inbound client authentication but are not reused for outbound calls.

2. Example: Outbound network policy via middleware or connector

use reqwest::Client;
use std::sync::Arc;

struct SafeConnector {
    inner: reqwest::tls::TlsConnector,
    allowed_subnets: Vec,
}

impl SafeConnector {
    fn new(allowed_subnets: Vec) -> Self {
        let tls = reqwest::tls::TlsConnector::new();
        Self { inner: tls, allowed_subnets }
    }
}

// In practice, integrate with reqwest::ConnectorBuilder to enforce egress rules.
// This snippet illustrates the concept; full implementation requires custom connector logic.

For production, consider deploying egress controls at the platform level (network policies, service mesh) so that even misconfigured clients cannot reach unintended endpoints. Combine this with runtime scanning (such as MiddleBrick) to detect SSRF and authentication misconfigurations across your API surface.

Related CWEs: ssrf

CWE IDNameSeverity
CWE-918Server-Side Request Forgery (SSRF) CRITICAL
CWE-441Unintended Proxy or Intermediary (Confused Deputy) HIGH

Frequently Asked Questions

Does mTLS prevent SSRF in Axum APIs?
No. mTLS secures inbound client-to-server traffic but does not restrict outbound requests from the server. SSRF mitigation requires explicit allowlisting of hosts and network-level egress controls for outbound HTTP clients.
How does MiddleBrick detect SSRF in Axum services with mTLS?
MiddleBrick tests unauthenticated attack surfaces and maps findings to OWASP API Top 10. It identifies whether outbound paths are constrained and whether server trust boundaries inadvertently enable SSRF, independent of inbound mTLS.