Fix Command Injection in Axum
Command injection in Axum occurs when user-controlled input is concatenated into system commands and executed via a shell. In Rust, 'std::process::Command' is safe by default as it doesn't invoke a shell, but developers often break this security boundary by explicitly calling 'sh -c' or 'cmd.exe /c' to leverage shell features. This allows attackers to chain commands using characters like ';', '&', or '|'.
The Vulnerable Pattern
use axum::{extract::Query, response::IntoResponse}; use std::collections::HashMap; use std::process::Command;async fn handler(Query(params): Query<HashMap<String, String>>) -> impl IntoResponse { let user_input = params.get(“filename”).unwrap(); // VULNERABLE: Using ‘sh -c’ with string interpolation allows command chaining let output = Command::new(“sh”) .arg(“-c”) .arg(format!(“ls -l contents/{}”, user_input)) .output() .expect(“failed to execute process”);
String::from_utf8_lossy(&output.stdout).to_string()
}
The Secure Implementation
The fix eliminates the shell intermediary. By calling 'Command::new("ls")' directly instead of 'sh -c', the OS treats the user input as a literal argument vector rather than a string to be parsed for shell metacharacters. Even if an attacker passes '; rm -rf /', the system will simply look for a file named '; rm -rf /' rather than executing the command. As a secondary defense, always validate input against an allowlist or use Rust's filesystem APIs instead of external binaries where possible.
use ax_um::{extract::Query, response::IntoResponse, http::StatusCode}; use std::collections::HashMap; use std::process::Command;async fn handler(Query(params): Query<HashMap<String, String>>) -> impl IntoResponse { let user_input = match params.get(“filename”) { Some(val) => val, None => return (StatusCode::BAD_REQUEST, “Missing param”).into_response(), };
// SECURE: 1. Avoid the shell wrapper. Call the binary directly. // SECURE: 2. Input is passed as a literal argument, not parsed by a shell. let output = Command::new("ls") .arg("-l") .arg(format!("contents/{}", user_input)) .output(); match output { Ok(o) => String::from_utf8_lossy(&o.stdout).to_string().into_response(), Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Execution failed").into_response(), }
}
Your Axum API
might be exposed to Command Injection
74% of Axum apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.
Free Tier • No Credit Card • Instant Report
Verified by Ghost Labs Security Team
This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.