Padding Oracle in Actix with Mutual Tls
Padding Oracle in Actix with Mutual Tls — how this specific combination creates or exposes the vulnerability
A padding oracle attack occurs when an application reveals whether decrypted ciphertext has valid padding, allowing an attacker to iteratively decrypt encrypted data without knowing the key. In Actix web applications that use TLS with client certificate authentication (Mutual TLS), this risk can emerge at the intersection of encrypted transport and application-level error handling.
Mutual TLS ensures the client presents a valid certificate during the TLS handshake, but it does not prevent an application from processing encrypted request bodies (e.g., JSON, JWE, or encrypted form fields) using a symmetric cipher like AES-CBC. If the application decrypts data and returns distinct HTTP status codes or response messages for padding errors versus other validation failures, an attacker who can observe these responses can use the oracle to decrypt ciphertext byte by byte.
In an Actix service, this typically happens when:
- TLS termination is handled at a load balancer or ingress, while the Actix app receives plaintext HTTP. In this case, Mutual TLS is enforced at the edge, but the application processes encrypted payloads and may leak padding errors.
- The Actix app explicitly decrypts encrypted fields (e.g., a token or encrypted JSON body) and uses a decryption routine that does not employ constant-time padding validation, returning 400 for bad padding and 401/403 for bad auth.
An example scenario: a client presents a valid certificate (Mutual TLS), then sends an encrypted blob in an HTTP JSON body. The Actix handler decrypts the blob using AES-CBC. If the decryption fails due to invalid padding and the handler responds with HttpResponse::BadRequest(), whereas a missing or invalid client certificate yields HttpResponse::Unauthorized(), the difference becomes an oracle. An attacker can craft chosen ciphertexts and observe status codes to recover plaintext.
Even with Mutual TLS, if the application treats padding errors differently from other failures, the encrypted payload’s security depends on the application, not the transport. middleBrick’s checks for Input Validation and Data Exposure can surface such risky response patterns by correlating error handling with encrypted input handling.
Mutual Tls-Specific Remediation in Actix — concrete code fixes
To mitigate padding oracle risks in an Actix service with Mutual TLS, ensure decryption is performed in constant time and that error responses do not distinguish between padding failures and other validation issues. Below are concrete patterns and code examples.
1. Use constant-time decryption and uniform error responses
Always use cryptographic libraries that provide constant-time padding removal (e.g., aes-gcm or properly used openssl with constant-time APIs). If you must use CBC, ensure padding validation does not branch on padding validity.
use actix_web::{web, HttpResponse, Responder};
use openssl::symm::{decrypt, Cipher};
use subtle::ConstantTimeEq; // recommended for padding checks
fn decrypt_constant_time(ciphertext: &[u8], key: &[u8], iv: &[u8]) -> Result, &'static str> {
// Example using AES-256-CBC with explicit padding handling
let cipher = Cipher::aes_256_cbc();
// openssl::symm::decrypt will raise an error on bad padding; avoid revealing the cause
match decrypt(cipher, key, Some(iv), ciphertext) {
Ok(plain) => Ok(plain),
Err(_) => {
// Return a generic error; do not indicate what failed
Err("decryption_failed")
}
}
}
async fn handle_secure(
body: web::Json,
) -> impl Responder {
let data = body.get("data").and_then(|v| v.as_str()).unwrap_or("");
let decoded = match base64::decode(data) {
Ok(d) => d,
Err(_) => return HttpResponse::BadRequest().json(serde_json::json!({ "error": "invalid_request" })),
};
// Assume key/iv are securely derived and available
let key = [0u8; 32];
let iv = [0u8; 16];
match decrypt_constant_time(&decoded, &key, &iv) {
Ok(plain) => HttpResponse::Ok().json(serde_json::json!({ "payload": plain })),
// Always use the same status and message shape for any decryption/padding failure
Err(_) => HttpResponse::BadRequest().json(serde_json::json!({ "error": "invalid_request" })),
}
}
2. Enforce Mutual TLS and avoid mixing transports
If you terminate TLS at a gateway, ensure the gateway validates client certificates and forwards only authenticated requests to Actix. If Actix also enforces Mutual TLS, ensure certificate verification is strict and that errors are not logged or surfaced in a way that helps an attacker.
// Example Actix middleware to verify client certificate details
use actix_web::dev::{ServiceRequest, ServiceResponse, Transform};
use actix_web::Error;
use futures::future::{ok, Ready};
use openssl::x509::X509;
pub struct MutualTlsValidator;
impl Transform for MutualTlsValidator
where
S: actix_web::dev::Service, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Transform = MutualTlsValidatorMiddleware;
type Future = Ready<Result Self::Future {
ok(MutualTlsValidatorMiddleware { service })
}
}
pub struct MutualTlsValidatorMiddleware<S> {
service: S,
}
impl<S, B> actix_web::dev::Service<ServiceRequest> for MutualTlsValidatorMiddleware<S>
where
S: actix_web::dev::Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = S::Future;
fn poll_ready(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, req: ServiceRequest) -> Self::Future {
// Inspect the peer certificate from the request extensions (populated by the TLS acceptor)
if let Some(cert) = req.connection_info().peer_certificate() {
if let Ok(x509) = X509::from_pem(cert.as_bytes()) {
// Perform validation: check issuer, expiry, etc.
if x509.subject_name().entries().any(|e| {
let data = e.data().as_slice_less_safe();
// Example: ensure CN is present
e.nid() == openssl::nid::Nid::COMMONNAME && !data.is_empty()
}) {
self.service.call(req)
} else {
futures::future::err(actix_web::error::ErrorUnauthorized("invalid client cert"))
}
} else {
futures::future::err(actix_web::error::ErrorUnauthorized("bad certificate format"))
}
} else {
futures::future::err(actix_web::error::ErrorUnauthorized("no client certificate"))
}
}
}
3. Operational practices
Use authenticated encryption (e.g., AES-GCM) where possible, avoid custom padding schemes, and ensure error logs do not include sensitive context that could aid an oracle. middleBrick’s LLM/AI Security checks can also detect whether endpoints leak information in error responses that could assist an attacker.
Remediation guidance is mapped to OWASP API Top 10 and relevant compliance frameworks; the Pro plan’s continuous monitoring can help detect regressions in error handling and encryption usage across API changes.