Bleichenbacher Attack in Axum with Firestore
Bleichenbacher Attack in Axum with Firestore — how this specific combination creates or exposes the vulnerability
A Bleichenbacher attack is a cryptographic padding oracle technique that can recover plaintext (e.g., session tokens or API keys) by iteratively querying an endpoint that leaks information about the validity of a ciphertext’s padding. In an Axum service that uses Firestore as a backing store for encrypted or signed tokens, the vulnerability arises when error handling distinguishes between a padding failure and other failures, or when Firestore queries expose timing or response differences that an attacker can observe.
Consider an Axum endpoint that accepts an encrypted cookie or Authorization header, decrypts or verifies it, and then uses Firestore to look up associated user or session data. If the service returns different HTTP status codes or response bodies for invalid padding versus valid-but-wrong tokens, and if Firestore read patterns or timing are influenced by record existence, the service may act as a padding oracle. For example, an attacker can supply modified ciphertexts and observe whether the service returns 401 versus 404, or whether Firestore queries succeed, thereby inferring padding validity bit by bit.
With Firestore, subtle timing differences can emerge based on whether a document exists or whether index lookups complete differently. If Axum routes conditionally perform Firestore gets or queries only after successful decryption/verification, the observable behavior changes based on internal state. This can allow an attacker to correlate response characteristics with padding correctness across many requests. Such an oracle can be exploited to decrypt or forge tokens that the service relies on for authentication or authorization, effectively bypassing intended access controls.
In practice, this means an Axum endpoint that accepts encrypted JWTs or custom encrypted blobs, decrypts them using a scheme with PKCS7 padding, and then uses Firestore to fetch user records based on a subject identifier extracted only after successful decryption, can be vulnerable. The decryption routine must avoid branching on secret-dependent data and must ensure constant-time behavior regardless of Firestore query outcomes. Any discrepancy in status codes, latency, or Firestore access patterns can be leveraged by an attacker to mount a Bleichenbacher-style padding oracle against the system.
Firestore-Specific Remediation in Axum — concrete code fixes
To mitigate a Bleichenbacher attack in an Axum service that integrates with Firestore, ensure that decryption, verification, and Firestore access patterns do not leak information via timing or error responses. Always perform constant-time operations where possible and avoid branching on secret-dependent values. Use a single, generic error path for all failures (invalid token, padding error, Firestore miss, etc.) and enforce consistent response characteristics.
Below are concrete Rust examples using the Firestore SDK for Rust and Axum. The first example shows a vulnerable pattern that should be avoided:
// ❌ Vulnerable: branching on Firestore result and exposing status
use axum::{routing::get, Router};
use firestore::FirestoreDb;
async fn get_user(
token: String,
db: firestore::FirestoreDb,
) -> Result {
// Decrypt token — in real code use a constant-time verify/decrypt
let payload = decrypt_token(&token)?; // may return Err on bad padding
// Firestore lookup only after successful decryption — timing leak
let user = db.get::(&payload.user_id).await?; // Firestore may return None
match user {
Some(u) => Ok(u),
None => Err((axum::http::StatusCode::NOT_FOUND, "not found".into())),
}
}
The above leaks information via both error types (decrypt failure vs Firestore miss) and timing (Firestore lookups only occur after decryption). An attacker can use this to perform a padding oracle.
A remediated approach performs a dummy Firestore-safe verification path and uses a single, consistent response:
// ✅ Remediated: constant-time style handling and uniform response
use axum::{routing::get, Router};
use firestore::FirestoreDb;
use std::time::Duration;
async fn get_user_safe(
token: String,
db: firestore::FirestoreDb,
) -> Result {
// Use a constant-time verification/decrypt routine that does not branch on secrets
let verified = verify_token_constant_time(&token).map_err(|_| {
// Always return the same status and generic message
(axum::http::StatusCode::UNAUTHORIZED, "invalid token".into())
})?;
// Perform a dummy query to mask timing differences; avoid branching on result
let _dummy = db.collection("users").limit(1).get().await;
// Fetch the actual document only after verified, but ensure Firestore call always occurs
let user_id = verified.user_id;
let result = db.collection("users").doc(user_id).get().await;
match result {
Ok(Some(doc)) => Ok(doc),
_ => {
// Even when missing, return the same status as invalid token to avoid leaks
(axum::http::StatusCode::UNAUTHORIZED, "invalid token".into())
}
}
}
// Placeholder: implement using cryptographic APIs that avoid secret-dependent branches
fn verify_token_constant_time(token: &str) -> Result {
// Use libraries that provide constant-time verification (e.g., subtle crate)
// Do not use simple == comparisons on secrets
unimplemented!()
}
Additionally, prefer using environment-controlled retry and timeout settings to reduce timing variance, and avoid exposing Firestore-specific errors to the client. Combine this with Axum middleware that normalizes error responses to ensure uniform status codes and body shapes for all failure cases.