Integer Overflow in Actix with Jwt Tokens
Integer Overflow in Actix with Jwt Tokens
Integer overflow in Actix web applications becomes critical when handling JWT token metadata such as expiration (exp), not-before (nbf), or issued-at (iat) claims represented as NumericDate (seconds since epoch). In Rust, arithmetic used to compute token lifetimes or validate time windows can overflow a u32 or u64 when values are cast, added, or multiplied, leading to an invalidation of security boundaries. An attacker can supply a crafted token with an extreme exp value that, when combined with arithmetic in application code, wraps to a seemingly valid timestamp in the past, bypassing expiration checks and enabling token replay or long-term access beyond intended scope.
Consider an Actix handler that computes a session TTL by adding a user-supplied lifetime to the current Unix time:
use actix_web::{web, HttpResponse};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
}
// Risky: adding user-supplied lifetime as usize can overflow
async fn login_handler(
body: web::Json<Claims>,
) -> HttpResponse {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as usize;
// If body.lifetime is large, `now + body.lifetime` may overflow in debug builds or produce wrong values in release
let exp = now + body.lifetime; // potential usize overflow
let token = encode(
&Header::default(),
&Claims { sub: body.sub.clone(), exp },
&EncodingKey::from_secret("secret".as_ref()),
);
match token {
Ok(t) => HttpResponse::Ok().body(t),
Err(_) => HttpResponse::BadRequest().finish(),
}
}
If body.lifetime is large (e.g., near usize::MAX), the addition wraps, producing a small exp that appears valid. During verification, the token may be accepted despite being expired or not-yet-valid, undermining the intended time-bound access control. This pattern is especially problematic when combined with unchecked deserialization or when using smaller integer types (u32) for legacy token parsers. The vulnerability maps to OWASP API Top 10:2023 API1:2023 Broken Object Level Authorization when overflow leads to authorization bypass, and it can be flagged by spec-aware scans that cross-reference OpenAPI definitions with runtime behavior.
middleBrick detects such risks by analyzing the unauthenticated attack surface and cross-referencing spec definitions with observed arithmetic patterns. In the context of JWT handling, findings may highlight missing bounds checks or unsafe numeric conversions, providing remediation guidance rather than attempting to fix or block traffic. This aligns with the platform’s focus on detection and reporting, while tools like the CLI (middlebrick scan <url>) or GitHub Action can integrate these checks into development workflows.
Jwt Tokens-Specific Remediation in Actix
Remediation centers on preventing integer overflow during time calculations and ensuring strict validation of JWT claims. Use checked arithmetic (e.g., checked_add) and prefer u64 for epoch-based timestamps to avoid wrapping. Validate exp, nbf, and iat against a trusted clock and enforce reasonable bounds relative to the token issuance time.
Below is a secure Actix handler example that avoids overflow and validates token metadata:
use actix_web::{web, HttpResponse};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, Header};
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
nbf: usize,
iat: usize,
}
fn current_unix_secs() -> Option {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.map(|d| d.as_secs())
}
// Safe: uses checked arithmetic and proper validation
async fn login_handler(
body: web::Json<Claims>,
) -> HttpResponse {
let now = match current_unix_secs() {
Some(t) => t,
None => return HttpResponse::InternalServerError().finish(),
};
// Validate lifetime bounds before use
let lifetime = body.lifetime;
if lifetime == 0 || lifetime > 30_000 { // e.g., max 30 seconds for this example policy
return HttpResponse::BadRequest().body("invalid lifetime");
}
// Use checked addition to prevent overflow
let exp = match now.checked_add(lifetime as u64) {
Some(t) => t,
None => return HttpResponse::BadRequest().body("arithmetic overflow"),
};
let nbf = now;
let iat = now;
let token = match encode(
&Header::default(),
&Claims { sub: body.sub.clone(), exp: exp as usize, nbf: nbf as usize, iat: iat as usize },
&EncodingKey::from_secret("secret".as_ref()),
) {
Ok(t) => t,
Err(_) => return HttpResponse::BadRequest().finish(),
};
// Verify with strict validation
let mut validation = Validation::new(Algorithm::HS256);
validation.validate_exp = true;
validation.validate_nbf = true;
validation.validate_iat = true;
match decode::<Claims>(&token, &DecodingKey::from_secret("secret".as_ref()), &validation) {
Ok(token_data) => {
// Additional business checks, e.g., max session window
if token_data.claims.exp.saturating_sub(token_data.claims.iat) > 30_000 {
return HttpResponse::BadRequest().body("token window too large");
}
HttpResponse::Ok().body(token_data.claims.sub)
}
Err(_) => HttpResponse::Unauthorized().finish(),
}
}
Key practices:
- Use
checked_addor similar safe arithmetic to avoid wrapping. - Enforce upper bounds on lifetimes and reject tokens with exp/nbf/iat values that fall outside acceptable windows.
- Prefer u64 for epoch arithmetic and convert to usize only when interfacing with APIs that require it, ensuring lossless conversions.
- Leverage the jsonwebtoken crate’s validation flags and perform additional business checks after decoding.
These measures reduce the risk of token validation bypass due to numeric overflow and align with secure coding guidelines for time-based claims.