Ssrf Server Side in Axum with Mutual Tls
Ssrf Server Side in Axum with Mutual Tls
Server-side request forgery (SSRF) in an Axum service that uses mutual TLS (mTLS) can occur when the server acts as an HTTP client and makes outbound requests to user-supplied URLs. Even though mTLS ensures that both client and server present valid certificates, it does not restrict where the server is allowed to connect. An attacker can supply a URL such as https://169.169.169.254/latest/meta-data/iam/security-credentials/ on a backend that forwards requests, leading to metadata exposure from cloud providers or to internal services that are not exposed on the public internet.
In Axum, if you build an endpoint that accepts a target URL and performs an HTTP call using a client that also presents a client certificate, mTLS is enforced for outbound connections, but the application logic determines the destination. Without validating or restricting destinations, the server can be tricked into connecting to internal endpoints, including the instance metadata service, Kubernetes API, or other sensitive internal APIs. The presence of mTLS may give a false sense of security, because it only authenticates the server to its outbound peer, not the peer’s identity in relation to internal networks.
SSRF can also combine with other risks. For example, if the Axum server follows redirects automatically, an attacker can chain an open redirect or an internal-only endpoint with a different network path. Tools like middleBrick can detect SSRF patterns during scans by analyzing the unauthenticated attack surface and the OpenAPI specification, cross-referencing definitions with runtime behavior to highlight risky endpoint behaviors. This helps identify whether user input directly influences the target of outbound requests without sufficient allowlisting or network segregation.
Remediation guidance focuses on destination validation and network controls. Implement strict allowlists for hostnames and IPs, disable redirect following unless explicitly required, and avoid forwarding requests to user-supplied targets. If metadata or internal services must be accessed, use environment-bound identities or service mesh policies rather than relying on mTLS alone. Leverage features like the middleBrick Pro plan for continuous monitoring that can alert on configuration drift or risky changes to routing logic.
Mutual Tls-Specific Remediation in Axum
To securely implement mutual TLS in Axum, you need to configure both the server and the client with certificates and private keys, and enforce certificate verification on the client side for outbound calls. Below are concrete, working examples using reqwest with native-tls or rustls, depending on your runtime requirements.
Example: Axum server with mTLS termination
An Axum server can require client certificates by configuring the TLS acceptor. This ensures that only clients presenting a valid certificate signed by your CA can establish a connection.
use axum::Server;
use axum::routing::get;
use std::net::SocketAddr;
use tokio_rustls::TlsAcceptor;
use std::sync::Arc;
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::io::BufReader;
use tokio::fs::File;
async fn build_axum_server() -> Result<(), Box> {
let certs = load_certs("ca.crt").await?;
let mut keys = load_private_keys("server.key").await?;
let cert_auth = Arc::new(
rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() // initially none; change to require_and_verify_client_cert()
.with_single_cert(certs, keys.remove(0))?
);
// To require client certs:
let mut cert_auth_with_client_auth = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_client_auth_verifier(
// Provide a verifier that checks client certs against your CA
Arc::new(|certs| {
// Simplified: in production, validate against your CA pool
if certs.is_empty() { return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "no client cert")); }
Ok(())
})
)
.with_single_cert(certs, keys.remove(0))?;
cert_auth_with_client_auth.client_auth_mandatory = true;
cert_auth_with_client_auth.client_auth_root_subjects = /* your CA roots */;
let tls_acceptor = TlsAcceptor::from(Arc::new(cert_auth_with_client_auth));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let app = get(|| async { "mTLS secured Axum server" });
Server::bind(&addr)
.tls_config(tls_acceptor)?
.serve(app.into_make_service())
.await?;
Ok(())
}
async fn load_certs(path: &str) -> Result, Box> {
let f = BufReader::new(File::open(path).await?);
let mut certs = vec![];
for cert in certs(f).flatten() {
certs.push(rustls::Certificate(cert));
}
Ok(certs)
}
async fn load_private_keys(path: &str) -> Result, Box> {
let f = BufReader::new(File::open(path).await?);
let mut keys = vec![];
for key in pkcs8_private_keys(f).flatten() {
keys.push(rustls::PrivateKey(key));
}
Ok(keys)
}
Example: Axum client making outbound mTLS requests
When your Axum service calls external APIs, configure the HTTP client to present a client certificate and verify the server’s certificate. This ensures mutual authentication for outbound calls.
use reqwest::Client;
use std::fs;
async fn build_mtls_client() -> Result> {
let cert = fs::read("client.crt")?;
let key = fs::read("client.key")?;
let ca = fs::read("ca.crt")?;
let certs = vec![rustls::Certificate(cert)];
let mut keys = vec![rustls::PrivateKey(key)];
let identity = rustls::Identity::from_pkcs8(&certs, &keys)?;
let client_cert = reqwest::Identity::from_pkcs8_pem(&std::str::from_utf8(&fs::read("client.pem")?)?)?;
let client = Client::builder()
.use_rustls_tls()
.add_root_certificate(reqwest::Certificate::from_pem(&ca)?)
.identity(client_cert)
.build()?;
Ok(client)
}
async fn call_protected_service(client: &Client, url: &str) -> Result> {
let resp = client.get(url).send().await?;
let body = resp.text().await?;
Ok(body)
}
By combining these patterns, you ensure that both inbound and outbound communication in Axum uses mutual TLS. Note that mTLS does not replace input validation or allowlisting for SSRF; you still need to control where the server is permitted to connect.
For ongoing security, middleBrick’s CLI can scan your endpoints from the terminal with middlebrick scan <url>, while the GitHub Action can add API security checks to your CI/CD pipeline to fail builds if risk scores drop below your chosen threshold.