path_security/
attacks.rs

1//! Attack pattern detection for path security
2
3use anyhow::{bail, Result};
4
5use crate::constants::*;
6
7/// Detect Windows-specific attacks
8pub fn detect_windows_attacks(path: &str) -> Result<()> {
9    // Check for NTFS alternate data streams
10    // For relative paths, ANY colon is suspicious (either NTFS stream or drive letter)
11    if path.contains(':') {
12        // Check if it's a drive letter at the start (which would make it absolute, caught elsewhere)
13        let starts_with_drive = path.len() >= 2 && 
14                                path.chars().nth(0).map_or(false, |c| c.is_ascii_alphabetic()) &&
15                                path.chars().nth(1) == Some(':');
16        
17        if !starts_with_drive {
18            // Any other colon is suspicious (NTFS streams, etc.)
19            bail!("Colon detected in path (possible NTFS stream or device): {}", path);
20        } else if path.len() > 2 {
21            // If it starts with drive letter, check the rest for more colons
22            if path[2..].contains(':') {
23                bail!("NTFS alternate data stream syntax detected: {}", path);
24            }
25        }
26    }
27    
28    // Check for UNC paths
29    if path.starts_with("\\\\") || path.starts_with("//") {
30        bail!("UNC path detected: {}", path);
31    }
32    
33    // Check for Windows long path prefix
34    if path.starts_with("\\\\?\\") || path.starts_with("\\\\.\\") {
35        bail!("Windows extended-length path prefix detected: {}", path);
36    }
37    
38    // Check for device paths
39    let path_upper = path.to_uppercase();
40    if path_upper.starts_with("\\\\.\\") || path_upper.contains("\\DEVICE\\") {
41        bail!("Windows device path detected");
42    }
43    
44    // Check for trailing dots (Windows ignores these)
45    if let Some(last_component) = path.split(&['/', '\\'][..]).last() {
46        if last_component.ends_with('.') && last_component != "." && last_component != ".." {
47            bail!("Trailing dot detected in path component (Windows exploit): {}", last_component);
48        }
49        
50        // Check for trailing spaces (Windows ignores these)
51        if last_component.ends_with(' ') {
52            bail!("Trailing space detected in path component (Windows exploit)");
53        }
54    }
55    
56    Ok(())
57}
58
59/// Detect path separator manipulation
60pub fn detect_separator_manipulation(path: &str) -> Result<()> {
61    // Check for multiple consecutive slashes
62    if path.contains("//") || path.contains("\\\\") {
63        // Allow only at the start for UNC (but we block UNC separately)
64        if !path.starts_with("//") && !path.starts_with("\\\\") {
65            bail!("Multiple consecutive path separators detected");
66        }
67    }
68    
69    // Check for mixed separators
70    if path.contains('/') && path.contains('\\') {
71        bail!("Mixed path separators detected (possible evasion)");
72    }
73    
74    // Check for alternative/unusual separators
75    for sep in DANGEROUS_SEPARATORS.iter() {
76        if path.contains(*sep) {
77            bail!("Unusual separator character detected in path");
78        }
79    }
80    
81    Ok(())
82}
83
84/// Detect advanced traversal patterns
85pub fn detect_advanced_traversal(path: &str) -> Result<()> {
86    // Check for various dot patterns
87    for pattern in TRAVERSAL_PATTERNS.iter() {
88        if path.contains(pattern) {
89            bail!("Directory traversal pattern detected: {}", pattern);
90        }
91    }
92    
93    // Check for nested traversal with separators
94    for pattern in NESTED_TRAVERSAL_PATTERNS.iter() {
95        if path.contains(pattern) {
96            bail!("Nested traversal pattern detected: {}", pattern);
97        }
98    }
99    
100    // Check for encoded dots that might be decoded later
101    if path.contains("\\x2e") || path.contains("\\x2f") || path.contains("\\x5c") {
102        bail!("Hex-encoded path characters detected");
103    }
104    
105    Ok(())
106}
107
108/// Validate against special file paths
109pub fn validate_special_paths(path: &str) -> Result<()> {
110    let path_lower = path.to_lowercase();
111    for dangerous in SYSTEM_PATHS.iter() {
112        if path_lower.starts_with(&dangerous.to_lowercase()) {
113            bail!("Access to sensitive system path denied: {}", dangerous);
114        }
115    }
116    
117    Ok(())
118}
119
120/// Detect file protocol schemes and URL patterns
121pub fn detect_protocol_schemes(path: &str) -> Result<()> {
122    let path_lower = path.to_lowercase();
123    
124    // Check for file:// protocol
125    if path_lower.starts_with("file://") || path_lower.starts_with("file:/") {
126        bail!("File protocol scheme detected in path");
127    }
128    
129    // Check for HTTP/HTTPS protocols (SSRF attempts)
130    if path_lower.starts_with("http://") || path_lower.starts_with("https://") {
131        bail!("HTTP protocol scheme detected in path (possible SSRF)");
132    }
133    
134    // Check for other potentially dangerous protocols
135    for protocol in DANGEROUS_PROTOCOLS.iter() {
136        if path_lower.starts_with(protocol) {
137            bail!("Dangerous protocol scheme detected: {}", protocol);
138        }
139    }
140    
141    Ok(())
142}
143
144/// Detect suspicious patterns
145pub fn detect_suspicious_patterns(path: &str) -> Result<()> {
146    for pattern in SUSPICIOUS_PATTERNS.iter() {
147        if path.contains(pattern) {
148            bail!("Suspicious pattern '{}' detected in path: {}", pattern, path);
149        }
150    }
151    
152    Ok(())
153}