HIGH cross site request forgeryaxum

Cross Site Request Forgery in Axum

How Cross Site Request Forgery Manifests in Axum

Cross-Site Request Forgery (CSRF) in Axum applications exploits the trust a server has in a user's browser by tricking a logged-in user's browser into making an unintended state-changing request. In Axum, which is a Rust framework built on Tokio and Tower, CSRF vulnerabilities typically arise from two patterns: (1) endpoints that rely solely on session cookies or HTTP authentication for identity without verifying request origin, and (2) missing or misconfigured CSRF protection middleware in the request pipeline.

A common vulnerable pattern in Axum is a state-changing POST handler that does not validate a CSRF token. For example, an account deletion endpoint might look like this:

use axum::{
    routing::post,
    Router,
    extract::State,
    response::Response,
};
use std::sync::Arc;

struct AppState {
    // ... shared state
}

async fn delete_account(State(state): State>) -> Response {
    // Logic to delete the user's account
    // No CSRF token validation here
    Response::new("Account deleted".into())
}

let app = Router::new()
    .route("/account/delete", post(delete_account))
    .with_state(Arc::new(AppState {}));

Because Axum does not enforce CSRF protection by default, any endpoint that performs actions based on cookie-based authentication is potentially vulnerable if an attacker can induce a user's browser to send a forged request. This is particularly risky for APIs that are also used by web browsers, as the SameSite cookie attribute alone is insufficient protection against CSRF in many cross-origin scenarios (e.g., when subdomains or older browsers are involved).

Another manifestation occurs when developers attempt custom CSRF checks but implement them incorrectly. For instance, checking only the Referer header is unreliable because it can be stripped by privacy tools or proxies. In Axum, a flawed custom extractor might look like:

use axum::{
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
};

struct CsrfCheck;

impl FromRequestParts for CsrfCheck {
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result {
        let referer = parts
            .headers
            .get("referer")
            .ok_or((StatusCode::BAD_REQUEST, "Missing Referer"))?;
        // Naive check: assume same origin if referer contains our domain
        if referer.to_str().map(|s| s.contains("example.com")).unwrap_or(false) {
            Ok(CsrfCheck)
        } else {
            Err((StatusCode::FORBIDDEN, "Invalid origin"))
        }
    }
}

This bypasses CSRF protection if the attacker can spoof or omit the Referer header, which is feasible in many attack vectors (e.g., from HTTPS to HTTP, or via meta tags). The correct approach in Axum is to use a cryptographically strong, per-session token that is submitted via a custom header or hidden form field and validated server-side.

Axum-Specific Detection with middleBrick

middleBrick identifies CSRF vulnerabilities in Axum APIs through black-box analysis of runtime behavior and specification deviations. The scanner tests for missing or weak CSRF defenses by submitting crafted state-changing requests (e.g., POST, PUT, DELETE) and analyzing responses under different conditions:

  • Token Presence Check: middleBrick first parses any provided OpenAPI/Swagger spec to identify state-changing operations. For each endpoint, it attempts a request without a CSRF token (if cookie-based auth is detected) and observes if the request succeeds. A 2xx/3xx response indicates potential CSRF risk.
  • Token Validation Check: If a token is present (e.g., in a header like X-CSRF-Token or form field), middleBrick submits an invalid/mismatched token to verify whether the server rejects it properly. Accepting an invalid token signals a broken CSRF implementation.
  • SameSite Cookie Analysis: The scanner examines Set-Cookie headers for session cookies. Missing SameSite=Strict or Lax attributes raises the risk score, as this weakens browser-enforced origin restrictions.
  • CORS and Credentialed Requests: middleBrick tests if the API allows credentialed cross-origin requests (via Access-Control-Allow-Credentials: true) without adequate CSRF tokens, which expands the attack surface.

For Axum applications, middleBrick's findings are mapped to OWASP API Security Top 10 category API1:2023 – Broken Object Level Authorization when CSRF leads to unauthorized actions, or API5:2023 – Broken Function Level Authorization if the endpoint itself should require additional verification. The report includes a severity rating (critical/high/medium/low) based on the exploitability and impact of the CSRF flaw, along with the specific Axum route and HTTP method affected.

Example middleBrick output snippet for a vulnerable Axum endpoint:

{
  "risk_score": 65,
  "grade": "D",
  "findings": [
    {
      "category": "Input Validation",
      "severity": "high",
      "title": "CSRF Protection Missing",
      "endpoint": "POST /api/v1/transfer",
      "detail": "State-changing endpoint accepts requests without CSRF token. Session cookie accepted, no token validation observed.",
      "remediation": "Implement CSRF token validation middleware (e.g., tower-csrf) and require tokens for all state-changing operations."
    }
  ]
}

