Sql Injection Union in Axum
How Sql Injection Union Manifests in Axum
In Axum applications, SQL injection via UNION attacks typically occurs when user input is concatenated directly into SQL query strings without proper parameterization. Axum's async handler functions often extract parameters from query strings, path segments, or JSON bodies, and if these values are passed unsanitized to a database client like sqlx or diesel via string formatting, attackers can inject UNION SELECT statements to extract additional data.
For example, consider an Axum endpoint that retrieves user profile data by ID:
use axum::extract::{Path, Query};
use axum::response::Json;
use sqlx::PgPool;
async fn get_user_profile(Path(user_id): Path, pool: Extension) -> Json {
let query = format!("SELECT * FROM users WHERE id = '{}'", user_id); // VULNERABLE
let user = sqlx::query_as::<_, User>(&query)
.fetch_one(&pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Json(json!(user))
}
If the user_id path parameter is controlled by the user, an attacker could supply 1' UNION SELECT password, email, NULL, NULL FROM admin_users--. The resulting query becomes:
SELECT * FROM users WHERE id = '1' UNION SELECT password, email, NULL, NULL FROM admin_users--'
The trailing comment (--) neutralizes the remainder of the original query. Since Axum handlers are often designed to return JSON, the UNION result—containing sensitive data like passwords or emails—is serialized and returned in the response, leading to data exposure. This pattern is common in Axum apps that manually construct SQL strings instead of using query builders or parameterized queries.
Axum-Specific Detection
Detecting UNION-based SQL injection in Axum requires analyzing both the application’s request handling and database interaction patterns. middleBrick identifies this vulnerability during its unauthenticated black-box scan by injecting sequential UNION probes and observing response anomalies.
During a scan, middleBrick sends payloads like:
' UNION SELECT NULL, NULL, NULL--' UNION SELECT @@version, NULL, NULL--' UNION SELECT table_name, NULL, NULL FROM information_schema.tables--
It then evaluates responses for signs of successful injection: changes in HTTP status code (e.g., 200 vs 404), presence of unexpected data in JSON bodies, or timing differences. For Axum specifically, middleBrick correlates input reflection points (e.g., where path or query parameters appear in responses) with database error messages or behavioral shifts.
For instance, if an Axum endpoint at /api/users/:id returns a 200 OK with JSON containing a version string like "PostgreSQL 14.5" when probed with /api/users/1' UNION SELECT @@version, NULL, NULL--, middleBrick flags this as a confirmed UNION-based SQL injection. The scanner also checks for blind UNION exploitation via boolean-based or time-based techniques when error messages are suppressed.
Because middleBrick requires no agents or configuration, simply providing the Axum API’s base URL (e.g., https://api.example.com) triggers these tests. The resulting report includes the vulnerable endpoint, the exact payload that succeeded, the severity (typically High or Critical), and remediation guidance aligned with OWASP API Security Top 10:2023 — API3:2023 Broken Object Property Authorization, which often overlaps with injection flaws in data retrieval endpoints.
Axum-Specific Remediation
Fixing UNION-based SQL injection in Axum applications involves eliminating string concatenation in SQL queries and adopting parameterized queries or type-safe query builders. The most effective approach uses sqlx with compile-time checked queries or diesel's query DSL, both of which prevent input from being interpreted as SQL syntax.
Refactor the vulnerable handler to use parameterized queries:
use axum::extract::{Path, Extension};
use axum::response::{Json, Result};
use sqlx::PgPool;
async fn get_user_profile(Path(user_id): Path, Extension(pool): Extension) -> Result, (StatusCode, String)> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", user_id)
.fetch_one(&pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Json(json!(user)))
}
Key changes:
- Changed
user_idtype fromStringtoi32— Axum’s extraction will reject non-numeric input at the handler level, returning 400 Bad Request before reaching the database. - Used
sqlx::query_as!macro with$1placeholder — ensures the input is treated strictly as a parameter, not executable SQL. - Removed string formatting (
format!), eliminating the injection vector.
If string input is unavoidable (e.g., usernames), always use parameterized queries:
async fn get_user_by_username(Path(username): Path, Extension(pool): Extension) -> Result, (StatusCode, String)> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = $1", username)
.fetch_optional(&pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
match user {
Some(u) => Ok(Json(json!(u))),
None => Err((StatusCode::NOT_FOUND, "User not found".to_string())),
}
}
For applications using an ORM like diesel, leverage its query builder:
use diesel::prelude::*;
use diesel::pg::PgConnection;
async fn get_user_profile(Path(user_id): Path, Extension(pool): Extension) -> Result, (StatusCode, String)> {
let mut conn = pool.get().await.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let user = web::block(move || {
users::table.filter(users::id.eq(user_id)).first::(&mut conn)
})
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Json(json!(user)))
}
These fixes ensure user input never alters query structure. Deploy the patched Axum service and rescan with middleBrick to confirm the vulnerability is resolved—the scanner will no longer detect successful UNION injection attempts.
Frequently Asked Questions
Can middleBrick detect UNION-based SQL injection in Axum apps that use generic error handling (e.g., returning 500 for all errors)?
UNION SELECT @@version might return a version string in a normally empty field, which middleBrick identifies as anomalous.Does validating input with Axum's extraction (e.g., parsing path parameters as integers) fully prevent UNION SQL injection?
Path blocking 1' UNION...), the request never reaches the database layer. However, validation must occur before any database interaction—using Axum’s extractors ensures this. Combine with parameterized queries for defense in depth.