Ssrf Server Side in Actix with Hmac Signatures
Ssrf Server Side 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 for request authentication can occur when an attacker is able to influence both the target URL and the set of headers or query parameters that are included in the HMAC computation. If the application builds a signed request to an arbitrary endpoint—such as a user-supplied URL or a header-derived host—and does not adequately restrict the scope of what is covered by the signature, the server may be tricked into forwarding requests to internal services that are not directly reachable from the internet.
In an Actix-web service, this often manifests when business logic accepts a URL and a set of parameters, computes an HMAC over selected fields (e.g., timestamp, nonce, endpoint path, or a subset of headers), and then forwards the request using an internal HTTP client. Because the HMAC is intended to ensure integrity and authenticity between two cooperating services, developers may mistakenly trust it as authorization or isolation. An attacker can exploit this by providing a URL that resolves to internal endpoints (e.g., http://169.254.169.254/latest/meta-data/ in cloud environments, or http://internal-database:8080/), while the HMAC verification still passes on the server side because the attacker-controlled inputs were included in the signed material in a way the developer did not intend to allow.
Common patterns that lead to SSRF with HMAC signatures in Actix include: (1) allowing the client to choose the endpoint while the server signs a subset of request components that do not sufficiently bind the request to a specific, safe target; (2) failing to validate that the target host resolves to an expected network zone (public versus internal); and (3) including mutable or attacker-controlled data in the HMAC without also binding the request to a strict schema and port. Because Actix is asynchronous and non-blocking by design, such requests can chain to internal metadata services, container metadata endpoints, or other internal APIs, leading to sensitive data exposure or further compromise.
For example, an endpoint that accepts a URL and a timestamp, computes HMAC-SHA256(timestamp + url, secret), and then forwards to the provided URL can be abused if an attacker supplies an internal IP or a cloud metadata URL. The HMAC will validate correctly as long as the attacker echoes back the same timestamp and URL in the request, and the server performs the forwarding without additional network restrictions.
Hmac Signatures-Specific Remediation in Actix — concrete code fixes
To mitigate SSRF when using HMAC signatures in Actix, bind the signature tightly to an explicit, server-controlled target and do not incorporate attacker-influenced URLs into the signed payload. Instead, maintain a server-side mapping of allowed endpoints or paths, and have the client reference an identifier that the server maps to a full URL. The HMAC should cover the method, the canonical path, selected headers, and any nonces or timestamps, but not the target host as provided by the client.
Below are two concrete Actix-web examples. The first shows a vulnerable pattern where an attacker-controlled URL is included in the HMAC; the second shows a hardened approach where the target is resolved from a server-side allowlist and the signature does not depend on the target host provided by the client.
Vulnerable pattern (do not use)
use actix_web::{web, HttpResponse, Result};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::collections::HashMap;
type HmacSha256 = Hmac<Sha256>;
// WARNING: this endpoint is SSRF-prone when the URL is used in HMAC and then forwarded
async fn fetch_vulnerable(
payload: web::Json<HashMap<String, String>>
) -> Result<HttpResponse> {
let url = payload.get("url").ok_or_else(|| actix_web::error::ErrorBadRequest("missing url"))?;
let timestamp = payload.get("timestamp").ok_or_else(|| actix_web::error::ErrorBadRequest("missing timestamp"))?;
let provided_hmac = payload.get("hmac").ok_or_else(|| actix_web::error::ErrorBadRequest("missing hmac"))?;
let secret = b"super-secret-key";
let mut mac = HmacSha256::new_from_slice(secret).map_err(|_| actix_web::error::ErrorInternalServerError("hmac error"))?;
mac.update(timestamp.as_bytes());
mac.update(url.as_bytes());
let computed = mac.finalize();
let computed_bytes = computed.into_bytes();
// Insecure: uses constant-time comparison, but the URL used for signing is attacker-controlled
if subtle::ConstantTimeEq::ct_eq(computed_bytes.as_slice(), hex::decode(provided_hmac).unwrap_or_default().as_slice()).into() {
// Forwards to the attacker-supplied URL — SSRF risk
let resp = reqwest::get(url.as_str()).await.map_err(|_| actix_web::error::ErrorBadGateway())?;
let body = resp.text().await.map_err(|_| actix_web::error::ErrorBadGateway())?;
Ok(HttpResponse::Ok().body(body))
} else {
Err(actix_web::error::ErrorUnauthorized("invalid hmac"))
}
}
Hardened remediation in Actix
use actix_web::{web, HttpResponse, Result};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use std::collections::HashMap;
// Server-side allowlist of safe endpoints
fn resolve_allowed_path(path_id: &str) -> Option<String> {
let mut map = std::collections::HashMap::new();
map.insert("public_data", "https://api.example.com/public/data");
map.insert("health_check", "https://internal-monitoring.example.local/health");
map.get(path_id).map(|s| s.to_string())
}
async fn fetch_hardened(
payload: web::Json<HashMap<String, String>>
) -> Result<HttpResponse> {
let path_id = payload.get("path").ok_or_else(|| actix_web::error::ErrorBadRequest("missing path"))?;
let timestamp = payload.get("timestamp").ok_or_else(|| actix_web::error::ErrorBadRequest("missing timestamp"))?;
let provided_hmac = payload.get("hmac").ok_or_else(|| actix_web::error::ErrorBadRequest("missing hmac"))?;
let allowed_url = resolve_allowed_path(path_id)
.ok_or_else(|| actix_web::error::ErrorBadRequest("invalid path identifier"))?;
let secret = b"super-secret-key";
let mut mac = HmacSha256::new_from_slice(secret).map_err(|_| actix_web::error::ErrorInternalServerError("hmac error"))?;
// Sign method, path_id, and timestamp — not the resolved host from client input
mac.update(b"GET");
mac.update(path_id.as_bytes());
mac.update(timestamp.as_bytes());
let computed = mac.finalize();
let computed_bytes = computed.into_bytes();
if subtle::ConstantTimeEq::ct_eq(computed_bytes.as_slice(), hex::decode(provided_hmac).unwrap_or_default().as_slice()).into() {
// Forward to a server-controlled URL — no SSRF from client-supplied target
let resp = reqwest::get(allowed_url.as_str()).await.map_err(|_| actix_web::error::ErrorBadGateway())?;
let body = resp.text().await.map_err(|_| actix_web::error::ErrorBadGateway())?;
Ok(HttpResponse::Ok().body(body))
} else {
Err(actix_web::error::ErrorUnauthorized("invalid hmac"))
}
}
Key remediation principles for Actix services using HMAC signatures:
- Do not include attacker-influenced URLs or hosts in the HMAC input.
- Use a server-side allowlist to map client-supplied identifiers to concrete, pre-vetted endpoints.
- Bind the signature to the HTTP method, canonical path identifier, and a server-side nonce or timestamp to prevent replay.
- Enforce network-level egress controls so that even if SSRF occurs, the runtime cannot reach internal metadata or sensitive services.