Header Injection in Actix with Jwt Tokens
Header Injection in Actix with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Header injection in Actix applications that rely on JWT tokens can occur when untrusted input is used to construct HTTP headers without validation or sanitization. Because JWT tokens are often passed in the Authorization header as a bearer token, any attempt to modify or inject additional headers can corrupt the token format or bypass intended authentication checks. This becomes particularly dangerous when the application dynamically builds headers using user-controlled data, such as query parameters or request headers that are forwarded without strict validation.
In an Actix web service, routes often extract the JWT from the Authorization header and validate its structure and signature. If an attacker can inject a newline or other control character into a header value, they may be able to add an additional Authorization header or other security-sensitive headers. For example, an input like Bearer abc123\nX-Forwarded-For: 10.0.0.1 could split the header processing and cause the server to treat a manipulated or missing token as valid. This violates the principle of a single, verifiable token and may allow an attacker to bypass authentication or elevate privileges depending on how the server handles duplicate or malformed headers.
The risk is compounded when the Actix service acts as a proxy or incorporates the token into other internal requests. An attacker might control a parameter that is concatenated into a header value, leading to malformed JWT handling or token confusion. Since JWT tokens contain structured claims, any alteration to the token string or surrounding headers can cause parsing errors or unexpected behavior, such as falling back to an unauthenticated context or misinterpreting the subject or roles claim. These parsing inconsistencies can lead to authorization flaws where an unauthenticated or low-privilege request is mistakenly accepted as authenticated.
Even when the server does not directly concatenate user input into the Authorization header, inconsistent header handling in middleware can expose issues. For instance, if an Actix middleware normalizes headers in a case-insensitive manner but preserves duplicates, an injected authorization header could shadow the intended bearer token. This can result in the server validating an attacker-controlled token while still believing it is processing the original JWT. Therefore, header injection risks with JWT tokens in Actix stem from insufficient input validation, unsafe header manipulation, and failure to enforce a single source of truth for authentication data.
Jwt Tokens-Specific Remediation in Actix — concrete code fixes
Remediation for JWT token header injection in Actix centers on strict input validation, canonical header handling, and avoiding any dynamic construction of security-sensitive headers. The application should treat the Authorization header as immutable after initial extraction and reject requests with multiple Authorization headers or malformed values. Below are concrete code examples demonstrating secure handling of JWT tokens in an Actix service.
Example 1: Strict Authorization header parsing
Ensure the Authorization header is present, uses the Bearer scheme, and contains no extra whitespace or newline characters. Reject the request if the header does not match the expected format.
use actix_web::{web, HttpRequest, Error, HttpResponse};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
role: String,
}
async fn validate_jwt(headers: &actix_web::http::HeaderMap) -> Result {
let auth = headers.get("authorization")
.ok_or("missing authorization header")?
.to_str()
.map_err(|_| "invalid authorization header encoding")?;
// Ensure exactly one Authorization header and strict Bearer format
if auth.split_whitespace().collect::>() != ["Bearer", ""] {
return Err("invalid authorization header format");
}
let token = auth.trim_start_matches("Bearer").trim();
if token.is_empty() {
return Err("missing token");
}
// Reject tokens containing control characters that may indicate injection
if token.chars().any(|c| c.is_control()) {
return Err("token contains invalid characters");
}
let token_data = decode::(
token,
&DecodingKey::from_secret(&[0u8; 32]),
&Validation::new(Algorithm::HS256),
)
.map_err(|_| "invalid token")?;
Ok(token_data)
}
async fn handler(req: HttpRequest) -> HttpResponse {
match validate_jwt(req.headers()) {
Ok(token_data) => HttpResponse::Ok().body(format!("user: {}", token_data.claims.sub)),
Err(e) => HttpResponse::Unauthorized().body(e),
Example 2: Rejecting duplicate headers and enforcing a single canonical header
Actix provides access to the raw header pairs. Use this to ensure there is only one Authorization header and that its value conforms to the expected pattern before any processing.
use actix_web::dev::ServiceRequest;
use actix_web::Error;
use std::collections::HashSet;
fn sanitize_headers(req: &ServiceRequest) -> Result<(), Error> {
let headers = req.headers();
let auth_headers: Vec<&(_, _)> = headers.iter().filter(|(k, _)| k.eq_ignore_ascii_case("authorization")).collect();
if auth_headers.len() != 1 {
return Err(actix_web::error::ErrorUnauthorized("multiple or missing authorization headers"));
}
let token = auth_headers[0].1.to_str().map_err(|_| actix_web::error::ErrorBadRequest("invalid header encoding"))?;
// Canonicalize by rejecting tokens with newlines or carriage returns
if token.contains('\r') || token.contains('\n') {
return Err(actix_web::error::ErrorBadRequest("malformed token detected"));
}
// Further checks can be applied here
Ok(())
}
// In a guard or middleware:
// let req = sanitize_headers(&req)?;
Example 3: Safe header forwarding in a proxy scenario
If the Actix service forwards requests to another backend, construct the outgoing header explicitly from a verified token rather than copying untrusted input headers.
use actix_web::http::header::HeaderValue;
use actix_web::web::Bytes;
fn build_authorization_header(token: &str) -> actix_web::http::HeaderValue {
// Ensure token does not contain newline or carriage return
if token.chars().any(|c| c == '\n' || c == '\r') {
panic!("invalid token");
}
let value = format!("Bearer {}", token);
HeaderValue::from_str(&value).expect("valid header value")
}
// When constructing an outbound request:
// let mut req = HttpRequest::default();
// req.headers_mut().insert("authorization", build_authorization_header(&verified_token));
General best practices
- Always validate the Authorization header format before extracting the token.
- Reject requests with multiple Authorization headers or malformed schemes.
- Canonicalize header names to a single case and ensure only one instance is processed.
- Treat JWT tokens as opaque strings for validation purposes; avoid parsing untrusted input beyond verifying signature and claims.
- Log and reject requests that contain control characters in headers to prevent injection attempts.