security_rust/injection/
ldap_injection.rs1use crate::{regex_detect, AttackCategory, DetectionResult, Detector, Severity};
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(&PATTERNS, self.name(), AttackCategory::Injection, Severity::High, "LDAP injection detected", input)
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35
36 fn det() -> LdapInjectionDetector {
37 LdapInjectionDetector
38 }
39
40 fn assert_hit(input: &str) {
41 crate::test_helpers::assert_detected(
42 &det(),
43 input,
44 AttackCategory::Injection,
45 Severity::High,
46 );
47 }
48
49 #[test]
50 fn name_is_ldap_injection() {
51 assert_eq!(det().name(), "ldap_injection");
52 }
53
54 #[test]
55 fn detects_common_payloads() {
56 for input in [
57 "(&(uid=admin)(!(|(cn=*))))",
58 "(&(cn=user))",
59 "(|(cn=admin))",
60 "*(cn=*)",
61 "(!(uid=*))",
62 "(objectClass=*)",
63 ")(&(uid=admin))",
64 ] {
65 assert_hit(input);
66 }
67 }
68
69 #[test]
70 fn benign_inputs_not_detected() {
71 for input in [
72 "Hello, this is a normal text input. Nothing suspicious here.",
73 "Please enter your username and password",
74 "The directory contains user records",
75 "uid=admin",
76 "cn=test",
77 ] {
78 assert!(det().detect(input).is_none(), "false positive: {input}");
79 }
80 }
81
82 #[test]
83 fn edge_cases() {
84 assert!(det().detect("").is_none());
85 assert!(det().detect(" \t\n ").is_none());
86 assert!(det().detect("你好世界 こんにちは").is_none());
87 assert!(det().detect("(uidadmin)").is_none());
89 assert!(det().detect("(xuid=1)").is_none());
90 assert!(det().detect("user (uid) admin").is_none());
91 }
92
93 #[test]
94 fn obfuscated_variants_detected() {
95 for input in ["(&(UID=admin))", "( uid =*)", "( cn = * )"] {
96 assert_hit(input);
97 }
98 }
99}