Ssrf in Actix with Hmac Signatures
Ssrf in Actix with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Server-Side Request Forgery (SSRF) in Actix applications that use HMAC signatures can arise when an endpoint accepts a user-supplied URL, adds an HMAC-signed query parameter or header, and then forwards the request internally without adequate validation. The HMAC is typically intended to prove integrity and authenticity of the outgoing request to a downstream service. However, if the application does not validate the target host, path, or port, an attacker can supply a malicious internal address such as http://169.254.169.254/latest/meta-data/ in AWS environments, or http://localhost:8080/admin, and the server-side logic will still compute a valid HMAC and make the request.
Because the downstream service verifies the HMAC, the forged request is accepted, allowing the attacker to force the server to interact with internal metadata services, container metadata endpoints, or other internal APIs that are not exposed externally. The presence of HMAC signatures does not mitigate SSRF; it may inadvertently increase risk by giving a false sense of integrity while the application is still making arbitrary internal requests. In Actix, this often occurs when route parameters or JSON bodies are used to construct target URLs without strict allowlisting of hosts and paths.
An example pattern is an Actix handler that reads a URL from a JSON payload, appends an HMAC computed with a shared secret, and performs an HTTP request using a client such as reqwest. If the handler trusts the user-provided host and port, the HMAC provides no protection against SSRF. Attackers can also exploit open redirects or DNS rebinding to reach internal services that the HMAC-verified downstream endpoint trusts. The risk is compounded when the HMAC is tied to a per-request nonce or timestamp without host validation, because the signature remains valid for the intended recipient but the request path is controlled by the attacker.
Real-world attack patterns include probing internal services like Redis on 127.0.0.1:6379, accessing cloud metadata at 100.100.100.200, or exploiting internal Kubernetes services via service DNS names. These SSRF vectors can lead to data exfiltration, internal network scanning, or further compromise depending on the exposed internal endpoints. The key takeaway is that HMAC signatures protect integrity and authentication of messages to known endpoints, but they do not restrict which endpoints the application is allowed to call.
Hmac Signatures-Specific Remediation in Actix — concrete code fixes
To remediate SSRF in Actix when HMAC signatures are in use, enforce strict allowlisting of hosts, ports, and paths before constructing any outgoing request. Do not rely on HMAC to validate the destination; treat it only as a mechanism to sign requests to known, trusted endpoints. Combine allowlisting with schema restrictions (e.g., only https), port restrictions, and a deny list of internal IP ranges and reserved hosts.
Example: Safe Actix handler with HMAC signing
The following example demonstrates an Actix web handler that validates the target host against an allowlist, ensures HTTPS, and then appends an HMAC signature to query parameters before issuing the request. It uses hmac, sha2, and reqwest with proper error handling.
use actix_web::{web, HttpResponse, Result};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use reqwest::Client;
use std::collections::HashSet;
// Type alias for HMAC-SHA256
type HmacSha256 = Hmac;
// Allowlisted hosts and their allowed ports
fn allowed_hosts() -> HashSet<(String, u16)> {
let mut set = HashSet::new();
set.insert(("api.example.com".to_string(), 443));
set.insert(("data.service.com".to_string(), 443));
set
}
async fn fetch_resource(query: web::Json<FetchRequest>) -> Result<HttpResponse> {
// Validate host and port against allowlist
let allowed = allowed_hosts();
if !allowed.contains(&(query.host.clone(), query.port)) {
return Ok(HttpResponse::BadRequest().body("Host or port not allowed"));
}
// Enforce HTTPS
if query.scheme != "https" {
return Ok(HttpResponse::BadRequest().body("Only HTTPS is allowed"));
}
// Build the target URL
let target_url = format!("{}://{}:{}{}", query.scheme, query.host, query.port, query.path);
// Compute HMAC over the target URL using a shared secret
let secret = b"super-secret-shared-key";
let mut mac = HmacSha256::new_from_slice(secret).expect("HMAC can take key of any size");
mac.update(target_url.as_bytes());
let result = mac.finalize();
let signature = result.into_bytes();
let signature_hex = hex::encode(signature);
// Perform the request with the HMAC in a query parameter
let client = Client::new();
let response = client
.get(&target_url)
.query(&[("hmac", signature_hex.as_str())])
.send()
.await
.map_err(|e| actix_web::error::ErrorBadGateway(e.to_string()))?;
let body = response.text().await.map_err(|e| actix_web::error::ErrorBadGateway(e.to_string()))?;
Ok(HttpResponse::Ok().body(body))
}
#[derive(serde::Deserialize)]
struct FetchRequest {
scheme: String,
host: String,
port: u16,
path: String,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| {
App::new()
.route("/fetch", actix_web::web::post().to(fetch_resource))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Key points in this fix:
- Host and port are validated against a strict allowlist before any URL construction.
- Only HTTPS is permitted to prevent cleartext interception.
- The HMAC is computed over the fully constructed target URL and sent as a query parameter for the downstream service to verify; the downstream service must also enforce the same allowlist to avoid trusting the HMAC alone.
- Error handling avoids leaking sensitive details and returns generic bad request responses for invalid inputs.
For production use, consider centralizing the allowlist configuration and rotating the HMAC secret via secure secret management. Combine these measures with runtime scanning using middleBrick to detect any residual SSRF risks in your Actix endpoints; the CLI (middlebrick scan <url>) and GitHub Action can help you enforce security gates in CI/CD.
Related CWEs: ssrf
| CWE ID | Name | Severity |
|---|---|---|
| CWE-918 | Server-Side Request Forgery (SSRF) | CRITICAL |
| CWE-441 | Unintended Proxy or Intermediary (Confused Deputy) | HIGH |