use crate::traits::PayloadOracle;
pub struct LdapOracle;
const LDAP_OPERATORS: &[&str] = &["|", "&", "!"];
const LDAP_ATTRIBUTES: &[&str] = &[
"uid=",
"cn=",
"dn=",
"dc=",
"ou=",
"o=",
"mail=",
"objectClass=",
"objectclass=",
"memberOf=",
"userPassword=",
"sn=",
"givenName=",
];
const LDAP_WILDCARD: &str = "*";
fn has_ldap_structure(payload: &str) -> bool {
let lower = payload.to_ascii_lowercase();
let paren_open_count = payload.matches('(').count();
let paren_close_count = payload.matches(')').count();
let has_parentheses = paren_open_count > 0 && paren_close_count > 0;
let has_operator = LDAP_OPERATORS.iter().any(|op| payload.contains(*op));
let has_attribute = LDAP_ATTRIBUTES.iter().any(|attr| lower.contains(attr));
let has_wildcard = payload.contains(LDAP_WILDCARD);
has_parentheses && (has_operator || has_attribute || has_wildcard)
}
fn has_balanced_parentheses(payload: &str) -> bool {
let mut depth = 0i32;
for ch in payload.chars() {
match ch {
'(' => depth += 1,
')' => depth -= 1,
_ => {}
}
if depth < 0 {
return false; }
}
depth == 0
}
impl PayloadOracle for LdapOracle {
fn is_semantically_valid(&self, _original: &str, transformed: &str) -> bool {
if transformed.trim().is_empty() {
return false;
}
if !has_ldap_structure(transformed) {
return false;
}
has_balanced_parentheses(transformed)
}
fn name(&self) -> &'static str {
"LDAP"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uid_filter_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(uid=admin)", "(uid=admin)"));
}
#[test]
fn cn_filter_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(cn=admin)", "(cn=admin)"));
}
#[test]
fn boolean_or_filter_valid() {
let oracle = LdapOracle;
assert!(
oracle.is_semantically_valid("(|(uid=admin)(uid=root))", "(|(uid=admin)(uid=root))",)
);
}
#[test]
fn boolean_and_filter_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid(
"(&(uid=admin)(objectClass=person))",
"(&(uid=admin)(objectClass=person))",
));
}
#[test]
fn negation_filter_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(!(uid=admin))", "(!(uid=admin))",));
}
#[test]
fn wildcard_filter_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(uid=*)", "(uid=*)"));
assert!(oracle.is_semantically_valid("(cn=ad*)", "(cn=ad*)"));
assert!(oracle.is_semantically_valid("(mail=*@domain.com)", "(mail=*@domain.com)"));
}
#[test]
fn complex_nested_filter_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid(
"(&(|(uid=admin)(uid=root))(objectClass=person))",
"(&(|(uid=admin)(uid=root))(objectClass=person))",
));
}
#[test]
fn injection_bypass_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(uid=admin)(|(uid=*))", "(uid=admin)(|(uid=*))",));
}
#[test]
fn objectclass_filter_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(objectClass=*)", "(objectClass=*)",));
}
#[test]
fn empty_string_invalid() {
let oracle = LdapOracle;
assert!(!oracle.is_semantically_valid("(uid=admin)", ""));
}
#[test]
fn plain_text_invalid() {
let oracle = LdapOracle;
assert!(!oracle.is_semantically_valid("(uid=admin)", "hello world"));
}
#[test]
fn missing_parentheses_invalid() {
let oracle = LdapOracle;
assert!(!oracle.is_semantically_valid("(uid=admin)", "uid=admin"));
}
#[test]
fn unbalanced_parens_invalid() {
let oracle = LdapOracle;
assert!(!oracle.is_semantically_valid("(uid=admin)", "((uid=admin)"));
assert!(!oracle.is_semantically_valid("(uid=admin)", "(uid=admin))"));
}
#[test]
fn encoded_operators_still_valid() {
let oracle = LdapOracle;
assert!(
oracle.is_semantically_valid("(|(uid=admin)(uid=root))", "(%7C(uid=admin)(uid=root))",)
);
}
#[test]
fn case_insensitive_attributes() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(uid=admin)", "(UID=admin)"));
assert!(oracle.is_semantically_valid("(cn=admin)", "(CN=admin)"));
assert!(oracle.is_semantically_valid("(objectClass=*)", "(objectclass=*)"));
}
#[test]
fn mail_attribute_valid() {
let oracle = LdapOracle;
assert!(
oracle.is_semantically_valid("(mail=admin@example.com)", "(mail=admin@example.com)",)
);
}
#[test]
fn dc_attribute_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(dc=example,dc=com)", "(dc=example,dc=com)",));
}
#[test]
fn dn_attribute_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(dn=cn=admin,dc=com)", "(dn=cn=admin,dc=com)",));
}
#[test]
fn memberof_attribute_valid() {
let oracle = LdapOracle;
assert!(
oracle.is_semantically_valid(
"(memberOf=cn=admins,dc=com)",
"(memberOf=cn=admins,dc=com)",
)
);
}
#[test]
fn ou_attribute_valid() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(ou=users)", "(ou=users)",));
}
#[test]
fn adversarial_unicode_injection() {
let oracle = LdapOracle;
assert!(!oracle.is_semantically_valid(
"(uid=admin)",
"(uid=admin)", ));
}
#[test]
fn adversarial_null_byte() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(uid=admin)", "(uid=admin)\x00",));
}
#[test]
fn adversarial_comment_injection() {
let oracle = LdapOracle;
assert!(oracle.is_semantically_valid("(uid=admin)", "(uid=admin)/*comment*/",));
}
#[test]
fn oracle_name_is_ldap() {
let oracle = LdapOracle;
assert_eq!(oracle.name(), "LDAP");
}
}