Formula Injection in Axum with Jwt Tokens
Formula Injection in Axum with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Formula Injection in Axum involving JWT tokens occurs when untrusted input from JWT claims is interpolated into templates, query strings, or configuration-like strings that are later evaluated or parsed by a downstream system. In Axum, this typically happens when a handler deserializes a JWT and uses a claim value (e.g., sub, email, or a custom role) to construct strings that are passed to external parsers or libraries expecting structured input.
For example, if a JWT contains a claim such as department=admin and the application builds a CSV-like string like format!("{},{},", user_id, department) without validation, an attacker who tampered with the token could inject newline or comma sequences to alter record parsing. More critically, if the application uses these values to build SQL fragments, shell commands, or LDAP filters—either directly or indirectly—formula injection can lead to logic bypass or unintended execution paths.
In Axum, JWTs are usually validated via middleware that decodes and verifies signatures. After validation, developers may extract claims and use them to construct responses or drive authorization logic. The risk arises when these claims are treated as safe and reused in contexts where syntax or structure matters. For instance, using a JWT role claim to dynamically select a feature flag or a data filter can allow an attacker to inject expressions that change evaluation order or escape intended constraints.
Consider an Axum handler that builds a dynamic filter string based on a JWT scope claim:
let scope = claims.get("scope").and_then(|v| v.as_str()).unwrap_or("read");
let filter = format!("resource_type={}", scope); // Unsafe if scope is attacker-controlled
If the scope claim contains read; drop table users;-- and the resulting string is used in a downstream parser that splits on semicolons, the injected command could alter behavior. While Axum itself does not execute this string, the integration point (e.g., a database or external service) might, creating a formula injection path.
Another scenario involves using JWT claims to construct redirect URLs or template contexts. If a claim is embedded into a Handlebars template or a query parameter without strict allowlisting, attackers can inject expressions that change rendering or navigation. For example, injecting {{7*7}} into a numeric claim field could trigger template logic injection if the rendering engine evaluates expressions.
Formula Injection with JWTs in Axum is therefore about controlling the flow of data from token claims into contexts where syntax has meaning. The vulnerability is not in JWT parsing itself but in how extracted values are reused. Mitigation requires strict input validation, avoid constructing executable or structured strings from claims, and prefer explicit mapping over dynamic evaluation.
Jwt Tokens-Specific Remediation in Axum — concrete code fixes
Remediation focuses on treating JWT claims as untrusted data and avoiding their direct use in contexts where formula or syntax interpretation occurs. In Axum, validate and transform claims into safe representations before any further use.
1. Use strongly typed, allowlisted claim values
Define an enum for expected roles or scopes and deserialize directly into that type. This prevents arbitrary strings from entering sensitive flows.
#[derive(Debug, Clone, PartialEq, Eq)]
enum Role {
Read,
Write,
Admin,
}
impl TryFrom<&str> for Role {
type Error = ();
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"read" => Ok(Role::Read),
"write" => Ok(Role::Write),
"admin" => Ok(Role::Admin),
_ => Err(()),
}
}
}
// In handler or extractor
let role_str = claims.get("role").and_then(|v| v.as_str()).ok_or_else(|| /* error */)?;
let role = Role::try_from(role_str).map_err(|_| /* invalid role */)?;
2. Avoid string interpolation for structured output
Instead of building CSV or key-value strings from claims, use serializers or explicit mappings.
use serde_json::json;
let user_id: u64 = claims.get("user_id").and_then(|v| v.as_u64()).ok_or_else(|| /* error */)?;
let role_str = claims.get("role").and_then(|v| v.as_str()).unwrap_or("read");
// Safe: structured JSON output
let payload = json!({
"user_id": user_id,
"role": role_str,
});
// Do not do: let csv = format!("{},{}
", user_id, role_str);
3. Encode or restrict values used in URLs and templates
If a claim must be used in a URL or template, apply strict encoding and allowlisting.
use axum::http::Uri;
use percent_encoding::percent_encode;
let raw = claims.get("org").and_then(|v| v.as_str()).unwrap_or("default");
let safe = percent_encode(raw.as_bytes(), percent_encoding::NON_ALPHANUMERIC).to_string();
let uri = format!("/org/{}/dashboard", safe);
// Use axum::Router::route with proper extractor instead of manual concatenation
4. Prefer extractor-based authorization
Leverage Axum extractors and middleware to enforce policies rather than constructing logic from claims.
use axum::{async_trait, extract::FromRequestParts};
struct AuthenticatedUser {
user_id: u64,
role: Role,
}
#[axum::async_trait]
impl FromRequestParts<S> for AuthenticatedUser
where
S: Send + Sync,
{
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
// Extract and validate JWT from parts extensions or headers
// Map claims to Role using allowlisted conversion
Ok(AuthenticatedUser { user_id: 1, role: Role::Read })
}
}
By combining strict claim validation, structured data handling, and avoiding dynamic formula construction from JWT values, you eliminate the injection path while preserving necessary functionality.