Session Fixation in Axum
How Session Fixation Manifests in Axum
Session fixation in Axum applications typically occurs when developers fail to properly rotate session identifiers after authentication. This vulnerability allows an attacker to pre-generate a session ID, trick a victim into using it, and then gain access once the victim authenticates.
In Axum, the most common manifestation involves middleware that doesn't properly handle session state transitions. Consider this vulnerable pattern:
use axum::extract::{State, Session};
use axum_sessions::SessionExt;
async fn login(
State(app_state): State,
session: Session,
Json(credentials): Json,
) -> Result {
// Authenticate user
let user = authenticate(credentials).await?;
// VULNERABLE: Session ID remains unchanged
session.insert("user_id", user.id)?;
Ok(Redirect::to("/dashboard"))
} The critical flaw here is that the session ID persists across the authentication boundary. An attacker who knows or can predict the session ID before login gains persistent access after the victim authenticates.
Another Axum-specific pattern involves improper use of signed cookies for session storage:
use axum_sessions::{CookieSessionLayer, SessionExt};
let app = Router::new()
.route("/login", post(login))
.layer(CookieSessionLayer::signed_with_key(ENCRYPTION_KEY))
.with_state(app_state);When using signed cookies without proper session rotation, the same session identifier travels from unauthenticated to authenticated states. Axum's middleware stack doesn't automatically rotate these identifiers, making this a common developer oversight.
Session fixation can also occur through improper middleware ordering. If session middleware executes after authentication checks, an attacker might exploit timing windows where session state is inconsistent:
let app = Router::new()
.route("/protected", get(protected_handler))
.layer(Extension(auth_middleware))
.layer(CookieSessionLayer::signed_with_key(ENCRYPTION_KEY))
.with_state(app_state);This ordering allows potential race conditions where session data might be manipulated between authentication and session establishment.
Axum-Specific Detection
Detecting session fixation in Axum applications requires examining both code patterns and runtime behavior. Start by auditing your middleware stack and session management code.
Static analysis should look for these red flags in your Axum codebase:
// Search for patterns like this:
session.insert("user_id", user.id)?; // No session rotation
// Check for missing session regeneration:
if authenticated {
// Should call session.regenerate() here
middleBrick's black-box scanning approach is particularly effective for Axum applications because it tests the actual runtime behavior without requiring source code access. The scanner sends requests to your Axum endpoints and analyzes session handling across authentication boundaries.
When middleBrick scans an Axum API, it specifically tests for session fixation by:
- Capturing the initial session identifier before authentication
- Authenticating with known credentials
- Verifying whether the session identifier changed post-authentication
- Checking if session data persists across the authentication boundary
The scanner reports findings with severity levels based on exploitability. For Axum applications, middleBrick provides specific remediation guidance including code snippets that demonstrate proper session rotation patterns using Axum's native session middleware.
middleBrick's continuous monitoring feature (Pro plan) can periodically re-scan your Axum APIs to ensure session management remains secure as code evolves. This is crucial because session fixation vulnerabilities often get introduced during feature updates or refactoring.
Axum-Specific Remediation
Properly fixing session fixation in Axum requires using the framework's built-in session rotation capabilities. The most straightforward approach uses axum_sessions' regenerate method:
use axum::extract::{State, Session};
use axum_sessions::SessionExt;
use axum::http::StatusCode;
async fn login(
State(app_state): State<AppState>,
session: Session,
Json(credentials): Json<Credentials>,
) -> Result<Redirect, StatusCode> {
let user = authenticate(credentials).await?;
// CRITICAL: Regenerate session ID before adding authenticated data
let new_session = session.regenerate()?;
new_session.insert("user_id", user.id)?;
Ok(Redirect::to("/dashboard"))
}The regenerate() call creates a new session with a fresh identifier while preserving existing session data. This breaks the link between any pre-authentication session fixation attempt and the authenticated session.
For applications using signed cookies, implement a two-step approach:
use axum_sessions::{CookieSessionLayer, SessionExt};
use axum::extract::{State, Session};
async fn secure_login(
State(app_state): State<AppState>,
session: Session,
Json(credentials): Json<Credentials>,
) -> Result<Redirect, StatusCode> {
let user = authenticate(credentials).await?;
// Rotate session and clear any pre-auth data
let mut new_session = session.regenerate()?;
new_session.clear(); // Remove any pre-auth data
new_session.insert("user_id", user.id)?;
new_session.insert("role", user.role)?;
Ok(Redirect::to("/dashboard"))
}Middleware ordering is also critical for security. Ensure session middleware executes before authentication checks:
let app = Router::new()
.route("/login", post(login))
.route("/logout", post(logout))
.layer(CookieSessionLayer::signed_with_key(ENCRYPTION_KEY))
.layer(Extension(auth_middleware))
.with_state(app_state);This ordering ensures session state is properly established before any authentication logic executes, preventing timing-based fixation attacks.
Finally, implement proper session timeout and invalidation on logout:
async fn logout(session: Session) -> Redirect {
session.destroy().expect("Failed to destroy session");
Redirect::to("/login")
}middleBrick's scanning can verify these fixes by testing session rotation behavior across your entire authentication flow, ensuring no fixation vectors remain exploitable.