Ldap Injection in Axum with Basic Auth
Ldap Injection in Axum with Basic Auth — how this specific combination creates or exposes the vulnerability
Ldap Injection occurs when untrusted input is concatenated into LDAP queries without validation or escaping, allowing an attacker to manipulate the query structure. In Axum, building HTTP Basic Authentication handlers that forward credentials to an LDAP server can expose this risk if the username or password is used directly in constructing LDAP filter strings. For example, if the application receives a username from a request header and embeds it into an LDAP filter like (uid={username}), an attacker can supply crafted input such as admin)(objectClass=person) to change the filter logic, potentially bypassing authentication or extracting unintended entries.
When using Basic Auth in Axum, the typical flow involves extracting the credentials from the Authorization header, decoding the base64 payload, and splitting into username and password. If these values are passed to an LDAP bind or search without sanitization, the query becomes susceptible. Consider an Axum handler that builds a search filter by string interpolation:
let filter = format!("(uid={})", username);
An attacker sending username=admin)(uid=*) could turn the filter into (uid=admin)(uid=*)), which may return all entries depending on server configuration. This can lead to authentication bypass or information disclosure. The risk is compounded if the application also uses the password directly in an LDAP modify or extended operation, as special characters such as parentheses, asterisks, or backslashes can break the LDAP message encoding and introduce injection paths.
Additionally, if the Axum service performs account enumeration by conducting a search before bind, injection can be used to alter scope or filters. For instance, an attacker might inject )(objectClass=person)(uid=*) to change the search base or return larger datasets. Because LDAP servers interpret certain characters as metacharacters, inputs containing *, (, ), and \ must be escaped according to the LDAP specification. Failing to do so in an Axum handler that relies on Basic Auth for initial credential validation creates a clear injection surface.
Basic Auth-Specific Remediation in Axum — concrete code fixes
To mitigate Ldap Injection in Axum when using Basic Auth, you must treat credentials as untrusted data and avoid building LDAP queries via string concatenation. Use parameterized LDAP APIs or properly escape special characters before incorporating user input into filters. Below are concrete Axum code examples demonstrating a vulnerable approach and a secure remediation.
Vulnerable Axum handler (do not use):
use axum::{
async_trait,
extract::Request,
http::HeaderValue,
response::Response,
};
use ldap3::{Ldap, LdapConnAsync};
async fn auth_with_ldap(
username: String,
password: String,
) -> Result {
let (conn, mut ldap) = LdapConnAsync::new("ldap://ldap.example.com")
.map_err(|e| e.to_string())?;
// Vulnerable: direct interpolation into filter
let filter = format!("(uid={})", username);
let (rs, _res) = ldap.search(
"dc=example,dc=com",
ldap3::Scope::Subtree,
&filter,
vec!["dn"],
)
.await
.map_err(|e| e.to_string())?;
let dn = rs.first().and_then(|e| e.dn().to_owned().into());
if let Some(dn) = dn {
// Bind with password
ldap3::Ldap::bind(&mut ldap, &dn, &password).await.map_err(|e| e.to_string())?;
Ok(true)
} else {
Ok(false)
}
}
In this example, username is directly interpolated into the LDAP filter, enabling injection via crafted input.
Secure Axum handler with escaping and parameterized search:
use axum::{
extract::State,
http::Request,
response::Response,
};
use ldap3::{Ldap, LdapConnAsync, Scope, SearchEntry};
use ldap3_escape::LdapEscape;
struct LdapState {
ldap_url: String,
base_dn: String,
}
async fn secure_auth_handler(
State(state): State>,
request: Request,
) -> Response {
// Extract Basic Auth credentials (omitted for brevity)
let credentials = match extract_basic_auth(&request) {
Ok(c) => c,
Err(_) => return Response::builder().status(401).body("Unauthorized".into()),
};
let username = credentials.0;
let _password = credentials.1;
let (conn, mut ldap) = match LdapConnAsync::new(&state.ldap_url).await {
Ok((c, l)) => (c, l),
Err(_) => return Response::builder().status(500).body("LDAP connection failed".into()),
};
// Secure: escape username for use in LDAP filter
let safe_username = LdapEscape::new_filter(&username);
let filter = format!("(uid={})", safe_username);
match ldap.search(
&state.base_dn,
Scope::Subtree,
&filter,
vec!["dn"],
)
.await
{
Ok((rs, _res)) => {
let entries: Vec = SearchEntry::construct_collection(rs);
if let Some(entry) = entries.first() {
let dn = entry.dn.clone();
// Proceed to bind with password using ldap3::Ldap::bind
// Additional validation and rate limiting recommended
}
Response::builder().status(200).body("Authenticated".into())
}
Err(_) => Response::builder().status(500).body("Search failed".into()),
}
}
fn extract_basic_auth(request: &Request) -> Result<(String, String), ()> {
// Implementation omitted for brevity; parse Authorization header
unimplemented!()
}
Key remediation steps include:
- Use an LDAP escaping library (e.g.,
ldap3-escape) to sanitizeusernamebefore placing it into filters. - Avoid using password values directly in LDAP queries; reserve them for the bind step after successful search.
- Prefer parameterized APIs where available, or ensure strict input validation (e.g., allow only alphanumeric usernames) to reduce injection surface.
These practices reduce the risk of Ldap Injection while preserving the use of Basic Auth within Axum handlers.