security_rust/protocol/
request_smuggling.rs1use regex::Regex;
4use std::sync::LazyLock;
5
6use crate::{regex_detect, AttackCategory, DetectionResult, Detector, Severity};
7
8static PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
9 vec![
10 Regex::new(r"(?i)Transfer-Encoding:.*\r\n.*Transfer-Encoding:").unwrap(),
11 Regex::new(r"(?i)Transfer-Encoding:[\s]*chunked").unwrap(),
12 ]
13});
14
15pub struct RequestSmugglingDetector;
16
17impl Detector for RequestSmugglingDetector {
18 fn name(&self) -> &'static str {
19 "request_smuggling"
20 }
21
22 fn detect(&self, input: &str) -> Option<DetectionResult> {
23 regex_detect(&PATTERNS, self.name(), AttackCategory::Protocol, Severity::High, "HTTP request smuggling detected", input)
24 }
25}
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30
31 fn assert_detected(input: &str) {
32 crate::test_helpers::assert_detected(
33 &RequestSmugglingDetector,
34 input,
35 AttackCategory::Protocol,
36 Severity::High,
37 );
38 }
39
40 fn assert_clean(input: &str) {
41 crate::test_helpers::assert_clean(&RequestSmugglingDetector, input);
42 }
43
44 #[test]
45 fn name_is_request_smuggling() {
46 assert_eq!(RequestSmugglingDetector.name(), "request_smuggling");
47 }
48
49 #[test]
50 fn detects_duplicate_transfer_encoding() {
51 assert_detected("Transfer-Encoding: chunked\r\nTransfer-Encoding: identity");
52 }
53
54 #[test]
55 fn detects_chunked_transfer_encoding() {
56 assert_detected("Transfer-Encoding: chunked");
57 assert_detected("Transfer-Encoding:chunked");
58 assert_detected("Transfer-Encoding:\tchunked");
59 }
60
61 #[test]
62 fn detects_mixed_case() {
63 assert_detected("transfer-encoding: CHUNKED");
64 }
65
66 #[test]
67 fn rejects_benign_headers() {
68 assert_clean("Content-Length: 5\r\nContent-Length: 10");
69 assert_clean("Transfer-Encoding: gzip");
70 assert_clean("Connection: keep-alive");
71 }
72
73 #[test]
74 fn rejects_missing_colon() {
75 assert_clean("Transfer-Encoding chunked");
76 }
77
78 #[test]
79 fn rejects_near_misses() {
80 assert_clean("Transfer-Encoding: chuncked");
81 }
82
83 #[test]
84 fn rejects_empty_and_whitespace() {
85 assert_clean("");
86 assert_clean(" ");
87 }
88
89 #[test]
90 fn rejects_unicode_text() {
91 assert_clean("普通请求体,无攻击特征");
92 }
93}