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