Skip to main content

security_rust/injection/
xpath_injection.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3use 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)\s*or\s*'1'\s*=\s*'1").unwrap(),
10        Regex::new(r"'(?i)\s*and\s*'1'\s*=\s*'2").unwrap(),
11        Regex::new(r"'(?i)\s*or\s*1\s*=\s*1").unwrap(),
12        Regex::new(r#""(?i)\s*or\s*"1"\s*=\s*"1"#).unwrap(),
13        Regex::new(r"'\s*\]\s*\|\s*").unwrap(),
14        Regex::new(r"'(?i)\s*or\s*''='").unwrap(),
15        Regex::new(r"'(?i)\s*or\s*true\s*\(").unwrap(),
16    ]
17});
18
19pub struct XPathInjectionDetector;
20
21impl Detector for XPathInjectionDetector {
22    fn name(&self) -> &'static str {
23        "xpath_injection"
24    }
25
26    fn detect(&self, input: &str) -> Option<DetectionResult> {
27        regex_detect(&PATTERNS, self.name(), AttackCategory::Injection, Severity::High, "XPATH injection detected", input)
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    fn det() -> XPathInjectionDetector {
36        XPathInjectionDetector
37    }
38
39    fn assert_hit(input: &str) {
40        crate::test_helpers::assert_detected(
41            &det(),
42            input,
43            AttackCategory::Injection,
44            Severity::High,
45        );
46    }
47
48    #[test]
49    fn name_is_xpath_injection() {
50        assert_eq!(det().name(), "xpath_injection");
51    }
52
53    #[test]
54    fn detects_common_payloads() {
55        for input in [
56            "' or '1'='1",
57            "' or 1=1",
58            "' and '1'='2",
59            r#"" or "1"="1"#,
60            "' or ''='",
61            "' or true()",
62            "']|//admin",
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 first quarter results are in",
73            "or is a conjunction in English",
74            "I want 1 pizza and 1 drink",
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        // near misses: quote missing or condition not satisfied
86        assert!(det().detect("or 1=1").is_none());
87        assert!(det().detect("' or '2'='1").is_none());
88        assert!(det().detect("' or 2=2").is_none());
89    }
90
91    #[test]
92    fn obfuscated_variants_detected() {
93        for input in ["' OR '1'='1", "' And '1'='2", r#"" Or "1"="1"#] {
94            assert_hit(input);
95        }
96    }
97}