MEDIUM clickjackingaxumrust

Clickjacking in Axum (Rust)

Clickjacking in Axum with Rust — how this specific combination creates or exposes the vulnerability

Clickjacking is a client-side vulnerability that relies on tricking a user into interacting with a hidden or disguised UI element inside an iframe. When an API or web app served by Axum with Rust does not enforce frame-embedding restrictions, an attacker can embed the target page in an invisible iframe and overlay interactive controls, leading to unauthorized actions (CSRF-like behavior) or UI redress attacks.

In Axum, responses are composed programmatically using handlers and middleware. If a developer does not explicitly set frame-embedding protections, the application may send responses that browsers allow to be framed. This is especially risky when sensitive endpoints (for example, a fund-transfer POST or a settings update) are rendered in the browser and can be overlaid by malicious content. The risk is not inherent to Rust or Axum but arises when security headers are omitted in the response construction logic.

Because Axum is commonly used to build APIs that also serve HTML or are consumed by web frontends, misconfigured CORS and missing anti-framing headers can expose endpoints to clickjacking. For instance, an endpoint that returns an HTML page with sensitive actions but lacks X-Frame-Options or Content-Security-Policy headers may be embedded by an attacker. In Rust, the absence of these headers in the response builder makes the application surface vulnerable to clickjacking despite the language’s safety guarantees.

Consider an Axum handler that returns an HTML page containing a state-changing form. If the handler does not add protective headers, a browser will honor framing directives from other origins. Attackers can craft a page that loads the target route inside a 1x1 pixel iframe and overlay buttons or inputs on top, capturing user interactions. This is a real-world attack pattern observed across web frameworks when security headers are not enforced consistently.

Unlike some higher-level frameworks that set safe defaults, Axum requires developers to explicitly add headers. This places the responsibility on the Rust developer to ensure that every response that renders UI or performs sensitive actions includes anti-framing protections. The use of strongly typed responses in Rust does not automatically prevent clickjacking; it is the headers that instruct browsers on whether a page can be framed.

Rust-Specific Remediation in Axum — concrete code fixes

To remediate clickjacking in Axum with Rust, explicitly set security headers on responses that can be rendered in a browser. The most direct mitigation is to add X-Frame-Options to deny framing entirely, or use Content-Security-Policy frame-ancestors directives for fine-grained control. Below are concrete, working Axum examples that demonstrate how to apply these protections.

1. Global middleware approach

Apply headers to all responses using Axum middleware. This ensures that every route benefits from frame protection without forgetting to add headers on a per-handler basis.

use axum::{
    async_trait,
    extract::Extension,
    http::{HeaderMap, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
    middleware::Next,
};
use std::convert::Infallible;

/// Middleware that adds security headers to all responses.
pub async fn security_headers_layer(
    Extension(_): Extension<()>,
    request: axum::extract::Request,
    next: Next,
) -> impl IntoResponse {
    let mut response = next.run(request).await;
    // Prevent framing by any page
    response.headers_mut().insert(
        "X-Frame-Options",
        HeaderValue::from_static("DENY"),
    );
    // Content-Security-Policy frame-ancestors none; modern alternative
    response.headers_mut().insert(
        "Content-Security-Policy",
        HeaderValue::from_static("frame-ancestors 'none'"),
    );
    response
}

/// Example usage in a router.
/// let app = Router::new()
///     .route("/action", post(handler))
///     .layer(Layer::new(security_headers_layer));

2. Per-handler header injection

If you prefer to add headers only for specific routes, inject them directly in the handler using IntoResponse implementations.

use axum::{
    async_trait,
    http::{
        header::{HeaderMap, HeaderValue},
        Response as HttpResponse,
    },
    response::{IntoResponse, Response},
    routing::get,
    Router,
};
use std::net::SocketAddr;
use tower_http::services::ServeDir;

async fn handler_with_headers() -> impl IntoResponse {
    let mut response = HttpResponse::builder()
        .status(StatusCode::OK)
        .header("X-Frame-Options", "DENY")
        .header("Content-Security-Policy", "frame-ancestors 'none'")
        .body("Secure page that cannot be framed");
    response
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/secure", get(handler_with_headers));
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

3. Combining with other security headers

Clickjacking protection is most effective when combined with a strong Content-Security-Policy and other headers. Ensure your responses include a robust CSP and avoid overly permissive directives.

use axum::http::HeaderValue;

fn add_frame_protection(headers: &mut HeaderMap) {
    headers.insert(
        "X-Frame-Options",
        HeaderValue::from_static("SAMEORIGIN"), // or "DENY"
    );
    headers.insert(
        "Content-Security-Policy",
        HeaderValue::from_static(
            "default-src 'self'; frame-ancestors 'none'; object-src 'none'"
        ),
    );
}

By explicitly setting these headers in Axum handlers or middleware, Rust developers can prevent clickjacking attacks while maintaining type safety and performance. Remember that headers must be present on every response that could be targeted; middleware is the most reliable way to enforce this consistently across an application.

Frequently Asked Questions

Does using Rust prevent clickjacking?
No. Rust does not prevent clickjacking; browser behavior is governed by HTTP headers such as X-Frame-Options and Content-Security-Policy. You must explicitly set these headers in Axum responses to protect against framing.
Should I use X-Frame-Options or Content-Security-Policy frame-ancestors?
Use both for defense in depth. X-Frame-Options provides broad compatibility with older browsers, while Content-Security-Policy frame-ancestors offers finer control. For example, set X-Frame-Options to DENY and Content-Security-Policy to frame-ancestors 'none'.