security_rust/injection/
ldap_injection.rs1use crate::{AttackCategory, DetectionResult, Detector, Severity, regex_detect};
4use regex::Regex;
5use std::sync::LazyLock;
6
7static PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
8 vec![
9 Regex::new(r"\(\s*&").unwrap(),
10 Regex::new(r"\(\s*\|").unwrap(),
11 Regex::new(r"\(!\s*\(").unwrap(),
12 Regex::new(r"\*\(cn=").unwrap(),
13 Regex::new(r"\(\s*objectClass\s*=").unwrap(),
14 Regex::new(r"\(\s*uid\s*=").unwrap(),
15 Regex::new(r"\)\s*\((?:&|\||!)").unwrap(),
16 Regex::new(r"\(\s*cn\s*=").unwrap(),
17 ]
18});
19
20pub struct LdapInjectionDetector;
21
22impl Detector for LdapInjectionDetector {
23 fn name(&self) -> &'static str {
24 "ldap_injection"
25 }
26
27 fn detect(&self, input: &str) -> Option<DetectionResult> {
28 regex_detect(
29 &PATTERNS,
30 self.name(),
31 AttackCategory::Injection,
32 Severity::High,
33 "LDAP injection detected",
34 input,
35 )
36 }
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 fn det() -> LdapInjectionDetector {
44 LdapInjectionDetector
45 }
46
47 fn assert_hit(input: &str) {
48 crate::test_helpers::assert_detected(
49 &det(),
50 input,
51 AttackCategory::Injection,
52 Severity::High,
53 );
54 }
55
56 #[test]
57 fn name_is_ldap_injection() {
58 assert_eq!(det().name(), "ldap_injection");
59 }
60
61 #[test]
62 fn detects_common_payloads() {
63 for input in [
64 "(&(uid=admin)(!(|(cn=*))))",
65 "(&(cn=user))",
66 "(|(cn=admin))",
67 "*(cn=*)",
68 "(!(uid=*))",
69 "(objectClass=*)",
70 ")(&(uid=admin))",
71 ] {
72 assert_hit(input);
73 }
74 }
75
76 #[test]
77 fn benign_inputs_not_detected() {
78 for input in [
79 "Hello, this is a normal text input. Nothing suspicious here.",
80 "Please enter your username and password",
81 "The directory contains user records",
82 "uid=admin",
83 "cn=test",
84 ] {
85 assert!(det().detect(input).is_none(), "false positive: {input}");
86 }
87 }
88
89 #[test]
90 fn edge_cases() {
91 assert!(det().detect("").is_none());
92 assert!(det().detect(" \t\n ").is_none());
93 assert!(det().detect("你好世界 こんにちは").is_none());
94 assert!(det().detect("(uidadmin)").is_none());
96 assert!(det().detect("(xuid=1)").is_none());
97 assert!(det().detect("user (uid) admin").is_none());
98 }
99
100 #[test]
101 fn obfuscated_variants_detected() {
102 for input in ["(&(UID=admin))", "( uid =*)", "( cn = * )"] {
103 assert_hit(input);
104 }
105 }
106}