pub const ASREP_CANDIDATES: &str = "(userAccountControl:1.2.840.113556.1.4.803:=4194304)";
pub const SPN_ACCOUNTS: &str =
"(&(servicePrincipalName=*)(!(objectClass=computer))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";
pub const UNCONSTRAINED_DELEGATION: &str =
"(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))";
pub fn constrained_delegation() -> &'static str {
"(|(msDS-AllowedToDelegateTo=*)(userAccountControl:1.2.840.113556.1.4.803:=1048576))"
}
pub const RBCD: &str = "(msDS-AllowedToActOnBehalfOfOtherIdentity=*)";
pub fn description_leak(keyword: &str) -> String {
format!("(&(objectCategory=person)(description=*{}*))", keyword)
}
pub fn group_by_cn(group_cn: &str) -> String {
format!("(&(objectClass=group)(cn={}))", group_cn)
}
pub fn group_members_recursive(group_dn: &str) -> String {
format!(
"(&(objectCategory=person)(memberOf:1.2.840.113556.1.4.1941:={}))",
group_dn
)
}
pub fn stale_service_passwords(timestamp_threshold: i64) -> String {
format!(
"(&(servicePrincipalName=*)(pwdLastSet<={})(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(objectClass=computer)))",
timestamp_threshold
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_asrep_candidates_contains_bit_pattern() {
assert!(ASREP_CANDIDATES.contains("4194304"));
assert!(ASREP_CANDIDATES.contains("1.2.840.113556.1.4.803"));
}
#[test]
fn test_spn_accounts_filter_structure() {
assert!(SPN_ACCOUNTS.contains("servicePrincipalName"));
assert!(SPN_ACCOUNTS.contains("objectClass=computer"));
assert!(SPN_ACCOUNTS.contains("userAccountControl"));
}
#[test]
fn test_unconstrained_delegation_filter() {
assert!(UNCONSTRAINED_DELEGATION.contains("524288"));
assert!(UNCONSTRAINED_DELEGATION.contains("computer"));
}
#[test]
fn test_rbcd_filter_is_not_empty() {
assert!(!RBCD.is_empty());
assert!(RBCD.contains("msDS-AllowedToActOnBehalfOfOtherIdentity"));
}
#[test]
fn test_constrained_delegation_generates_valid_ldap() {
let filter = constrained_delegation();
assert!(filter.contains("msDS-AllowedToDelegateTo"));
assert!(filter.contains("1048576")); assert!(filter.starts_with("(|")); }
#[test]
fn test_description_leak_escapes_input() {
let filter = description_leak("password");
assert!(filter.contains("description=*password*"));
assert!(filter.contains("objectCategory=person"));
}
#[test]
fn test_description_leak_special_chars() {
let filter = description_leak("test*");
assert!(filter.contains("test*")); }
#[test]
fn test_group_by_cn_filter() {
let filter = group_by_cn("Domain Admins");
assert!(filter.contains("Domain Admins"));
assert!(filter.contains("objectClass=group"));
}
#[test]
fn test_group_members_recursive_uses_in_chain() {
let filter = group_members_recursive("CN=DA,OU=Groups,DC=corp,DC=local");
assert!(filter.contains("1.2.840.113556.1.4.1941")); assert!(filter.contains("CN=DA,OU=Groups,DC=corp,DC=local"));
assert!(filter.contains("objectCategory=person"));
}
#[test]
fn test_stale_service_passwords_filter() {
let threshold = 130680960000000000i64; let filter = stale_service_passwords(threshold);
assert!(filter.contains("servicePrincipalName=*"));
assert!(filter.contains(&threshold.to_string()));
assert!(filter.contains("pwdLastSet<=")); assert!(filter.contains("objectClass=computer")); }
#[test]
fn test_all_filters_start_with_open_paren() {
assert!(ASREP_CANDIDATES.starts_with("("));
assert!(SPN_ACCOUNTS.starts_with("("));
assert!(UNCONSTRAINED_DELEGATION.starts_with("("));
assert!(RBCD.starts_with("("));
}
#[test]
fn test_all_filters_end_with_close_paren() {
assert!(ASREP_CANDIDATES.ends_with(")"));
assert!(SPN_ACCOUNTS.ends_with(")"));
assert!(UNCONSTRAINED_DELEGATION.ends_with(")"));
assert!(RBCD.ends_with(")"));
}
#[test]
fn test_filter_basic_syntax_balance() {
fn count_parens(s: &str) -> (i32, i32) {
let opens = s.chars().filter(|&c| c == '(').count();
let closes = s.chars().filter(|&c| c == ')').count();
(opens as i32, closes as i32)
}
for (name, filter) in &[
("ASREP", ASREP_CANDIDATES),
("SPN", SPN_ACCOUNTS),
("UNCONSTRAINED", UNCONSTRAINED_DELEGATION),
("RBCD", RBCD),
] {
let (opens, closes) = count_parens(filter);
assert_eq!(opens, closes, "{} filter has unbalanced parens", name);
}
}
}