Clickjacking in Axum
How Clickjacking Manifests in Axum
Clickjacking in Axum applications occurs when malicious sites embed your API endpoints or web interfaces in invisible iframes, tricking users into performing unintended actions. Unlike traditional web applications where clickjacking primarily affects HTML pages, Axum's API-first architecture creates unique vulnerabilities.
The most common attack pattern involves embedding an Axum endpoint that performs state-changing operations. Consider an API endpoint that processes financial transactions:
async fn transfer_funds(
Path((from_account, to_account, amount)): Path<(u64, u64, f64)>
) -> impl IntoResponse {
// Process transfer
HttpResponse::Ok().finish()
}A malicious site can embed this endpoint in an iframe with zero dimensions, overlaying invisible buttons on top of legitimate UI elements. When users believe they're clicking a "Submit" button on the attacker's page, they're actually triggering the Axum endpoint to transfer funds.
Another Axum-specific scenario involves WebSocket endpoints. Axum's WebSocket support allows real-time communication, but if authentication is handled solely through cookies without additional verification, clickjacking can lead to unauthorized WebSocket connections:
async fn websocket_endpoint(
ws: WebSocket,
data: web::Data<AppState>
) -> impl IntoResponse {
// WebSocket upgrade logic
}Attackers can embed this endpoint and establish WebSocket connections under the victim's authenticated context, potentially accessing sensitive real-time data streams.
API endpoints that return sensitive data are also vulnerable. An endpoint that exposes user information without proper anti-clickjacking headers can be embedded to scrape data:
async fn get_user_data(
Path(user_id): Path<u64>
) -> Result<Json<UserData>> {
// Return user data
}The attacker's site can repeatedly embed this endpoint to harvest data from authenticated users who have active sessions with your Axum application.
Axum-Specific Detection
Detecting clickjacking vulnerabilities in Axum requires examining both your route definitions and response headers. The most reliable detection method is using middleBrick's API security scanner, which specifically tests for clickjacking vulnerabilities in Rust web applications.
middleBrick scans your Axum endpoints for missing X-Frame-Options headers and Content-Security-Policy frame-ancestors directives. The scanner sends requests to your API endpoints and analyzes the response headers to identify clickjacking vulnerabilities. Here's how to scan with middleBrick:
npx middlebrick scan https://your-axum-app.com/api/transfer
# Or use the CLI tool
middlebrick scan https://your-axum-app.com/api/transferThe scanner tests multiple attack scenarios: embedding endpoints in iframes, testing for cross-origin frame access, and verifying that sensitive endpoints cannot be framed. It provides a security score and specific findings about clickjacking vulnerabilities.
Manual detection involves checking your Axum route handlers for proper header configuration. Use middleware to inspect responses:
use axum::middleware::Next;
use axum::response::IntoResponse;
use axum::http::HeaderMap;
async fn clickjacking_middleware(
mut req: axum::http::Request<axum::body::Body>,
next: Next,
) -> impl IntoResponse {
let mut response = next.run(req).await;
// Check if X-Frame-Options header exists
if !response.headers().contains_key("x-frame-options") {
// Vulnerability found - header missing
}
response
}Run this middleware on your endpoints and verify that all responses include appropriate clickjacking protection headers. Pay special attention to endpoints that handle authentication, financial operations, or return sensitive data.
Axum-Specific Remediation
Remediating clickjacking vulnerabilities in Axum requires implementing proper HTTP headers and architectural safeguards. The most effective approach uses Axum's middleware system to enforce clickjacking protection across all endpoints.
First, implement the X-Frame-Options header using Axum middleware:
use axum::middleware::Next;
use axum::response::IntoResponse;
use axum::http::{HeaderMap, HeaderName, HeaderValue};
async fn clickjacking_protection(
mut req: axum::http::Request<axum::body::Body>,
next: Next,
) -> impl IntoResponse {
let mut response = next.run(req).await;
// Add X-Frame-Options: DENY to prevent framing
response.headers_mut().insert(
"x-frame-options",
HeaderValue::from_static("DENY")
);
response
}
let app = Router::new()
.route("/api/transfer", post(transfer_funds))
.layer(axum::middleware::from_fn(clickjacking_protection));For more granular control, use Content-Security-Policy with frame-ancestors directive:
async fn csp_protection(
mut req: axum::http::Request<axum::body::Body>,
next: Next,
) -> impl IntoResponse {
let mut response = next.run(req).await;
response.headers_mut().insert(
"content-security-policy",
HeaderValue::from_static("frame-ancestors 'none'")
);
response
}For endpoints that legitimately need to be framed (like embedded widgets), use more specific policies:
response.headers_mut().insert(
"content-security-policy",
HeaderValue::from_static("frame-ancestors 'self' https://trusted.com")
);Implement route-specific protection for sensitive operations:
async fn protected_transfer(
Path((from_account, to_account, amount)): Path<(u64, u64, f64)>
) -> impl IntoResponse {
// Process transfer with additional anti-CSRF measures
HttpResponse::Ok().finish()
}
let app = Router::new()
.route("/api/transfer", post(protected_transfer))
.layer(
axum::middleware::from_fn(clickjacking_protection)
.and_then(axum::middleware::from_fn(csp_protection))
);Combine clickjacking protection with CSRF tokens for maximum security. Axum's middleware chain allows stacking multiple security layers:
let security_middleware = clickjacking_protection
.and_then(csp_protection)
.and_then(csrf_protection);
let app = Router::new()
.route(...)
.layer(security_middleware);Test your remediation using middleBrick to verify that clickjacking protections are properly implemented across all endpoints.