Skip to main content

ploit_malper/engine/
matcher.rs

1use crate::models::ModuleSuggestion;
2
3const BANNER_CATALOG: &[(&str, &[&str])] = &[
4    (
5        "apache",
6        &[
7            "exploit/multi/http/apache_mod_cgi_bash_env_exec",
8            "auxiliary/scanner/http/apache_version",
9        ],
10    ),
11    ("iis", &["auxiliary/scanner/http/iis_version"]),
12    ("openssh", &["auxiliary/scanner/ssh/ssh_version"]),
13    (
14        "mysql",
15        &[
16            "auxiliary/scanner/mysql/mysql_version",
17            "exploit/linux/mysql/mysql_yassl_getname",
18        ],
19    ),
20    (
21        "postgresql",
22        &["auxiliary/scanner/postgres/postgres_version"],
23    ),
24    (
25        "smb",
26        &[
27            "auxiliary/scanner/smb/smb_version",
28            "exploit/windows/smb/ms17_010_eternalblue",
29        ],
30    ),
31    ("ftp", &["auxiliary/scanner/ftp/ftp_version"]),
32    (
33        "rdp",
34        &[
35            "auxiliary/scanner/rdp/rdp_scanner",
36            "exploit/windows/rdp/cve_2019_0708_bluekeep",
37        ],
38    ),
39    ("tomcat", &["exploit/multi/http/tomcat_jsp_upload_bypass"]),
40    ("jenkins", &["exploit/multi/http/jenkins_script_console"]),
41    ("wordpress", &["auxiliary/scanner/http/wordpress_scanner"]),
42    ("drupal", &["exploit/unix/webapp/drupal_drupalgeddon2"]),
43    (
44        "wp-admin",
45        &[
46            "auxiliary/scanner/http/wordpress_scanner",
47            "auxiliary/scanner/http/wordpress_login_enum",
48        ],
49    ),
50    ("robots.txt", &["auxiliary/scanner/http/robots_txt"]),
51    ("pprof", &["auxiliary/scanner/http/golang_pprof"]),
52    (
53        "debug",
54        &[
55            "auxiliary/scanner/http/debug_page",
56            "auxiliary/scanner/http/http_version",
57        ],
58    ),
59];
60
61fn banner_matches(banner_lower: &str, keyword: &str) -> bool {
62    if keyword.len() <= 3 {
63        let pattern = format!(
64            r"(?i)(^|[\s/_.\-:;]){}([\s/_.\-:;]|$)",
65            regex::escape(keyword)
66        );
67        regex::Regex::new(&pattern)
68            .expect("valid regex")
69            .is_match(banner_lower)
70    } else {
71        banner_lower.contains(keyword)
72    }
73}
74
75pub fn suggest_modules(service_banner: &str) -> Vec<ModuleSuggestion> {
76    let banner_lower = service_banner.to_lowercase();
77    let mut suggestions = Vec::new();
78
79    for (keyword, modules) in BANNER_CATALOG {
80        if banner_matches(&banner_lower, keyword) {
81            for module in *modules {
82                suggestions.push(ModuleSuggestion {
83                    service_banner: service_banner.to_string(),
84                    suggested_module: (*module).to_string(),
85                    confidence: "high".to_string(),
86                });
87            }
88        }
89    }
90
91    suggestions
92}
93
94pub fn suggest_from_title(title: &str, details: &str) -> Vec<ModuleSuggestion> {
95    let combined = format!("{} {}", title.to_lowercase(), details.to_lowercase());
96    let mut suggestions = Vec::new();
97
98    for (keyword, modules) in BANNER_CATALOG {
99        if banner_matches(&combined, keyword) {
100            for module in *modules {
101                suggestions.push(ModuleSuggestion {
102                    service_banner: format!("title/details match: {}", keyword),
103                    suggested_module: (*module).to_string(),
104                    confidence: "medium".to_string(),
105                });
106            }
107        }
108    }
109
110    suggestions
111}
112
113pub fn get_all_known_banners() -> Vec<String> {
114    let mut banners = BANNER_CATALOG
115        .iter()
116        .map(|(keyword, _)| (*keyword).to_string())
117        .collect::<Vec<_>>();
118    banners.sort();
119    banners
120}