Padding Oracle in Axum with Firestore
Padding Oracle in Axum with Firestore — how this specific combination creates or exposes the vulnerability
A padding oracle in the context of Axum applications that use Firestore typically arises when an API endpoint accepts encrypted input (for example, a token or a serialized object), passes it to Axum for deserialization or decryption, and reveals distinct error behaviors depending on whether the padding is valid. Firestore does not perform decryption or padding validation itself, but it often stores encrypted blobs, ciphertexts, or structured data that an Axum service retrieves and processes. If Axum-based code uses a cryptographic primitive with padding (such as PKCS#7 in AES-CBC) and returns different HTTP status codes or response messages for padding errors versus other failures, an attacker can iteratively submit manipulated ciphertexts and learn whether padding is correct based on the observable response. This iterative feedback loop is the padding oracle.
In this specific combination, Firestore may serve as the persistence layer for sensitive payloads that are encrypted before being stored and decrypted after retrieval. For example, an Axum handler might fetch a document by ID from a Firestore collection, extract a ciphertext field, and attempt to decrypt it using a key and a chosen mode that requires padding validation. If the error handling in Axum leaks information about padding validity—such as returning a 400 with a message like “invalid padding” versus a generic “bad request”—the endpoint becomes a padding oracle. An attacker who can influence the ciphertext (for example, by modifying a token stored in Firestore or by observing and manipulating an API request that includes an encrypted parameter) can exploit this behavior to gradually decrypt data or recover plaintext without knowing the key.
The risk is compounded when the Firestore document contains sensitive fields and the Axum service performs per-request decryption in an unauthenticated or low-privilege context, aligning with common API security findings such as BOLA/IDOR and Improper Authorization. Even though Firestore enforces its own security rules, the Axum application must enforce strict input validation and consistent error handling. If the Axum route does not validate the structure or integrity of the decrypted data (for example, by verifying an authentication tag or using an AEAD mode) before interpreting padding, the padding oracle becomes reachable. Attack patterns like ciphertext manipulation, bit-flipping, and adaptive chosen-ciphertext attacks become practical when the oracle is available over HTTP, and findings from a middleBrick scan would surface this as a high-severity issue tied to input validation and authentication concerns.
Firestore-Specific Remediation in Axum — concrete code fixes
To remediate padding oracle risks in an Axum service that interacts with Firestore, ensure that decryption never exposes granular error information and that cryptographic operations use authenticated encryption. The following Axum handler demonstrates a secure approach: retrieve a document from Firestore, decrypt its contents using AES-GCM (an AEAD mode that does not require padding), and return a consistent response regardless of decryption failures.
use axum::{routing::get, Router, response::IntoResponse};
use firestore::FirestoreDb;
use std::net::SocketAddr;
use aes_gcm::{Aes256Gcm, Key, Nonce, aead::{Aead, KeyInit, generic_array::GenericArray}};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct SecureRecord {
id: String,
ciphertext_b64: String,
nonce_b64: String,
}
async fn get_secret(
Path(id): Path,
db: State,
) -> impl IntoResponse {
// Fetch document from Firestore
let doc: SecureRecord = match db.collection("secrets").get_doc(&id).await {
Ok(doc) => doc,
Err(_) => return (&[("content-type", "application/json")], serde_json::json!({ "error": "not_found" })),
};
// Decode base64 fields
let key = Key::from_slice(b"an example very very secret key!!"); // 32 bytes for AES-256
let cipher = Aes256Gcm::new(key);
let nonce_bytes = match base64::decode(&doc.nonce_b64) {
Ok(n) => n,
Err(_) => return (&[("content-type", "application/json")], serde_json::json!({ "error": "invalid_data" })),
};
let ciphertext = match base64::decode(&doc.ciphertext_b64) {
Ok(c) => c,
Err(_) => return (&[("content-type", "application/json")], serde_json::json!({ "error": "invalid_data" })),
};
let nonce = Nonce::from_slice(&nonce_bytes[..12]);
match cipher.decrypt(nonce, ciphertext.as_ref()) {
Ok(plaintext) => {
// Further validation of plaintext structure should happen here
let response = serde_json::json!({ "data": String::from_utf8_lossy(&plaintext) });
(axum::http::StatusCode::OK, axum::Json(response)).into_response()
}
Err(_) => {
// Always return the same generic error to avoid leaking padding or decryption details
(&[("content-type", "application/json")], serde_json::json!({ "error": "invalid_data" })).into_response()
}
}
}
#[tokio::main]
async fn main() {
let db = FirestoreDb::new("my-project-id").expect("valid project");
let app = Router::new()
.route("/secrets/:id", get(get_secret))
.with_state(db);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Key points in this remediation:
- Use AEAD modes such as AES-GCM or ChaCha20-Poly1305, which do not require padding and provide integrity verification, eliminating padding oracle conditions.
- Ensure that all decryption errors return a uniform error response (e.g.,
{"error": "invalid_data"}) with the same HTTP status code, preventing attackers from distinguishing padding failures from other issues. - Validate and encode data before storing in Firestore, and carefully handle base64 decoding and key management in Axum handlers to avoid secondary injection or deserialization issues.
For deployments using older schemes that require padding (e.g., AES-CBC), always use a constant-time comparison after decryption and verify an authentication tag or HMAC before processing plaintext. Never expose low-level cryptographic errors to the HTTP layer, and consider leveraging middleBrick scans to verify that your error handling and input validation consistently prevent oracle-like behavior.