Bleichenbacher Attack in Axum with Jwt Tokens
Bleichenbacher Attack in Axum with Jwt Tokens — how this specific combination creates or exposes the vulnerability
A Bleichenbacher attack is a chosen-ciphertext attack against RSA-based encryption or signature schemes that rely on PKCS#1 v1.5 padding. In the context of an Axum service that uses JWT tokens with RSA signatures (for example, RS256), this attack can be possible when signature verification is performed in a way that leaks information about whether a signature is valid, typically via timing differences or error messages. If an endpoint in Axum deserializes a JWT, extracts the signature, and makes remote calls or performs comparisons that behave differently for valid versus invalid signatures, an attacker can iteratively adaptively send modified tokens to gradually recover the private key or forge tokens.
In Axum, this often arises when JWT validation logic does not use constant-time verification and returns distinct errors (e.g., invalid signature vs. malformed token). An attacker can observe HTTP response status codes, response times, or body content to distinguish these cases. For instance, an endpoint that returns 401 for invalid signatures but 400 for malformed tokens allows an attacker to learn the validity of each guess. Even when tokens are cryptographically sound, implementation-level timing leaks and non-constant string comparisons create a practical Bleichenbacher-style oracle. This becomes more impactful when the same service both issues and validates JWTs, or when introspection or downstream calls also expose validation behavior.
Consider an Axum handler that parses and validates a JWT using a public key without constant-time safeguards:
use axum::{routing::get, Router}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData, Header}; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use std::sync::Arc; async fn handler() { /* omitted */ } #[derive(Debug, Serialize, Deserialize)] struct Claims { sub: String, exp: usize, } async fn validate_jwt( token: &str, public_key: &Arc<jsonwebtoken::EncodingKey> ) -> Result<TokenData<Claims>, jsonwebtoken::Error> { let decoding_key = DecodingKey::from_rsa_pem(public_key.to_rs_pem().as_ref())?; let validation = Validation::new(Algorithm::RS256); decode::token_data(&token, &decoding_key, &validation) }If the decode call leaks timing via network or application behavior, and the service distinguishes between malformed input and a bad signature, an attacker can mount a Bleichenbacher-style adaptive attack against the RSA signature. This highlights the importance of using constant-time verification and uniform error handling in Axum JWT validation to prevent token forgery via adaptive chosen-ciphertext techniques.
Jwt Tokens-Specific Remediation in Axum — concrete code fixes
To mitigate Bleichenbacher-style attacks in Axum when working with JWT tokens, ensure signature verification is performed in constant time and that error handling does not distinguish between malformed tokens and invalid signatures. Use well-maintained libraries that implement constant-time RSA verification and avoid exposing distinct error paths to the network. Always decode and validate tokens in a single, uniform code path that returns the same HTTP status and similar response shape for any invalid input.
Below are concrete Axum examples with JWT tokens that demonstrate secure validation patterns. First, a robust handler that uses uniform error responses and avoids branching on signature validity details:
use axum::{routing::get, Router, http::StatusCode, response::IntoResponse}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData, errors::Error as JwtError}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tower_http::trace::TraceLayer; #[derive(Debug, Serialize, Deserialize)] struct Claims { sub: String, exp: usize, } async fn validate_jwt_secure( token: &str, decoding_key: &Arc<DecodingKey> ) -> Result<TokenData<Claims>, (StatusCode, String)> { let validation = Validation::new(Algorithm::RS256); match decode::token_data(&token, decoding_key, &validation) { Ok(token_data) => Ok(token_data), Err(_) => Err((StatusCode::UNAUTHORIZED, String::from("invalid_token"))), } } async fn handler( axum::extract::Header(axum::http::header::AUTHORIZATION) auth_header: axum::extract::Header<String>, decoding_key: Arc<DecodingKey>, ) -> impl IntoResponse { let token = match auth_header.strip_prefix("Bearer ") { Some(stripped) => stripped, None => return (StatusCode::BAD_REQUEST, String::from("malformed_auth_header")).into_response(), }; match validate_jwt_secure(token, &decoding_key).await { Ok(token_data) => (StatusCode::OK, format("Hello, user: {}", token_data.claims.sub)).into_response(), Err(_) => (StatusCode::UNAUTHORIZED, String::from("invalid_token")).into_response(), } } #[tokio::main] async fn main() { let decoding_key = Arc::new( DecodingKey::from_rsa_pem(include_bytes!("public_key.pem")).expect("invalid key") ); let app = Router::new() .route("/hello", get(handler)) .layer(TraceLayer::new_for_http()); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); }Second, if you use jsonwebtoken with Axum, always prefer from_rsa_pem with a verified public key and avoid using secret-based validation with timing-sensitive compare patterns. Do not parse or verify tokens in multiple branches; keep error paths uniform:
use axum::routing::get; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData}; use serde::Deserialize; use std::net::SocketAddr; use std::sync::Arc; #[derive(Debug, Deserialize)] struct Claims { sub: String, exp: usize, } async fn verify_token(token: &str, key: &Arc<DecodingKey>) -> Result<TokenData<Claims>, jsonwebtoken::errors::Error> { let validation = Validation::new(Algorithm::RS256); decode::token_data(token, key, &validation) }Additionally, consider integrating middleBrick into your development workflow using the CLI for quick scans: middlebrick scan <url>, or add the GitHub Action to your CI/CD pipeline to fail builds if security scores drop below your chosen threshold. These integrations help detect configuration issues and validation inconsistencies before deployment, reducing the risk of introducing timing leaks or weak verification patterns in Axum services handling JWT tokens.