Bleichenbacher Attack in Axum
How Bleichenbacher Attack Manifests in Axum
The Bleichenbacher attack exploits PKCS#1 v1.5 padding oracles in RSA implementations. In Axum applications, this manifests most commonly through improper handling of RSA decryption errors in TLS termination and custom cryptographic implementations.
Axum's architecture, which separates request handling from cryptographic operations, creates specific attack surfaces. When Axum applications use TLS termination (either via Axum's built-in TLS or through reverse proxies), the timing and error message differences in RSA padding validation can leak information about the plaintext.
Consider an Axum endpoint that performs client certificate authentication using RSA:
use axum::{routing::get, Router, extract::Extension};
use tokio_postgres::Client;
async fn client_auth(
Extension(client): Extension<Client>,
cert: ClientCert,
) -> Result<String> {
// Vulnerable: timing oracle through early return on padding failure
let decrypted = match rsa_decrypt(cert.data()) {
Ok(data) => data,
Err(_) => return Err(Forbidden::new("Invalid certificate"))
};
// Database query using decrypted data
let row = client.query_one("SELECT * FROM users WHERE id = $1", &[&decrypted]).await?;
Ok(format!("Authenticated user: {}", row.get::<_, String>("name")))
}
async fn rsa_decrypt(data: &[u8]) -> Result<Vec<u8>> {
// PKCS#1 v1.5 decryption without constant-time implementation
// Returns early on padding validation failure
// Attacker can measure response time differences
// and craft adaptive queries
unimplemented!()
}The vulnerability appears when the decryption function returns different error types or response times based on whether padding validation failed. An attacker can send crafted ciphertexts and observe whether the server returns a padding error or a different error (like invalid certificate format), gradually recovering the plaintext.
Axum's middleware chain can also introduce Bleichenbacher-like timing oracles. If middleware performs different operations based on decrypted data before authentication is complete, it creates a side-channel:
use axum::{middleware, Router};
async fn vulnerable_middleware(
req: Request,
next: Next,
) -> Result<Response> {
// Early branching based on decrypted TLS data
let decrypted = extract_tls_data(req)?;
// Different execution paths based on decrypted content
if decrypted.starts_with(b"admin") {
// Path A: more processing, different timing
let result = next.run(req).await;
log::info!("Admin request processed");
result
} else {
// Path B: less processing, different timing
let result = next.run(req).await;
log::info!("Regular request processed");
result
}
}The core issue is that Axum's modular design allows developers to easily introduce timing-sensitive operations before proper authentication and authorization checks are in place.
Axum-Specific Detection
Detecting Bleichenbacher vulnerabilities in Axum applications requires examining both the application code and the cryptographic implementations used. middleBrick's black-box scanning can identify timing oracles and error message differences that indicate Bleichenbacher vulnerabilities.
For Axum applications, middleBrick performs several specific checks:
- Timing Analysis: Sends crafted RSA ciphertexts and measures response time variations across requests. Significant timing differences suggest padding oracle vulnerabilities.
- Error Message Analysis: Examines HTTP response bodies and status codes for information leakage through different error types (padding vs. format errors vs. authentication failures).
- Cryptographic Implementation Audit: Analyzes the TLS configuration and cryptographic libraries used by the Axum application, flagging deprecated PKCS#1 v1.5 padding usage.
middleBrick's scanning process for Axum applications includes:
# Scan an Axum API endpoint for Bleichenbacher vulnerabilities
middlebrick scan https://api.example.com/auth --profile rsa-padding
# Output includes:
# - Timing variance analysis (ms)
# - Error message classification
# - Cryptographic library fingerprinting
# - Severity score with remediation guidanceDevelopers can also perform manual detection using Axum's built-in middleware for timing analysis:
use axum::{middleware, Router};
use std::time::Instant;
async fn timing_middleware(
req: Request,
next: Next,
) -> Result<Response> {
let start = Instant::now();
let result = next.run(req).await;
let duration = start.elapsed();
// Log timing for analysis
log::info!("Request processed in {}ms", duration.as_millis());
// Flag suspicious timing patterns
if duration.as_millis() > 100 {
log::warn!("Potential timing oracle detected");
}
result
}
let app = Router::new()
.route("/api/protected", get(protected_handler))
.layer(middleware::from_fn(timing_middleware));Additional detection can be performed using middleware that inspects TLS handshake details:
use axum::{middleware, Router, extract::RequestParts};
use rustls::ClientCertVerified;
async fn tls_inspection_middleware(
req: RequestParts,
next: Next,
) -> Result<Response> {
if let Some(tls_info) = req.tls_info() {
// Check for RSA key exchange usage
if tls_info.key_exchange_algorithm() == "RSA" {
log::warn!("RSA key exchange detected - potential Bleichenbacher risk");
}
}
next.run(req).await
}Axum-Specific Remediation
Remediating Bleichenbacher vulnerabilities in Axum applications requires both architectural changes and cryptographic best practices. The primary defense is eliminating RSA PKCS#1 v1.5 padding entirely in favor of secure alternatives.
For Axum applications, the recommended approach is migrating to RSA-OAEP or elliptic curve cryptography:
use axum::{routing::get, Router, Json};
use rsa::{PaddingScheme, RsaPrivateKey, RsaPublicKey};
use rand::rngs::OsRng;
// Replace PKCS#1 v1.5 with RSA-OAEP
async fn secure_decrypt(
pub_key: RsaPublicKey,
priv_key: RsaPrivateKey,
ciphertext: Vec<u8>,
) -> Result<Vec<u8>> {
// RSA-OAEP padding is resistant to Bleichenbacher attacks
let padding = PaddingScheme::new_oaep::();
// Constant-time decryption implementation
let decrypted = priv_key.decrypt(padding, &ciphertext)?;
Ok(decrypted)
}
async fn secure_endpoint(
Json(payload): Json<Payload>,
) -> Result<Json<Response>> {
// Use RSA-OAEP for all cryptographic operations
let decrypted = secure_decrypt(payload.encrypted_data).await?;
// Process data without timing-sensitive operations
let response = process_secure_data(decrypted);
Ok(Json(response))
}
#[derive(Deserialize)]
struct Payload {
encrypted_data: Vec<u8>,
}
#[derive(Serialize)]
struct Response {
message: String,
}
fn process_secure_data(data: Vec<u8>) -> Response {
// Constant-time operations only
// No early returns based on decrypted content
Response { message: "Data processed securely".to_string() }
} Axum's middleware system can enforce constant-time execution paths:
use axum::{middleware, Router, Json};
use std::time::Instant;
async fn constant_time_middleware(
req: Request,
next: Next,
) -> Result<Response> {
let start = Instant::now();
let result = next.run(req).await;
let duration = start.elapsed();
// Ensure constant minimum execution time
let min_duration = std::time::Duration::from_millis(100);
if duration < min_duration {
tokio::time::sleep(min_duration - duration).await;
}
result
}
let app = Router::new()
.route("/api/secure", post(secure_handler))
.layer(middleware::from_fn(constant_time_middleware));For applications that must maintain RSA PKCS#1 v1.5 compatibility, implement strict constant-time validation:
use subtle::ConstantTimeEq;
async fn constant_time_validation(
decrypted: Vec<u8>,
expected_prefix: &[u8],
) -> bool {
// Constant-time comparison
let prefix_match = decrypted.starts_with(expected_prefix);
let length_valid = decrypted.len() == expected_prefix.len() + 1;
// Combine conditions without short-circuiting
prefix_match && length_valid
}
async fn secure_handler(
Json(payload): Json<Payload>,
) -> Result<Json<Response>> {
let decrypted = rsa_decrypt_v1_5(payload.data)?;
// Constant-time validation instead of early returns
let is_valid = constant_time_validation(decrypted, b"valid_prefix");
if !is_valid {
// Always perform same operations regardless of validation
log_error_and_return();
}
// Continue processing
Ok(Json(Response { message: "Processed".to_string() }))
}