Ssrf in Actix with Jwt Tokens
Ssrf in Actix with Jwt Tokens — how this specific combination creates or exposes the validation gap
Server-Side Request Forgery (SSRF) in Actix applications that also handle JWT tokens arises when an API endpoint accepts a user-supplied URL, performs an outbound HTTP request on behalf of the caller, and either fails to validate the target or inadvertently includes authorization information in a way that exposes internal resources. JWT tokens are often used to carry authentication or identity claims; if an endpoint uses those tokens to authorize access to internal services (e.g., calling another microservice with the token as a bearer credential) without strict destination validation, SSRF can occur.
For example, consider an Actix endpoint that accepts a target_url parameter and forwards an authenticated request using a JWT extracted from an incoming Authorization header. If the developer does not enforce a strict allowlist of hostnames or schemes, an attacker can supply a URL like http://169.254.169.254/latest/meta-data/iam/security-credentials/ (AWS instance metadata). The Actix service, carrying the original JWT as a bearer token, forwards the request internally, potentially leaking sensitive metadata or cloud instance roles. Even if the JWT is validated for signature and claims, the SSRF bypasses intended access boundaries because the token is used downstream without additional network controls.
In an OpenAPI 3.0 specification, such a route might appear permissive by accepting any string for a redirect or callback URL. Runtime SSRF checks in middleBrick scan for these patterns by correlating the OpenAPI definition with observed behavior: the scanner submits probe URLs (including internal IPs, cloud metadata endpoints, and SSRF-sensitive schemes) and checks whether the service follows the supplied redirect or attempts to reach internal destinations. When JWT tokens are involved, the scan also verifies whether tokens are forwarded to untrusted hosts, which would constitute a high-severity finding under the BFLA/Privilege Escalation and SSRF checks.
Remediation guidance centers on two controls: strict URL allowlisting and separation of trust boundaries. Do not forward user-supplied URLs to internal services, and never propagate JWTs to destinations that are not explicitly trusted. In cases where outbound calls are required, use a dedicated service account with minimal permissions and enforce network-level restrictions (for example, egress rules that block private IP ranges).
Jwt Tokens-Specific Remediation in Actix — concrete code fixes
To mitigate SSRF in Actix when JWT tokens are used, ensure that any outbound HTTP client is configured with strict destination validation and does not automatically forward incoming Authorization headers. Below are two approaches: a vulnerable pattern and a secure pattern with code examples.
Vulnerable pattern (for illustration)
use actix_web::{web, HttpResponse, HttpRequest};
use reqwest::Client;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
async fn proxy_endpoint(
req: HttpRequest,
body: web::Json,
) -> HttpResponse {
// Extract token from incoming request
let auth_header = req.headers().get("Authorization");
let token = match auth_header {
Some(h) => h.to_str().unwrap_or("").strip_prefix("Bearer ").unwrap_or(""),
None => return HttpResponse::Unauthorized().finish(),
};
// Decode token for claims (validation skipped for brevity)
let _claims = decode::(
token,
&DecodingKey::from_secret("secret".as_ref()),
&Validation::new(Algorithm::HS256),
).map_err(|_| HttpResponse::Unauthorized().finish())?;
// Build client and forward to user-supplied URL — SSRF risk
let client = Client::new();
let target = body.get("target_url").and_then(|v| v.as_str()).unwrap_or("");
let response = client.get(target)
.bearer_auth(token)
.send()
.await;
match response {
Ok(resp) => HttpResponse::Ok().body(resp.text().await.unwrap_or_default()),
Err(_) => HttpResponse::BadGateway().finish(),
}
}
Secure remediation with allowlist and header separation
use actix_web::{web, HttpResponse, HttpRequest};
use reqwest::Client;
use url::Url;
async fn secure_proxy_endpoint(
body: web::Json,
) -> HttpResponse {
let target = match body.get("target_url").and_then(|v| v.as_str()) {
Some(u) => u,
None => return HttpResponse::BadRequest().body("target_url is required"),
};
// Strict allowlist: only specific hosts and HTTPS
let parsed = match Url::parse(target) {
Ok(u) => u,
Err(_) => return HttpResponse::BadRequest().body("invalid URL"),
};
if parsed.scheme() != "https" {
return HttpResponse::BadRequest().body("only HTTPS allowed");
}
let host = match parsed.host_str() {
Some(h) => h,
None => return HttpResponse::BadRequest().body("missing host"),
};
if !["api.trusted.com", "internal.service.corp"].contains(&host) {
return HttpResponse::BadRequest().body("host not allowed");
}
// Build client without injecting the incoming Authorization header
let client = Client::new();
let response = match client.get(parsed).send().await {
Ok(r) => r,
Err(_) => return HttpResponse::BadGateway().body("request failed"),
};
// Optionally map selected response headers/body
HttpResponse::Ok().body(response.text().await.unwrap_or_default())
}
Key points in the secure example:
- The incoming JWT is not forwarded; authentication for the outbound call is handled separately (e.g., via a static key or service-specific credentials scoped to the allowed host).
- URL parsing and an explicit allowlist prevent redirection to internal or cloud metadata endpoints.
- The Actix handler remains focused on the intended business flow, avoiding automatic propagation of untrusted inputs.
middleBrick scans can validate that your OpenAPI spec and runtime behavior align with these practices by flagging endpoints that forward user-controlled URLs with bearer authorization and by checking for missing host allowlists.
Related CWEs: ssrf
| CWE ID | Name | Severity |
|---|---|---|
| CWE-918 | Server-Side Request Forgery (SSRF) | CRITICAL |
| CWE-441 | Unintended Proxy or Intermediary (Confused Deputy) | HIGH |