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-Tokenor 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-Cookieheaders for session cookies. MissingSameSite=StrictorLaxattributes 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.comThe 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.