1use super::ScanError;
19use serde::{Deserialize, Serialize};
20use std::fs;
21use std::path::Path;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ThreatPatterns {
26 prompt_injection: Vec<String>,
27 command_injection: Vec<String>,
28 path_traversal: Vec<String>,
29 sql_injection: Vec<String>,
30 xss_patterns: Vec<String>,
31 ldap_injection: Vec<String>,
32 xml_injection: Vec<String>,
33 nosql_injection: Vec<String>,
34}
35
36impl ThreatPatterns {
37 pub fn load_from_file(path: &Path) -> Result<Self, ScanError> {
39 let content = fs::read_to_string(path)
40 .map_err(|e| ScanError::InvalidInput(format!("Failed to read patterns file: {e}")))?;
41
42 serde_json::from_str(&content)
43 .map_err(|e| ScanError::InvalidInput(format!("Invalid patterns JSON: {e}")))
44 }
45
46 pub fn prompt_injection_patterns(&self) -> &[String] {
48 &self.prompt_injection
49 }
50
51 pub fn command_injection_patterns(&self) -> &[String] {
53 &self.command_injection
54 }
55
56 pub fn path_traversal_patterns(&self) -> &[String] {
58 &self.path_traversal
59 }
60
61 pub fn sql_injection_patterns(&self) -> &[String] {
63 &self.sql_injection
64 }
65
66 pub fn xss_patterns(&self) -> &[String] {
68 &self.xss_patterns
69 }
70
71 pub fn ldap_injection_patterns(&self) -> &[String] {
73 &self.ldap_injection
74 }
75
76 pub fn xml_injection_patterns(&self) -> &[String] {
78 &self.xml_injection
79 }
80
81 pub fn nosql_injection_patterns(&self) -> &[String] {
83 &self.nosql_injection
84 }
85
86 pub const fn get_all_patterns(&self) -> &Self {
88 self
89 }
90}
91
92impl Default for ThreatPatterns {
93 fn default() -> Self {
94 Self {
95 prompt_injection: vec![
96 r"(?i)ignore\s+(previous|all|above)\s+(instructions?|prompts?|commands?)"
98 .to_string(),
99 r"(?i)disregard\s+(previous|all|above)\s+(instructions?|prompts?|commands?)"
100 .to_string(),
101 r"(?i)forget\s+(everything|all|previous)\s+(above|before)".to_string(),
102 r"(?i)new\s+instructions?:\s*".to_string(),
103 r"(?i)system\s*:\s*you\s+are".to_string(),
104 r"(?i)pretend\s+you\s+are".to_string(),
105 r"(?i)act\s+as\s+if\s+you".to_string(),
106 r"(?i)roleplay\s+as".to_string(),
107 r"(?i)you\s+are\s+now\s+in\s+developer\s+mode".to_string(),
108 r"(?i)enable\s+developer\s+mode".to_string(),
109 r"\[\[.*\]\]".to_string(), r"<system>.*</system>".to_string(), r"(?i)simulation\s+mode".to_string(),
112 r"(?i)testing\s+mode".to_string(),
113 ],
114
115 command_injection: vec![
116 r";\s*rm\s+".to_string(),
118 r";\s*cat\s+".to_string(),
119 r";\s*ls\s+".to_string(),
120 r";\s*wget\s+".to_string(),
121 r";\s*curl\s+".to_string(),
122 r";\s*python\s+".to_string(),
123 r";\s*python3\s+".to_string(),
124 r";\s*perl\s+".to_string(),
125 r";\s*ruby\s+".to_string(),
126 r";\s*php\s+".to_string(),
127 r";\s*node\s+".to_string(),
128 r";\s*java\s+".to_string(),
129 r"&&\s*[a-z]+\s+".to_string(),
130 r"\|\|\s*[a-z]+\s+".to_string(),
131 r"\|\s*[a-z]+\s+".to_string(),
132 r"\|\s*nc\s+".to_string(),
133 r"\|\s*netcat\s+".to_string(),
134 r"\|\s*ncat\s+".to_string(),
135 r"\|\s*socat\s+".to_string(),
136 r"\|\s*telnet\s+".to_string(),
137 r"\|\s*ssh\s+".to_string(),
138 r"\$\([^)]+\)".to_string(),
140 r"`[^`]+`".to_string(),
141 r"\$\{[^}]+\}".to_string(),
142 r"(?i)(rm|del)\s+-rf?\s+".to_string(),
144 r"(?i)format\s+[cd]:".to_string(),
145 r"(?i)shutdown\s+".to_string(),
146 r"/bin/(sh|bash|zsh|dash)".to_string(),
147 r"(?i)cmd\.exe\s*/c".to_string(),
149 r"(?i)cmd\s*/c".to_string(),
150 r"(?i)cmd\.exe\s+/c".to_string(),
151 r"(?i)cmd\s+/c".to_string(),
152 r"(?i)powershell\.exe\s*-c".to_string(),
153 r"(?i)powershell\s*-c".to_string(),
154 r"(?i)powershell\.exe\s*-Command".to_string(),
155 r"(?i)powershell\s*-Command".to_string(),
156 r"(?i)powershell\.exe\s*-ExecutionPolicy".to_string(),
157 r"(?i)powershell\s*-ExecutionPolicy".to_string(),
158 r"(?i)wscript\.exe".to_string(),
159 r"(?i)cscript\.exe".to_string(),
160 r"(?i)mshta\.exe".to_string(),
161 r"(?i)rundll32\.exe".to_string(),
162 r"(?i)regsvr32\.exe".to_string(),
163 r"&\s*net\s+".to_string(),
165 r"&\s*netsh\s+".to_string(),
166 r"&\s*sc\s+".to_string(),
167 r"&\s*reg\s+".to_string(),
168 r"&\s*wmic\s+".to_string(),
169 r"\|\s*findstr\s+".to_string(),
170 r"\^\s*".to_string(), r"(?i)%comspec%".to_string(),
173 r"(?i)%systemroot%".to_string(),
174 r"(?i)%windir%".to_string(),
175 r"\n\s*/bin/".to_string(),
177 r"\n\s*/usr/bin/".to_string(),
178 r"\r\n\s*cmd".to_string(),
179 r"\n\s*python".to_string(),
180 r"\n\s*perl".to_string(),
181 r"\n\s*ruby".to_string(),
182 r"\n\s*sh\s*".to_string(),
183 r"\n\s*bash\s*".to_string(),
184 r"\.\./\.\./(\.\./)*(etc|bin|usr|var)".to_string(),
186 ],
187
188 path_traversal: vec![
189 r"\.\./\.\./\.\./etc/passwd".to_string(), r"\.\./(\.\./)*(etc|usr|bin|var|tmp|home|root)".to_string(),
192 r"/\.\.".to_string(),
193 r"\.\.%2[fF]".to_string(), r"%2e%2e/".to_string(), r"%2e%2e%2f".to_string(), r"%252e%252e/".to_string(), r"%252e%252e%252f".to_string(), r"%252f".to_string(), r"\.\.\\(\.\.\\)*(windows|system32|users|program files)".to_string(),
201 r"\\\.\.".to_string(),
202 r"\.\.%5[cC]".to_string(), r"%2e%2e\\\\".to_string(),
204 r"\.{2,}/".to_string(), r"\.\.\.+/".to_string(), r"\.\./\.\./".to_string(), r"%c0%af".to_string(), r"%c1%9c".to_string(), r"%00".to_string(),
213 r"\x00".to_string(),
214 r"^/etc/".to_string(),
216 r"^/usr/".to_string(),
217 r"^/var/".to_string(),
218 r"^[cC]:\\\\".to_string(),
219 r"etc/passwd".to_string(),
221 r"etc/shadow".to_string(),
222 r"windows/win\.ini".to_string(),
223 r"boot\.ini".to_string(),
224 ],
225
226 sql_injection: vec![
227 r"'\s*(OR|AND)\s*'?1'?\s*=\s*'?1".to_string(),
229 r"'\s*(OR|AND)\s+[0-9]+\s*=\s*[0-9]+".to_string(),
230 r"admin'\s*--".to_string(),
231 r"'\s*OR\s*'[^']*'\s*=\s*'[^']*".to_string(),
232 r"(?i)union\s+(all\s+)?select".to_string(),
234 r"(?i)union\s+distinct\s+select".to_string(),
235 r"(?i)sleep\s*\(\s*[0-9]+\s*\)".to_string(),
237 r"(?i)waitfor\s+delay".to_string(),
238 r"(?i)benchmark\s*\(".to_string(),
239 r"(?i)extractvalue\s*\(".to_string(),
241 r"(?i)updatexml\s*\(".to_string(),
242 r"(?i)xmltype\s*\(".to_string(),
243 r"(?i)(drop|create|alter)\s+(table|database|schema|index)".to_string(),
245 r"(?i)insert\s+into\s+".to_string(),
246 r"(?i)delete\s+from\s+".to_string(),
247 r"(?i)update\s+\w+\s+set".to_string(),
248 r"--\s*$".to_string(),
250 r"/\*.*\*/".to_string(),
251 r"#\s*$".to_string(),
252 r"0x[0-9a-fA-F]+".to_string(),
254 r"(?i)concat\s*\(".to_string(),
256 r"(?i)char\s*\(".to_string(),
257 r"(?i)load_file\s*\(".to_string(),
258 r"(?i)into\s+outfile".to_string(),
259 ],
260
261 xss_patterns: vec![
262 r"<script[^>]*>".to_string(),
267 r"</script[^>]*>".to_string(),
268 r"(?i)<script[^>]*>".to_string(),
269 r"(?i)\bon\w+\s*=".to_string(), r"(?i)(onerror|onload|onclick|onmouseover|onfocus|onblur)\s*=".to_string(),
272 r"(?i)javascript\s*:".to_string(),
274 r"(?i)vbscript\s*:".to_string(),
275 r"(?i)livescript\s*:".to_string(),
276 r"(?i)data:[^,]*script".to_string(),
278 r"(?i)data:.*base64".to_string(),
279 r"(?i)<iframe[^>]*>".to_string(),
281 r"(?i)<object[^>]*>".to_string(),
282 r"(?i)<embed[^>]*>".to_string(),
283 r"(?i)<applet[^>]*>".to_string(),
284 r"(?i)<form[^>]*>".to_string(),
285 r"(?i)<link[^>]*>".to_string(),
286 r"(?i)<meta[^>]*>".to_string(),
287 r"(?i)<svg[^>]*>".to_string(),
289 r"(?i)<svg[^>]*onload".to_string(),
290 r"(?i)<style[^>]*>".to_string(),
292 r"(?i)style\s*=.*expression\s*\(".to_string(),
293 r"(?i)style\s*=.*javascript\s*:".to_string(),
294 r"%3[Cc]script".to_string(),
296 r"[Cc];script".to_string(),
297 r"<script".to_string(),
298 r"\\u003[Cc]script".to_string(),
299 r"\\x3[Cc]script".to_string(),
300 r"String\.fromCharCode".to_string(),
302 r"eval\s*\(".to_string(),
303 r"(?i)expression\s*\(".to_string(),
304 r"(?i)document\s*\.\s*(write|writeln)".to_string(),
305 r"(?i)window\s*\.\s*location".to_string(),
306 r"(?i)document\s*\.\s*cookie".to_string(),
307 r"(?i)onhashchange\s*=".to_string(),
309 r"(?i)onpopstate\s*=".to_string(),
310 r"(?i)onstorage\s*=".to_string(),
311 r"<scr\x00ipt".to_string(),
313 r"<scr\<!--.*--\>ipt".to_string(),
314 ],
315
316 ldap_injection: vec![
317 r"\(\|\(".to_string(),
319 r"\)\(\|".to_string(),
320 r"\(\&\(".to_string(),
321 r"\)\(\&".to_string(),
322 r"(?i)\*\)\(\|".to_string(),
323 r"(?i)\)\(\|\(".to_string(),
324 r"\\2a".to_string(), r"\\28".to_string(), r"\\29".to_string(), r"\\5c".to_string(), r"(?i)admin\*\)\(".to_string(),
331 r"(?i)\*\)\(uid=\*".to_string(),
332 r"(?i)\*\)\(objectClass=\*".to_string(),
333 r"(?i)cn=\*\)\(".to_string(),
334 r"(?i)\|\(uid=\*".to_string(),
336 r"(?i)\&\(uid=\*".to_string(),
337 r"(?i)\!\(uid=\*".to_string(),
338 ],
339
340 xml_injection: vec![
341 r"<!ENTITY".to_string(),
343 r"<!DOCTYPE".to_string(),
344 r#"SYSTEM\s+["']file:"#.to_string(),
345 r#"SYSTEM\s+["']http:"#.to_string(),
346 r#"SYSTEM\s+["']https:"#.to_string(),
347 r"&xxe;".to_string(),
349 r"%xxe;".to_string(),
350 r"<!ENTITY\s+xxe".to_string(),
351 r"<!ENTITY\s+%\s+xxe".to_string(),
352 r"<!\[CDATA\[.*\]\]>".to_string(),
354 r"]]>.*<!\[CDATA\[".to_string(),
355 r"</[^>]+><[^>]+>".to_string(),
357 r"(?i)<soap:.*>".to_string(),
358 r"(?i)' or '1'='1".to_string(),
360 r#"(?i)" or "1"="1"#.to_string(),
361 r"(?i)' or 1=1".to_string(),
362 r#"(?i)" or 1=1"#.to_string(),
363 r#"<!ENTITY\s+lol\s+"lol"#.to_string(),
365 r"&lol9;".to_string(),
366 ],
367
368 nosql_injection: vec![
369 r"\$ne".to_string(),
371 r"\$eq".to_string(),
372 r"\$gt".to_string(),
373 r"\$gte".to_string(),
374 r"\$lt".to_string(),
375 r"\$lte".to_string(),
376 r"\$nin".to_string(),
377 r"\$in".to_string(),
378 r"\$regex".to_string(),
379 r"\$where".to_string(),
380 r"\$exists".to_string(),
381 r"\$type".to_string(),
382 r"\$expr".to_string(),
383 r"\$jsonSchema".to_string(),
384 r"\$mod".to_string(),
385 r#"["']\s*:\s*\{\s*["']\$"#.to_string(),
387 r#"\{\s*["']\$ne["']\s*:"#.to_string(),
388 r#"\{\s*["']\$gt["']\s*:"#.to_string(),
389 r"(?i)function\s*\(".to_string(),
391 r"(?i)return\s+true".to_string(),
392 r"(?i)return\s+1\s*==\s*1".to_string(),
393 r"(?i)this\.\w+\s*==".to_string(),
394 r"_design/".to_string(),
396 r"_view/".to_string(),
397 r"\$text\s*:\s*\{".to_string(),
398 r"\$search\s*:".to_string(),
399 r"(?i)FLUSHDB".to_string(),
401 r"(?i)FLUSHALL".to_string(),
402 r"(?i)CONFIG\s+SET".to_string(),
403 r"(?i)SCRIPT\s+LOAD".to_string(),
404 ],
405 }
406 }
407}
408
409pub fn save_default_patterns(path: &Path) -> Result<(), ScanError> {
411 let patterns = ThreatPatterns::default();
412 let json = serde_json::to_string_pretty(&patterns)
413 .map_err(|e| ScanError::InvalidInput(format!("Failed to serialize patterns: {e}")))?;
414
415 fs::write(path, json)
416 .map_err(|e| ScanError::InvalidInput(format!("Failed to write patterns file: {e}")))
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[test]
424 fn test_default_patterns() {
425 let patterns = ThreatPatterns::default();
426 assert!(!patterns.prompt_injection.is_empty());
427 assert!(!patterns.command_injection.is_empty());
428 assert!(!patterns.path_traversal.is_empty());
429 assert!(!patterns.sql_injection.is_empty());
430 }
431
432 #[test]
433 fn test_pattern_serialization() {
434 let patterns = ThreatPatterns::default();
435 let json = serde_json::to_string(&patterns).unwrap();
436 let loaded: ThreatPatterns = serde_json::from_str(&json).unwrap();
437
438 assert_eq!(
439 patterns.prompt_injection.len(),
440 loaded.prompt_injection.len()
441 );
442 assert_eq!(
443 patterns.command_injection.len(),
444 loaded.command_injection.len()
445 );
446 }
447}