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