Scanning an Axum API is straightforward: submit the base URL to the middleBrick web dashboard, or use the CLI:

middlebrick scan https://api.example.com

The CLI returns JSON that can be integrated into CI/CD pipelines via the middleBrick GitHub Action, allowing you to fail builds if CSRF risks exceed a threshold.

Axum-Specific Remediation with Native Features

Remediating CSRF in Axum involves integrating a proven CSRF middleware into the request pipeline and ensuring all state-changing endpoints are protected. The recommended approach uses the tower-csrf crate, which is compatible with Axum's middleware system and provides robust token generation and validation.

Step 1: Add Dependencies

[dependencies]
axum = "0.7"
tower-csrf = "0.4"
tower = "0.4"
# For cookie-based token storage (optional but common)
tower-sessions = "0.11"

Step 2: Configure CSRF Middleware

For APIs that serve both browser-based clients and programmatic access, you typically want CSRF protection only for browser-originated requests. tower-csrf allows you to define a CsrfLayer with a custom AcceptPredicate that checks the Origin or Referer header. However, the most secure method is to require CSRF tokens for all state-changing requests regardless of origin, and have clients (including SPAs) fetch and submit tokens.

use axum::{
    Router,
    routing::post,
    response::Response,
    middleware,
};
use tower_csrf::{CsrfLayer, CsrfToken, Randomize, CsrfProtect};
use tower_sessions::{Expiry, SessionManagerLayer};
use tower_sessions_sqlite_store::SqliteStore;
use std::time::Duration;

#[tokio::main]
async fn main() {
    // Set up session storage (SQLite example)
    let store = SqliteStore::new("sqlite.db").await.unwrap();
    let session_layer = SessionManagerLayer::new(store)
        .with_expiry(Expiry::OnInactivity(Duration::days(7)));

    // Set up CSRF layer with a random token generator
    let csrf_layer = CsrfLayer::new(Randomize::new())
        // Optionally, restrict to browsers only (less secure)
        // .with_accept_policy(tower_csrf::AcceptPredicate::Browser)
        ;

    let app = Router::new()
        .route("/account/delete", post(delete_account))
        .layer(session_layer)
        .layer(csrf_layer)
        .layer(middleware::from_fn(auth_middleware));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

// Handler now requires a valid CSRF token
async fn delete_account(
    CsrfToken(token): CsrfToken,
) -> Response {
    // token is validated by middleware before reaching handler
    // Proceed with deletion
    Response::new("Account deleted".into())
}

Step 3: Client-Side Token Submission

For browser-based clients, the CSRF token must be included in state-changing requests. Typically, the token is embedded in HTML forms or made available via a dedicated endpoint (e.g., GET /csrf-token) for JavaScript clients. Axum can expose the token like this:

use axum::{
    extract::Extension,
    Json,
};
use tower_csrf::CsrfToken;

async fn get_csrf_token(
    Extension(csrf_token): Extension,
) -> Json<serde_json::Value> {
    Json(serde_json::json!({ "token": csrf_token.0 }))
}

Important Considerations for Axum:

  • Stateless APIs: If your Axum API is entirely stateless (no sessions), CSRF is less of a concern because there is no session cookie to forge. However, if you use cookie-based auth (even with JWT in cookies), CSRF protection is required.
  • API Keys/Token Auth: For APIs using Authorization headers (Bearer tokens), CSRF is not applicable because browsers cannot automatically attach custom headers cross-origin without CORS preflight approval. Still, ensure CORS is configured correctly.
  • Double-Submit Cookie Pattern: If you cannot use server-side sessions, consider the double-submit cookie pattern: set a CSRF token in a cookie (csrf_token) and require the client to send the same token in a custom header (X-CSRF-Token). The server verifies they match. This can be implemented in Axum with custom middleware.

After remediation, re-scan with middleBrick to confirm the CSRF finding is resolved and the risk score improves.

Frequently Asked Questions

Why is CSRF a risk in Axum if it's a Rust backend framework?
CSRF exploits browser behavior, not server language. If your Axum API uses cookie-based authentication and is accessed by browsers (e.g., via a web frontend), an attacker can trick a user's browser into sending authenticated requests to your Axum endpoints. The vulnerability exists in the interaction between the browser and your API, regardless of Rust's memory safety.
Can middleBrick detect CSRF in an Axum API that requires authentication?
Yes. middleBrick's black-box scanner does not need credentials. It tests unauthenticated attack surfaces first. For CSRF, it observes if state-changing endpoints (like POST /transfer) accept requests without a CSRF token when a session cookie is present (if the API sets cookies). It may also detect weak token validation by submitting mismatched tokens. However, if the API requires authentication for all endpoints and middleBrick cannot obtain a valid session cookie, CSRF detection may be limited. Use the free tier to test your public endpoints.