Hallucination Attacks in Actix with Hmac Signatures
Hallucination Attacks in Actix with Hmac Signatures — how this specific combination creates or exposes the vulnerability
A hallucination attack in the context of Actix web services using Hmac Signatures occurs when an application accepts unverified or loosely validated authenticated requests and synthesizes plausible but false backend responses, often reflecting missing verification of the Hmac rather than outright rejection of tampered input. This can arise when the server computes an Hmac over a subset of the message (e.g., selected headers or a partial payload) but then proceeds to process the request as authoritative even when the computed Hmac does not match the client-supplied signature, effectively hallucinating a valid authentication context.
Hmac Signatures-Specific Remediation in Actix — concrete code fixes
To mitigate hallucination risks, enforce strict Hmac verification before any business logic in Actix. Always compute the Hmac over a canonical representation of the request (including required headers and the raw body), compare using a constant-time equality check, and reject the request if verification fails. Below are concrete, working examples for Actix web that demonstrate a secure verification pattern.
Example: Secure Hmac-SHA256 verification in Actix
use actix_web::{web, HttpRequest, HttpResponse, Result};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::collections::BTreeMap;
// Type alias for Hmac-SHA256
// N.B. In production, derive the key from a secure source, never hardcode.
type HmacSha256 = Hmac;
/// Canonicalize headers we choose to include in the Hmac.
/// Using BTreeMap enforces a deterministic order for the signature base string.
fn canonical_headers(req: &HttpRequest, include_headers: &[&str]) -> BTreeMap {
let mut map = BTreeMap::new();
for &name in include_headers {
if let Some(value) = req.headers().get(name) {
// Use to_str() only if you control encoding expectations; otherwise handle errors.
if let Ok(value_str) = value.to_str() {
map.insert(name.to_string(), value_str.trim().to_string());
}
}
}
map
}
/// Verify the Hmac signature provided in the "X-API-Signature" header.
/// The client should have signed: method + path + canonical headers + SHA256(body).
async fn verify_hmac(req: &HttpRequest, body: &str, api_key: &[u8]) -> bool {
// The header may contain a scheme; we assume "hmac " for clarity.
let sig_header = match req.headers().get("X-API-Signature") {
Some(h) => h,
None => return false,
};
let sig_str = match sig_header.to_str() {
Ok(s) => s.trim(),
Err(_) => return false,
};
// Strip optional scheme prefix if present.
let sig_hex = sig_str.strip_prefix("hmac ").unwrap_or(sig_str);
// Build the string-to-sign consistently with the client.
let method = req.method().as_str();
let path = req.path();
let headers = canonical_headers(req, &["x-request-id", "x-timestamp", "content-type"]);
// Construct a canonical representation. Avoid including fragile or multi-valued headers
// unless explicitly defined in your spec.
let mut canonical = String::new();
canonical.push_str(method);
canonical.push(' ');
canonical.push_str(path);
canonical.push(' ');
for (k, v) in &headers {
canonical.push_str(&format!("\n{}:{}", k, v));
}
// Include the raw body as part of the signed payload.
canonical.push('\n');
canonical.push_str(&sha2::Digest::chain_update(sha2::Sha256::new(), body.as_bytes()).finalize().iter().map(|b| format("{:02x}", b)).collect::());
// Compute Hmac over the canonical string.
let mut mac = HmacSha256::new_from_slice(api_key).expect("Hmac key should be valid length");
mac.update(canonical.as_bytes());
let computed = mac.finalize().into_bytes();
// Decode client signature and compare in constant time.
let client_sig = match hex::decode(sig_hex) {
Ok(bytes) => bytes,
Err(_) => return false,
};
// Use subtle::ConstantTimeEq to avoid timing leaks.
// If you prefer no extra crate, you can use hmac::Output::verify_slice but subtle is safer.
computed.as_slice().ct_eq(client_sig.as_slice()).into()
}
/// Example handler that rejects hallucinated authentication context.
async fn secure_endpoint(req: HttpRequest, body: web::Bytes) -> Result {
let api_key = include_bytes!("./api_key.bin"); // Load securely at startup.
if !verify_hmac(&req, &body, api_key).await {
return Ok(HttpResponse::Unauthorized().body("Invalid signature"));
}
// Proceed only after strict verification; do not synthesize a valid context from a weak check.
Ok(HttpResponse::Ok().body("Authenticated and processed"))
}
Key points in this remediation:
- Canonicalization: Use a deterministic ordering (e.g., BTreeMap for headers) to ensure client and server build the same string-to-sign.
- Body handling: Include the full request body (as bytes) in the Hmac input, not a transformed or truncated version, to prevent body-swapping hallucinations.
- Constant-time comparison: Avoid early-exit string comparisons that leak timing information.
- Strict rejection: Do not fall back to a weaker authentication path if Hmac verification fails; return 401/403 immediately.
Example: Reject requests with missing or malformed signatures
async fn validate_wrapper(
req: HttpRequest,
body: web::Bytes,
) -> Result {
const API_KEY: &[u8; 32] = b"an_example_32_byte_key_for_hmac_demo_123456";
if !verify_hmac(&req, &body, API_KEY).await {
return Ok(HttpResponse::Forbidden().json(serde_json::json!({
"error": "invalid_hmac",
"message": "Request signature verification failed; reject hallucinated authentication"
})));
}
// Continue with safe, verified processing.
Ok(HttpResponse::Ok().json(serde_json::json!({"status": "ok"})))
}
Related CWEs: llmSecurity
| CWE ID | Name | Severity |
|---|---|---|
| CWE-754 | Improper Check for Unusual or Exceptional Conditions | MEDIUM |