Dns Rebinding in Axum
How Dns Rebinding Manifests in Axum
Dns Rebinding attacks exploit the gap between how browsers handle DNS resolution and how servers validate requests. In Axum applications, this vulnerability typically manifests when your API accepts requests from any origin without proper origin validation or when it processes requests that contain internal network references.
The attack works by registering a domain that points to a malicious server. When a victim visits this domain, the server responds with a TTL of 0, forcing the browser to re-resolve the DNS. The attacker then switches the DNS record to point to an internal IP address like 192.168.1.1 or 10.0.0.1. Because browsers maintain open connections, they'll send the same-origin request to the internal IP, bypassing network segmentation.
In Axum, this commonly appears in these patterns:
- Open CORS policies that accept any origin without validation
- Endpoints that process URLs or hostnames without DNS pinning
- Internal API endpoints exposed to the public internet
- WebSocket handlers that accept connections from any origin
Here's a vulnerable Axum endpoint that demonstrates the problem:
use axum::{routing::get, Router, http::Uri, extract::Path};
async fn proxy_url(Path(url): Path) -> String {
// DANGER: No validation of the URL's actual destination
format!("Proxying request to: {}", url)
}
let app = Router::new()
.route("/proxy/:url", get(proxy_url))
.with_cors(axum::http::header::HeaderValue::from_static("*"));
This endpoint accepts any URL and has an open CORS policy. An attacker could use DNS rebinding to make the browser request internal services like http://localhost:8080 or http://192.168.1.1, potentially exposing internal APIs or administrative interfaces.
Another common pattern is WebSocket endpoints:
use axum::{routing::get, Router, extract::ws::WebSocket, ws::Message};
async fn websocket_connection(ws: WebSocket) {
let (mut sender, mut receiver) = ws.split();
while let Some(msg) = receiver.recv().await {
// Process messages without origin validation
sender.send(msg).await.ok();
}
}
let app = Router::new()
.route("/ws", get(websocket_connection))
.with_cors(axum::http::header::HeaderValue::from_static("*"));
Without origin validation, a DNS rebinding attack could connect WebSockets to internal services, potentially accessing databases, message queues, or other internal APIs that expect trusted connections.
Axum-Specific Detection
Detecting DNS rebinding vulnerabilities in Axum applications requires both static analysis of your code and dynamic runtime scanning. middleBrick's API security scanner can identify these issues by testing your endpoints against various attack patterns.
For Axum applications, middleBrick performs these specific checks:
- Origin validation testing: attempts requests with forged origins to see if your CORS policies properly validate
- URL parameter testing: submits URLs pointing to internal IPs and localhost to proxy-like endpoints
- WebSocket origin testing: attempts WebSocket connections from unexpected origins
- Internal network scanning: tests if your API responds to requests targeting RFC1918 addresses
Here's how you'd scan an Axum API with middleBrick:
# Install the CLI tool
npm install -g middlebrick
# Scan your Axum API endpoint
middlebrick scan https://your-axum-api.com/api/v1
# Or integrate into your CI/CD pipeline
middlebrick scan --format json --output report.json https://your-axum-api.com
The scanner will report findings like:
{
"category": "Authentication",
"severity": "high",
"finding": "CORS policy allows all origins without validation",
"remediation": "Implement origin validation middleware to restrict allowed origins",
"impact": "DNS rebinding attacks can access internal services through your API"
}
For WebSocket endpoints, middleBrick tests connection attempts from unexpected origins and validates that your origin validation logic properly handles edge cases like null origins or malformed origin headers.
You can also use middleBrick's GitHub Action to automatically scan your Axum API in your CI/CD pipeline:
name: API Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run middleBrick Scan
run: |
npm install -g middlebrick
middlebrick scan https://staging.your-axum-app.com
continue-on-error: true
- name: Fail on high severity findings
if: failure()
run: |
echo "Security scan failed - please review the findings"
exit 1
This ensures any DNS rebinding vulnerabilities or other security issues are caught before deployment.
Axum-Specific Remediation
Securing Axum applications against DNS rebinding requires implementing proper origin validation, URL sanitization, and network access controls. Here are Axum-specific remediation patterns:
First, implement strict CORS policies with origin validation:
use axum::{routing::get, Router, extract::RequestPartsExt, headers::HeaderMap};
use http::HeaderValue;
async fn validate_origin(mut req: axum::http::Request) -> Result, axum::http::Error> {
let allowed_origins = ["https://yourdomain.com", "https://yourotherdomain.com"];
if let Some(origin) = req.headers().get("origin") {
if let Ok(origin_str) = origin.to_str() {
if !allowed_origins.contains(&origin_str) {
return Err(axum::http::Error::new(
axum::http::StatusCode::FORBIDDEN,
"Origin not allowed"
));
}
}
}
Ok(req)
}
let app = Router::new()
.route("/api/data", get(data_handler))
.layer(axum::middleware::from_fn(validate_origin));
For URL proxy endpoints, implement strict validation and DNS pinning:
use axum::{routing::get, Router, http::Uri, extract::Path};
use url::Url;
use std::net::IpAddr;
async fn safe_proxy(Path(url_str): Path) -> Result {
// Parse and validate the URL
let url = match Url::parse(&url_str) {
Ok(u) if u.scheme() == "http" || u.scheme() == "https" => u,
_ => return Err(axum::http::Error::new(
axum::http::StatusCode::BAD_REQUEST,
"Invalid URL scheme"
))
};
// DNS pinning - resolve and validate the IP
let host = url.host_str().unwrap();
let ip_addrs = match tokio::net::lookup_host(host).await {
Ok(ips) => ips,
Err(_) => return Err(axum::http::Error::new(
axum::http::StatusCode::BAD_REQUEST,
"DNS resolution failed"
))
};
// Block internal IPs
for ip in ip_addrs {
if is_internal_ip(ip) {
return Err(axum::http::Error::new(
axum::http::StatusCode::FORBIDDEN,
"Internal IP addresses are not allowed"
));
}
}
// Store the resolved IP for the session duration
// (implementation would store this in a session store)
Ok(format!("Proxying to validated URL: {}", url_str))
}
fn is_internal_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(addr) => {
addr.is_private() ||
addr.is_loopback() ||
addr.is_link_local() ||
addr.octets()[0] == 10 || // 10.x.x.x
(addr.octets()[0] == 192 && addr.octets()[1] == 168) // 192.168.x.x
}
IpAddr::V6(addr) => addr.is_private() || addr.is_loopback()
}
}
For WebSocket endpoints, implement origin validation:
use axum::{routing::get, Router, extract::ws::WebSocket, ws::Message, extract::RequestPartsExt};
use http::HeaderValue;
async fn validated_websocket(ws: WebSocket) {
let (mut sender, mut receiver) = ws.split();
// Origin validation
if let Some(origin) = ws.request().headers().get("origin") {
if let Ok(origin_str) = origin.to_str() {
if !is_allowed_origin(origin_str) {
return; // Drop the connection
}
}
}
while let Some(msg) = receiver.recv().await {
sender.send(msg).await.ok();
}
}
fn is_allowed_origin(origin: &str) -> bool {
// Your validation logic here
origin.starts_with("https://yourdomain.com")
}
let app = Router::new()
.route("/ws", get(validated_websocket));
These patterns prevent DNS rebinding attacks by ensuring that URLs resolve to expected external IPs and that only trusted origins can access your WebSocket endpoints.