#[derive(Debug, Clone, PartialEq)]
pub struct InfoLeakMatch {
pub pattern: &'static str,
pub category: &'static str,
pub description: &'static str,
}
#[derive(Debug, Clone)]
struct LeakPattern {
pattern: &'static str,
category: &'static str,
description: &'static str,
}
const LEAK_PATTERNS: &[LeakPattern] = &[
LeakPattern {
pattern: "stack trace",
category: "leak",
description: "Response contains a stack trace, potentially revealing internal code paths",
},
LeakPattern {
pattern: "exception",
category: "leak",
description: "Response contains exception details",
},
LeakPattern {
pattern: "syntaxerror",
category: "leak",
description: "Response contains a syntax error message",
},
LeakPattern {
pattern: "referenceerror",
category: "leak",
description: "Response contains a reference error",
},
LeakPattern {
pattern: "typeerror",
category: "leak",
description: "Response contains a type error",
},
LeakPattern {
pattern: "traceback",
category: "leak",
description: "Response contains a Python traceback",
},
LeakPattern {
pattern: "sql syntax",
category: "leak",
description: "Response contains SQL syntax information, potential SQL injection vector",
},
LeakPattern {
pattern: "mysql_error",
category: "leak",
description: "Response contains a MySQL error message",
},
LeakPattern {
pattern: "ora-",
category: "leak",
description: "Response contains an Oracle database error",
},
LeakPattern {
pattern: "postgresql",
category: "leak",
description: "Response references PostgreSQL, potential database error leak",
},
LeakPattern {
pattern: "select * from",
category: "leak",
description: "Response contains SQL query, potential SQL injection",
},
LeakPattern {
pattern: "insert into",
category: "leak",
description: "Response contains SQL query, potential SQL injection",
},
LeakPattern {
pattern: "drop table",
category: "leak",
description: "Response contains SQL DROP statement, potential SQL injection",
},
LeakPattern {
pattern: "union select",
category: "leak",
description: "Response contains SQL UNION query, potential SQL injection",
},
LeakPattern {
pattern: "fatal:",
category: "leak",
description: "Response contains a fatal error prefix",
},
LeakPattern {
pattern: "warning:",
category: "leak",
description: "Response contains a warning message",
},
LeakPattern {
pattern: "fatal error",
category: "leak",
description: "Response contains a fatal error message",
},
LeakPattern {
pattern: "internal error",
category: "leak",
description: "Response contains an internal error message",
},
LeakPattern {
pattern: "internal server error",
category: "leak",
description: "Generic internal server error (may be acceptable)",
},
LeakPattern {
pattern: "debug",
category: "leak",
description: "Response contains debug information",
},
LeakPattern {
pattern: "com.",
category: "leak",
description: "Response contains a Java package name, potentially revealing code structure",
},
LeakPattern {
pattern: "org.",
category: "leak",
description: "Response contains a Java/org package name, potentially revealing code structure",
},
LeakPattern {
pattern: "java.lang",
category: "leak",
description: "Response contains a Java language class reference",
},
LeakPattern {
pattern: "system.",
category: "leak",
description: "Response contains a .NET System namespace reference",
},
LeakPattern {
pattern: "system.io",
category: "leak",
description: "Response contains a .NET System.IO namespace reference",
},
LeakPattern {
pattern: "microsoft",
category: "leak",
description: "Response references Microsoft framework internals",
},
LeakPattern {
pattern: "root:x:",
category: "leak",
description: "Response contains password file data (/etc/passwd)",
},
LeakPattern {
pattern: "daemon:x:",
category: "leak",
description: "Response contains password file data (daemon entry)",
},
LeakPattern {
pattern: "/etc/passwd",
category: "leak",
description: "Response references /etc/passwd, potential path traversal",
},
LeakPattern {
pattern: "/etc/shadow",
category: "leak",
description: "Response references /etc/shadow, potential path traversal",
},
];
pub fn detect_info_leaks(body: &str) -> Vec<InfoLeakMatch> {
let lower = body.to_lowercase();
let mut results = Vec::new();
for entry in LEAK_PATTERNS {
if lower.contains(entry.pattern) {
results.push(InfoLeakMatch {
pattern: entry.pattern,
category: entry.category,
description: entry.description,
});
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_stack_trace() {
let matches = detect_info_leaks("stack trace: at main() in file.php line 42");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "stack trace"));
}
#[test]
fn test_detect_exception() {
let matches = detect_info_leaks("Fatal error: syntax error");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "fatal error"));
}
#[test]
fn test_detect_sql() {
let matches = detect_info_leaks("SELECT * FROM users");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "select * from"));
}
#[test]
fn test_detect_sql_syntax() {
let matches = detect_info_leaks("SQL syntax near 'SELECT'");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "sql syntax"));
}
#[test]
fn test_detect_ora() {
let matches = detect_info_leaks("ORA-00942: table not found");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "ora-"));
}
#[test]
fn test_detect_path() {
let matches = detect_info_leaks("/etc/passwd");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "/etc/passwd"));
}
#[test]
fn test_detect_passwd_entry() {
let matches = detect_info_leaks("root:x:0:0:root:/root:/bin/bash");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "root:x:"));
}
#[test]
fn test_no_false_positive() {
let matches = detect_info_leaks(r#"{"status": "ok"}"#);
assert!(matches.is_empty());
}
#[test]
fn test_no_match_clean_text() {
let matches = detect_info_leaks("hello world");
assert!(matches.is_empty());
}
#[test]
fn test_multiple_matches() {
let matches = detect_info_leaks("stack trace: SELECT * FROM users");
assert!(matches.len() >= 2);
assert!(matches.iter().any(|m| m.pattern == "stack trace"));
assert!(matches.iter().any(|m| m.pattern == "select * from"));
}
#[test]
fn test_case_insensitive() {
let matches = detect_info_leaks("STACK TRACE: At Main()");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "stack trace"));
}
#[test]
fn test_detect_insert_into() {
let matches = detect_info_leaks("INSERT INTO users VALUES (1)");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "insert into"));
}
#[test]
fn test_detect_drop_table() {
let matches = detect_info_leaks("DROP TABLE users");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "drop table"));
}
#[test]
fn test_detect_union_select() {
let matches = detect_info_leaks("UNION SELECT * FROM admin");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "union select"));
}
#[test]
fn test_detect_traceback() {
let matches = detect_info_leaks("Traceback (most recent call last):");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "traceback"));
}
#[test]
fn test_detect_internal_server_error() {
let matches = detect_info_leaks("Internal Server Error");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "internal server error"));
}
#[test]
fn test_detect_java_lang() {
let matches = detect_info_leaks("java.lang.NullPointerException");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "java.lang"));
}
#[test]
fn test_detect_etc_shadow() {
let matches = detect_info_leaks("/etc/shadow");
assert!(!matches.is_empty());
assert!(matches.iter().any(|m| m.pattern == "/etc/shadow"));
}
}