Adversarial Input in Axum
How Adversarial Input Manifests in Axum
Adversarial input refers to malicious data deliberately crafted to exploit vulnerabilities in an application's input handling. In Axum, a popular Rust web framework, adversarial input can manifest due to the framework's automatic extraction and lack of built-in validation. Axum's extractors (Json, Form, Path, Query, String) parse request data but impose no size limits or content validation. Common attack vectors include:
- Denial-of-Service via large JSON payloads: The
Jsonextractor will deserialize the entire body without a cap, enabling memory exhaustion. - Injection flaws: Extracted strings used directly in SQL queries, file paths, or shell commands can lead to SQL injection, path traversal, or command injection.
- Cross-site scripting (XSS): Reflecting extracted input in responses without encoding allows JavaScript injection.
- Server-side request forgery (SSRF): Using user-supplied URLs in outgoing requests without validation can force the server to access internal resources.
These vulnerabilities are not theoretical; they are commonly exploited. For example, CVE-2021-33574 (Rust std::fs::File::open path traversal) demonstrates the risk of unsanitized path parameters. In Axum, such issues arise when developer assumptions about input safety are violated.
Consider this vulnerable handler:
use axum::{
extract::{Json, Path},
routing::post,
Router,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
username: String,
bio: String,
}
async fn create_user(Json(user): Json) -> String {
// Vulnerable: directly using user input in a SQL query without parameterization
let query = format!("INSERT INTO users (username, bio) VALUES ('{}', '{}')", user.username, user.bio);
// execute query...
"User created".to_string()
}
async fn get_user(Path(id): Path) -> String {
// Vulnerable: using path parameter to read a file without validation
let path = format!("./users/{}.json", id);
std::fs::read_to_string(path).unwrap_or_else(|_| "User not found".to_string())
}
The create_user function is susceptible to SQL injection, while get_user suffers from path traversal.
Axum-Specific Detection
Detecting adversarial input vulnerabilities in Axum requires code review, dynamic testing, and automated scanning. Inspect each endpoint to ensure extracted data is validated and sanitized before use.
Dynamic testing involves sending malicious payloads (e.g., SQL injection strings, path traversal sequences) and observing responses for errors or data leakage.
middleBrick automates this with its black-box scanner. It probes your API with adversarial inputs and checks for vulnerability indicators across categories like Input Validation, Rate Limiting, and Data Exposure. For example, scanning https://api.example.com might reveal that /users/{id} is vulnerable to path traversal because a probe with id=../../../etc/passwd returned the server's passwd file. The report includes a risk score and prioritized remediation steps, mapped to OWASP API Top 10 and compliance frameworks.
Since middleBrick requires no credentials and scans in seconds, it's ideal for quickly assessing Axum APIs in development or CI/CD pipelines.
Axum-Specific Remediation
1. Enforce Request Body Size Limits
Prevent memory exhaustion by adding a request body size limit with tower_http::limit::RequestBodyLimitLayer:
use axum::Router;
use tower_http::limit::RequestBodyLimitLayer;
let app = Router::new()
.route("/users", post(create_user))
.layer(RequestBodyLimitLayer::new(1024)); // 1 KB
Oversized requests receive a 413 response.
2. Validate JSON Input
Use the validator crate to declaratively enforce constraints on JSON fields:
use validator::{Validate, ValidationError};
#[derive(Deserialize, Validate)]
struct CreateUser {
#[validate(length(min = 1, max = 50))]
username: String,
#[validate(length(max = 500))]
bio: String,
}
async fn create_user(Json(user): Json) -> Result {
user.validate().map_err(|e| (StatusCode::BAD_REQUEST, format!("{e:?}")))?;
"User created".to_string()
}
Limiting input length mitigates injection risks.
3. Sanitize Path Parameters
Validate path parameters with regex and avoid direct filesystem concatenation:
#[derive(Deserialize, Validate)]
struct UserPath {
#[validate(regex(path = "\\d+"))] // digits only
id: String,
}
async fn get_user(Path(path): Path) -> String {
let id = path.id;
// Use id safely, e.g., query database
// If filesystem access is needed, map id to a safe filename and canonicalize
format!("User {id} data")
}
Always canonicalize paths and ensure they stay within an allowed directory.
4. Avoid Command Injection
Never pass user input to shell commands. Use Command::new() with arg() and whitelist values:
async fn generate_report(Path(format): Path) -> Result {
let allowed = vec!["pdf", "csv"];
if !allowed.contains(&format.as_str()) {
return Err((StatusCode::BAD_REQUEST, "Invalid format".into()));
}
let output = Command::new("report-generator")
.arg("--format")
.arg(&format)
.output()?;
Ok(String::from_utf8(output.stdout).unwrap_or_default())
}
This prevents arbitrary command execution.
Additionally, consider applying these validations globally via middleware to ensure consistent protection across all endpoints. Rate limiting, for example, can be added with tower_http::limit::RateLimitLayer to prevent brute-force attacks.
Combining these techniques—size limits, validation, path safety, and safe command execution—significantly reduces adversarial input risks in Axum.