Webhook Abuse in Actix with Bearer Tokens
Webhook Abuse in Actix with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Webhook abuse in Actix applications that rely on Bearer Tokens occurs when an attacker can induce the server to make unauthorized outbound HTTP requests to arbitrary endpoints. This is often a Server-Side Request Forgery (SSRF) or an Unsafe Consumption issue where the application fetches a user-supplied URL and adds an Authorization header using a Bearer Token intended for a downstream service.
In Actix, a common vulnerable pattern is accepting a URL from a request body or query parameter and using an HTTP client to call that URL while attaching a static Bearer Token. Because the token is attached automatically, an attacker can point the request to an internal metadata service (e.g., http://169.254.169.254/latest/meta-data/) or to an SSRF-vulnerable endpoint that they control, causing the server to leak sensitive credentials or to perform actions on behalf of the service identified by the Bearer Token.
The risk is compounded when the Bearer Token has elevated scopes, since the server is effectively acting as a proxy with those permissions. Even if the application validates that the URL uses HTTPS, an attacker can abuse URL parsing ambiguities (e.g., hostname confusion via Unicode, mixed-case schemes, or unexpected ports) to redirect the request internally. Because middleBrick tests the unauthenticated attack surface, it can detect whether a webhook or HTTP-forwarding endpoint is reachable and whether Bearer Tokens are used in a way that exposes internal services or allows data exfiltration.
An illustrative vulnerable Actix handler (Rust) is shown below. This handler takes a target URL from JSON input and forwards a request with a hardcoded Bearer Token. Without strict allowlisting and canonicalization, an attacker can supply http://169.254.169.254/latest/meta-data/ as the target, causing the server to leak instance credentials.
use actix_web::{web, HttpResponse, Responder};
use reqwest::Client;
use serde::Deserialize;
#[derive(Deserialize)]
struct ForwardRequest {
url: String,
}
async fn webhook_forward(data: web::Json<ForwardRequest>) -> impl Responder {
let client = Client::new();
let token = "secret_bearer_token_abc123";
let req_result = client
.get(&data.url)
.bearer_auth(token)
.send()
.await;
match req_result {
Ok(resp) => HttpResponse::Ok().body(resp.text().await.unwrap_or_default()),
Err(_) => HttpResponse::BadGateway().finish(),
}
}
In this example, the Bearer Token is static and reused for any URL provided by the caller. If the URL is not strictly validated against an allowlist, an attacker can abuse the webhook endpoint to make the server act as a proxy with the token’s privileges. middleBrick detects such patterns by correlating the use of Bearer Tokens in request flows with risky behaviors like unvalidated URL inputs and missing egress filtering, which are common in Webhook Abuse scenarios.
Additionally, logging and monitoring may not capture these outbound calls, allowing an attacker to repeatedly probe internal endpoints without detection. The combination of dynamic user input, automatic Bearer Token injection, and insufficient egress controls creates a clear path for information disclosure or unauthorized actions. Remediation requires strict allowlisting, input canonicalization, and avoiding the use of highly privileged tokens for forwarded requests, which are covered in the remediation guidance below.
Bearer Tokens-Specific Remediation in Actix — concrete code fixes
To remediate Bearer Token-related webhook abuse in Actix, you must ensure that outbound requests are restricted to known, safe endpoints and that tokens are scoped to the minimum required permissions. The following examples demonstrate secure patterns.
1. Strict allowlist of domains. Only permit forwarding to pre-approved hosts and reject any URL that does not match exactly. Normalize the URL to prevent bypass via whitespace, Unicode, or mixed-case schemes.
use actix_web::{web, HttpResponse, Responder};
use reqwest::Client;
use url::Url;
async fn webhook_forward_secure(data: web::Json<ForwardRequest>) -> impl Responder {
// Strict allowlist
let allowed_hosts = ["api.example.com", "hooks.partner.com"];
let parsed = match Url::parse(&data.url) {
Ok(u) => u,
Err(_) => return HttpResponse::BadRequest().body("Invalid URL"),
};
if !allowed_hosts.contains(&parsed.host_str().unwrap_or("")) {
return HttpResponse::Forbidden().body("Host not allowed");
}
let client = Client::new();
let token = "secret_bearer_token_abc123";
let req_result = client
.get(parsed.as_str())
.bearer_auth(token)
.send()
.await;
match req_result {
Ok(resp) => HttpResponse::Ok().body(resp.text().await.unwrap_or_default()),
Err(_) => HttpResponse::BadGateway().finish(),
}
}
2. Per-request tokens or short-lived credentials. Avoid long-lived static Bearer Tokens for forwarded requests. If possible, use tokens scoped to the specific target and rotate them frequently. For integrations that require fixed tokens, store them as environment variables and avoid hardcoding them in source files.
use std::env;
async fn webhook_forward_with_env_token(data: web::Json<ForwardRequest>) -> impl Responder {
let allowed_hosts = ["api.example.com"];
let parsed = match Url::parse(&data.url) {
Ok(u) => u,
Err(_) => return HttpResponse::BadRequest().body("Invalid URL"),
};
if !allowed_hosts.contains(&parsed.host_str().unwrap_or("")) {
return HttpResponse::Forbidden().body("Host not allowed");
}
let token = match env::var("OUTBOUND_BEARER_TOKEN") {
Ok(t) => t,
Err(_) => return HttpResponse::InternalServerError().body("Missing token config"),
};
let client = Client::new();
let req_result = client
.get(parsed.as_str())
.bearer_auth(token)
.send()
.await;
match req_result {
Ok(resp) => HttpResponse::Ok().body(resp.text().await.unwrap_or_default()),
Err(_) => HttpResponse::BadGateway().finish(),
}
}
3. Validate URL schemes and disable redirects. Prevent the HTTP client from following redirects to arbitrary locations, and reject non-HTTPS schemes unless explicitly required and safe.
async fn webhook_forward_no_redirects(data: web::Json<ForwardRequest>) -> impl Responder {
let allowed_hosts = ["api.example.com"];
let parsed = match Url::parse(&data.url) {
Ok(u) => u,
Err(_) => return HttpResponse::BadRequest().body("Invalid URL"),
};
if parsed.scheme() != "https" {
return HttpResponse::BadRequest().body("Only HTTPS allowed");
}
if !allowed_hosts.contains(&parsed.host_str().unwrap_or("")) {
return HttpResponse::Forbidden().body("Host not allowed");
}
let client = Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();
let token = "secret_bearer_token_abc123";
let req_result = client
.get(parsed.as_str())
.bearer_auth(token)
.send()
.await;
match req_result {
Ok(resp) => HttpResponse::Ok().body(resp.text().await.unwrap_or_default()),
Err(_) => HttpResponse::BadGateway().finish(),
}
}
By combining allowlisting, token scoping, and redirect control, you reduce the attack surface for Webhook Abuse. middleBrick can highlight whether your runtime behavior aligns with these secure patterns by checking whether Bearer Tokens are used with unvalidated endpoints and whether outbound calls are constrained.