Ldap Injection in Actix with Basic Auth
Ldap Injection in Actix with Basic Auth — how this specific combination creates or exposes the vulnerability
LDAP Injection occurs when user-supplied input is concatenated into an LDAP query without proper validation or escaping. In Actix applications that use HTTP Basic Auth, the username and password extracted from the Authorization header are sometimes reused in backend LDAP calls—for example, to validate group membership or to build a user-specific search filter. When the application does not sanitize these inputs, an attacker can inject LDAP metacharacters such as (, ), &, |, *, or ~ into the credential values, altering the LDAP query logic.
Consider an Actix service that receives a Basic Auth header, extracts the credentials, and then performs an LDAP bind or search using string interpolation. An attacker can supply a username like admin)(uid=*) or a password containing (objectClass=*). These characters change the structure of the LDAP filter, potentially bypassing authentication, enumerating users via partial matches, or extracting attributes depending on the server’s access controls. Because the scan checks Authentication and Input Validation in parallel, findings may surface both an authentication bypass risk and unchecked special characters in credential fields.
In a black-box scenario—where the scanner tests the unauthenticated attack surface—the tool can probe the login or user-info endpoint with crafted Basic Auth strings containing LDAP metacharacters and observe whether the server responds differently (e.g., different status codes, timing differences, or data leakage). Because the credentials flow through the application into LDAP, the attack chain ties together the Authentication check (weak or misapplied auth mechanisms), Input Validation (lack of sanitization or parameterization), and Data Exposure (potential disclosure of user attributes or authentication results). This illustrates why treating credentials as untrusted input is essential even when they are transmitted via Basic Auth.
Basic Auth-Specific Remediation in Actix — concrete code fixes
To mitigate LDAP Injection in Actix when using HTTP Basic Auth, treat the decoded username and password as untrusted input and avoid building LDAP queries through string concatenation. Use parameterized LDAP APIs or an LDAP filter escape mechanism provided by your LDAP client library. Below are concrete Actix examples showing a vulnerable approach and a hardened alternative.
Vulnerable Actix handler using Basic Auth and direct string interpolation
use actix_web::{web, HttpResponse, HttpRequest};
use ldap3::{LdapConnAsync, Scope, SearchEntry};
async fn auth_ldap(req: HttpRequest, body: web::Json) -> HttpResponse {
let credentials = req.headers().get("Authorization")
.and_then(|h| h.to_str().ok())
.and_then(|s| s.strip_prefix("Basic "))
.and_then(|encoded| base64::decode(encoded).ok())
.and_then(|b| String::from_utf8(b).ok());
if let Some(creds) = credentials {
let parts: Vec<&str> = creds.splitn(2, ':').collect();
if parts.len() == 2 {
let (username, password) = (parts[0], parts[1]);
// Vulnerable: directly embedding user input into LDAP filter
let filter = format!("(&(uid={})(userPassword={}))", username, password);
let (conn, mut ldap) = LdapConnAsync::new("ldap://ldap.example.com").await.unwrap();
let (res, _) = ldap.search("dc=example,dc=com", Scope::Subtree, &filter, vec!["uid"]).await.unwrap();
// ... handle result
}
}
HttpResponse::Unauthorized().finish()
}
Hardened Actix handler with input validation and parameterized LDAP filter
use actix_web::{web, HttpResponse, HttpRequest};
use ldap3::{LdapConnAsync, Scope, SearchEntry};
use ldap3::result::Result as LdapResult;
/// Basic Auth credentials should be validated and escaped before use in LDAP.
async fn auth_ldap_secure(req: HttpRequest) -> HttpResponse {
let credentials = req.headers().get("Authorization")
.and_then(|h| h.to_str().ok())
.and_then(|s| s.strip_prefix("Basic "))
.and_then(|encoded| base64::decode(encoded).ok())
.and_then(|b| String::from_utf8(b).ok());
let (username, password) = match credentials {
Some(creds) => {
let parts: Vec<&str> = creds.splitn(2, ':').collect();
if parts.len() != 2 { return HttpResponse::BadRequest().body("Invalid credentials"); }
(parts[0], parts[1])
},
None => return HttpResponse::Unauthorized().finish(),
};
// Validate input: allow only safe characters for LDAP identifiers
if !is_safe_username(username) || !is_safe_password(password) {
return HttpResponse::BadRequest().body("Invalid characters in credentials");
}
// Use parameterized construction or an escaping function provided by the LDAP library
let filter = format!("(&(uid={})(userPassword={}))",
ldap_escape::filter(&username),
ldap_escape::creds(&password)
);
let (conn, mut ldap) = match LdapConnAsync::new("ldap://ldap.example.com").await {
Ok(conn) => conn,
Err(_) => return HttpResponse::InternalServerError().finish(),
};
// Perform the search with the constructed filter; handle result safely
match ldap.search("dc=example,dc=com", Scope::Subtree, &filter, vec!["uid"]).await {
Ok((res, _)) => {
for entry in res.success().unwrap().entries {
let e: SearchEntry = SearchEntry::construct(entry);
// Process user data
}
HttpResponse::Ok().finish()
},
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
fn is_safe_username(s: &str) -> bool {
// Allow alphanumeric, underscore, hyphen; reject parentheses, asterisks, and other LDAP metacharacters
s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
}
fn is_safe_password(s: &str) -> bool {
// For passwords, prefer rejecting control characters and LDAP specials
!s.chars().any(|c| c.is_control() || "()*&|^~".contains(c))
}
Key remediation points include validating input against a strict allow-list, using library-supplied escaping for LDAP filters, and avoiding any direct interpolation of credentials into query strings. These practices reduce the risk of injection while preserving the functionality of authentication against an LDAP directory.