Ldap Injection in Actix with Mutual Tls
Ldap Injection in Actix with Mutual Tls — how this specific combination creates or exposes the vulnerability
Ldap Injection occurs when user-controlled input is concatenated into an LDAP query without proper validation or escaping. In an Actix web service that performs LDAP binds or searches, if the application builds filter strings by directly interpolating parameters such as uid or mail, an attacker can supply crafted input like (uid=*)(objectClass=*) to change query semantics or bypass authentication.
Mutual Transport Layer Security (Mutual TLS) ensures both client and server present valid certificates. This strongly authenticates the client to Actix and protects channel integrity, but it does not constrain how the application uses the client identity after the TLS handshake. A common pattern is to extract the client certificate’s subject or SAN and use it to build an LDAP filter, for example searching for an entry that matches the presented certificate. Because Mutual TLS provides identity assurance at the network layer, developers may mistakenly assume the identity is safe to embed directly in LDAP queries, leading to insecure concatenation and an Ldap Injection surface.
Consider an Actix handler that receives a client certificate, extracts the Common Name (CN), and builds an LDAP filter without sanitization:
use actix_web::{web, HttpResponse, Result};
use ldap3::{LdapConnAsync, Scope, SearchEntry};
async fn login_by_client_cert(cert_subject: String) -> Result {
// Dangerous: directly embedding cert_subject into LDAP filter
let filter = format!("(&(objectClass=person)(cn={}))");
let (conn, mut ldap) = LdapConnAsync::new("ldap://ldap.example.com").await?;
let (res, _) = ldap.search(
"dc=example,dc=com",
Scope::Subtree,
&filter,
vec!["mail"]
).await?;
// ... process res
Ok(HttpResponse::Ok().finish())
}
If an attacker’s certificate contains CN value (uid=*)(objectClass=*), the resulting filter becomes (&(objectClass=person)(cn=(uid=*)(objectClass=*))), which may bypass intended matching rules or return unintended entries. This is Ldap Injection in the context of Actix with Mutual TLS: the secure client authentication does not sanitize or parameterize the identity when constructing LDAP queries.
In addition to search filters, bind DN construction is another risk area. If the application uses certificate attributes to assemble a bind Distinguished Name (DN), unsanitized input can change the target entry or enable traversal beyond intended directory subtrees. For example:
let bind_dn = format!("cn={},ou=people,dc=example,dc=com", cert_subject);
An input such as CN=foo,OU=../../..,DC=evilcom could redirect the bind to a different branch. Because Mutual TLS only authenticates, the application must still treat identity inputs as untrusted data and apply strict validation, canonicalization, and parameterized queries.
Mutual Tls-Specific Remediation in Actix — concrete code fixes
Remediation focuses on never directly interpolating certificate-derived values into LDAP queries. Use parameterized searches where the LDAP library supports placeholders, or rigorously escape special LDAP filter characters. Treat certificate attributes as untrusted input and validate them against strict patterns before use.
1. Use parameterized filter building with ldap3’s filter builder to avoid concatenation:
use ldap3::{LdapConnAsync, Scope, SearchEntry, filter::Filter};
use actix_web::Result;
async fn login_by_client_cert_safe(cert_subject: String) -> Result {
// Validate CN format before using it
let cn = validate_cn(&cert_subject)?;
let filter = Filter::and(vec![
Filter::present("objectClass".to_string(), "person".to_string()),
Filter::equality("cn".to_string(), cn),
]);
let (conn, mut ldap) = LdapConnAsync::new("ldap://ldap.example.com").await?;
let (res, _) = ldap.search(
"dc=example,dc=com",
Scope::Subtree,
filter,
vec!["mail"]
).await?;
// ... process res
Ok(HttpResponse::Ok().finish())
}
fn validate_cn(input: &str) -> Result {
// Allow only safe characters and enforce length limits
let re = regex::Regex::new(r"^[A-Za-z0-9._-]{1,64}$").unwrap();
if re.is_match(input) {
Ok(input.to_string())
} else {
Err(actix_web::error::ErrorBadRequest("Invalid certificate subject"))
}
}
2. If your LDAP library lacks a filter builder, escape special characters according to RFC 4515 before concatenation:
fn escape_ldap_filter(s: &str) -> String {
s.replace("\\", "\\5c")
.replace("*", "\\2a")
.replace("(", "\\28")
.replace(")", "\\29")
.replace("\x00", "\\00")
}
async fn login_with_escape(cert_subject: String) -> Result {
let safe_cn = escape_ldap_filter(&cert_subject);
let filter = format!("(&(objectClass=person)(cn={}))");
let (conn, mut ldap) = LdapConnAsync::new("ldap://ldap.example.com").await?;
let (res, _) = ldap.search(
"dc=example,dc=com",
Scope::Subtree,
&filter,
vec!["mail"]
).await?;
// ... process res
Ok(HttpResponse::Ok().finish())
}
3. For bind DN construction, validate and canonicalize the DN components, avoid concatenating raw paths, and prefer binding with a mapped local account rather than dynamic DN assembly when possible:
fn safe_bind_dn(cn: &str) -> Result {
let cn = validate_cn(cn)?;
Ok(format!("cn={},ou=people,dc=example,dc=com", cn))
}
async fn bind_with_safe_dn(cert_subject: String) -> Result {
let bind_dn = safe_bind_dn(&cert_subject)?;
let (conn, mut ldap) = LdapConnAsync::new("ldap://ldap.example.com").await?;
// Use simple bind with constructed DN and certificate credentials
ldap.simple_bind(&bind_dn, "").await?;
Ok(HttpResponse::Ok().finish())
}
These patterns ensure that Mutual TLS provides authentication while the application treats identity inputs as untrusted data, preventing Ldap Injection in Actix services.