1use serde::Deserialize;
7use std::collections::HashMap;
8use std::path::Path;
9
10#[derive(Debug, Clone, Deserialize)]
12pub struct ServiceRule {
13 pub id: String,
15 pub service: String,
17 pub protocol: String,
19 pub common_ports: Vec<u16>,
21 pub patterns: Vec<String>,
23 pub version_pattern: Option<String>,
25 pub security_signals: Vec<String>,
27 pub priority: u8,
29}
30
31#[derive(Debug, Deserialize)]
32struct ServiceRulesFile {
33 #[serde(default)]
34 rule: Vec<ServiceRule>,
35}
36
37pub fn load_from_toml<P: AsRef<Path>>(path: P) -> Result<Vec<ServiceRule>, anyhow::Error> {
55 let body = std::fs::read_to_string(path.as_ref())?;
56 let parsed: ServiceRulesFile = toml::from_str(&body)?;
57 Ok(parsed.rule)
58}
59
60pub fn builtin_plus<P: AsRef<Path>>(custom_path: P) -> Vec<ServiceRule> {
64 let mut out = load_from_toml(custom_path).unwrap_or_default();
65 out.extend(builtin_rules());
66 out
67}
68
69#[derive(Debug, Clone)]
71pub struct ServiceMatch {
72 pub rule_id: String,
74 pub service: String,
76 pub version: Option<String>,
78 pub confidence: f32,
80 pub signals: Vec<String>,
82 pub metadata: HashMap<String, String>,
84}
85
86pub fn builtin_rules() -> Vec<ServiceRule> {
91 let mut out = vec![
92 ServiceRule {
94 id: "http-apache".into(),
95 service: "Apache HTTP Server".into(),
96 protocol: "tcp".into(),
97 common_ports: vec![80, 443, 8080, 8443],
98 patterns: vec![
99 "Apache/".into(),
100 "Server: Apache".into(),
101 ],
102 version_pattern: Some(r"Apache/(\d+\.\d+\.\d+)".into()),
103 security_signals: vec![
104 "server-version-disclosure".into(),
105 ],
106 priority: 10,
107 },
108 ServiceRule {
109 id: "http-nginx".into(),
110 service: "nginx".into(),
111 protocol: "tcp".into(),
112 common_ports: vec![80, 443, 8080, 8443],
113 patterns: vec![
114 "nginx/".into(),
115 "Server: nginx".into(),
116 ],
117 version_pattern: Some(r"nginx/(\d+\.\d+\.\d+)".into()),
118 security_signals: vec![
119 "server-version-disclosure".into(),
120 ],
121 priority: 10,
122 },
123 ServiceRule {
124 id: "http-iis".into(),
125 service: "Microsoft IIS".into(),
126 protocol: "tcp".into(),
127 common_ports: vec![80, 443, 8080],
128 patterns: vec![
129 "Microsoft-IIS/".into(),
130 "Server: Microsoft-IIS".into(),
131 ],
132 version_pattern: Some(r"Microsoft-IIS/(\d+\.\d+)".into()),
133 security_signals: vec![
134 "server-version-disclosure".into(),
135 ],
136 priority: 10,
137 },
138 ServiceRule {
140 id: "ssh-openssh".into(),
141 service: "OpenSSH".into(),
142 protocol: "tcp".into(),
143 common_ports: vec![22, 2222],
144 patterns: vec![
145 "SSH-2.0-OpenSSH".into(),
146 "SSH-1.99-OpenSSH".into(),
147 ],
148 version_pattern: Some(r"OpenSSH[_\s](\d+\.\d+\S*)".into()),
149 security_signals: vec![
150 "ssh-version-disclosure".into(),
151 ],
152 priority: 10,
153 },
154 ServiceRule {
155 id: "ssh-dropbear".into(),
156 service: "Dropbear SSH".into(),
157 protocol: "tcp".into(),
158 common_ports: vec![22, 2222],
159 patterns: vec!["dropbear".into()],
160 version_pattern: Some(r"dropbear[_\s](\d+\.\d+)".into()),
161 security_signals: vec!["ssh-version-disclosure".into()],
162 priority: 8,
163 },
164 ServiceRule {
166 id: "ftp-vsftpd".into(),
167 service: "vsftpd".into(),
168 protocol: "tcp".into(),
169 common_ports: vec![21],
170 patterns: vec!["vsftpd".into(), "vsFTPd".into()],
171 version_pattern: Some(r"vsftpd\s+(\d+\.\d+\.\d+)".into()),
172 security_signals: vec!["ftp-version-disclosure".into()],
173 priority: 10,
174 },
175 ServiceRule {
176 id: "ftp-proftpd".into(),
177 service: "ProFTPD".into(),
178 protocol: "tcp".into(),
179 common_ports: vec![21],
180 patterns: vec!["ProFTPD".into()],
181 version_pattern: Some(r"ProFTPD\s+(\d+\.\d+\.\d+\S*)".into()),
182 security_signals: vec!["ftp-version-disclosure".into()],
183 priority: 10,
184 },
185 ServiceRule {
187 id: "mysql".into(),
188 service: "MySQL".into(),
189 protocol: "tcp".into(),
190 common_ports: vec![3306],
191 patterns: vec![
192 "mysql_native_password".into(),
193 "MariaDB".into(),
194 "\x00\x00\x00\x0a".into(), ],
196 version_pattern: Some(r"(\d+\.\d+\.\d+)".into()),
197 security_signals: vec![
198 "database-exposed".into(),
199 "version-disclosure".into(),
200 ],
201 priority: 10,
202 },
203 ServiceRule {
204 id: "postgresql".into(),
205 service: "PostgreSQL".into(),
206 protocol: "tcp".into(),
207 common_ports: vec![5432],
208 patterns: vec!["PostgreSQL".into()],
209 version_pattern: Some(r"PostgreSQL\s+(\d+\.\d+)".into()),
210 security_signals: vec!["database-exposed".into()],
211 priority: 10,
212 },
213 ServiceRule {
214 id: "redis".into(),
215 service: "Redis".into(),
216 protocol: "tcp".into(),
217 common_ports: vec![6379],
218 patterns: vec![
219 "-ERR".into(),
220 "+PONG".into(),
221 "$".into(),
222 "redis_version:".into(),
223 ],
224 version_pattern: Some(r"redis_version:(\d+\.\d+\.\d+)".into()),
225 security_signals: vec![
226 "redis-unauthenticated".into(),
227 "database-exposed".into(),
228 ],
229 priority: 10,
230 },
231 ServiceRule {
232 id: "mongodb".into(),
233 service: "MongoDB".into(),
234 protocol: "tcp".into(),
235 common_ports: vec![27017],
236 patterns: vec!["ismaster".into(), "MongoDB".into()],
237 version_pattern: Some(r"(\d+\.\d+\.\d+)".into()),
238 security_signals: vec!["database-exposed".into()],
239 priority: 10,
240 },
241 ServiceRule {
243 id: "smtp".into(),
244 service: "SMTP".into(),
245 protocol: "tcp".into(),
246 common_ports: vec![25, 465, 587],
247 patterns: vec![
248 "220 ".into(),
249 "ESMTP".into(),
250 "Postfix".into(),
251 "Exim".into(),
252 ],
253 version_pattern: Some(r"(?:Postfix|Exim|Sendmail)\s*(\d+\.\d+\S*)".into()),
254 security_signals: vec!["smtp-open-relay-check".into()],
255 priority: 8,
256 },
257 ServiceRule {
259 id: "elasticsearch".into(),
260 service: "Elasticsearch".into(),
261 protocol: "tcp".into(),
262 common_ports: vec![9200, 9300],
263 patterns: vec![
264 "\"cluster_name\"".into(),
265 "\"tagline\" : \"You Know, for Search\"".into(),
266 ],
267 version_pattern: Some(r#""number"\s*:\s*"(\d+\.\d+\.\d+)""#.into()),
268 security_signals: vec![
269 "elasticsearch-unauthenticated".into(),
270 "search-engine-exposed".into(),
271 ],
272 priority: 10,
273 },
274 ServiceRule {
276 id: "rdp".into(),
277 service: "RDP".into(),
278 protocol: "tcp".into(),
279 common_ports: vec![3389],
280 patterns: vec![
281 "\x03\x00".into(), ],
283 version_pattern: None,
284 security_signals: vec!["rdp-exposed".into()],
285 priority: 10,
286 },
287 ];
288 out.extend(extended_rules());
290 out
291}
292
293fn extended_rules() -> Vec<ServiceRule> {
294 let r = |id: &str, service: &str, protocol: &str, ports: Vec<u16>,
295 patterns: Vec<&str>, version: Option<&str>,
296 signals: Vec<&str>, priority: u8| -> ServiceRule {
297 ServiceRule {
298 id: id.into(),
299 service: service.into(),
300 protocol: protocol.into(),
301 common_ports: ports,
302 patterns: patterns.into_iter().map(String::from).collect(),
303 version_pattern: version.map(String::from),
304 security_signals: signals.into_iter().map(String::from).collect(),
305 priority,
306 }
307 };
308 vec![
309 r("http-haproxy", "HAProxy", "tcp", vec![80, 443, 8080, 8443],
311 vec!["Server: HAProxy", "HAProxy", "X-Haproxy"],
312 Some(r"HAProxy[/\s]+(\d+\.\d+(?:\.\d+)?)"),
313 vec!["server-version-disclosure"], 9),
314 r("http-caddy", "Caddy", "tcp", vec![80, 443, 2015, 2019],
315 vec!["Server: Caddy"],
316 Some(r"Caddy/(\d+\.\d+\.\d+)"),
317 vec!["server-version-disclosure"], 10),
318 r("http-litespeed", "LiteSpeed", "tcp", vec![80, 443, 7080],
319 vec!["Server: LiteSpeed", "LiteSpeed/"],
320 Some(r"LiteSpeed[/\s](\d+\.\d+\.\d+)"),
321 vec!["server-version-disclosure"], 9),
322 r("http-tomcat", "Apache Tomcat", "tcp", vec![8080, 8443, 8005, 8009],
323 vec!["Apache-Coyote/", "Apache Tomcat"],
324 Some(r"Apache Tomcat/?(\d+\.\d+\.\d+)"),
325 vec!["servlet-container-exposed", "server-version-disclosure"], 10),
326 r("http-jetty", "Eclipse Jetty", "tcp", vec![8080, 8443],
327 vec!["Server: Jetty", "Jetty("],
328 Some(r"Jetty\(?(\d+\.\d+\.\d+)"),
329 vec!["server-version-disclosure"], 9),
330 r("http-tornado", "Tornado", "tcp", vec![8888, 8080],
331 vec!["Server: TornadoServer"],
332 Some(r"TornadoServer/(\d+\.\d+\.\d+)"),
333 vec!["server-version-disclosure"], 8),
334 r("http-gunicorn", "Gunicorn", "tcp", vec![8000, 8080],
335 vec!["Server: gunicorn"],
336 Some(r"gunicorn/(\d+\.\d+\.\d+)"),
337 vec!["server-version-disclosure"], 8),
338 r("http-werkzeug", "Werkzeug", "tcp", vec![5000, 8000],
339 vec!["Werkzeug/"],
340 Some(r"Werkzeug/(\d+\.\d+\.\d+)"),
341 vec!["debug-server-exposed", "server-version-disclosure"], 9),
342 r("http-uvicorn", "Uvicorn", "tcp", vec![8000, 8080],
343 vec!["Server: uvicorn"],
344 Some(r"uvicorn"),
345 vec!["server-version-disclosure"], 7),
346 r("http-puma", "Puma", "tcp", vec![3000, 9292],
347 vec!["Server: puma"],
348 Some(r"puma\s+(\d+\.\d+\.\d+)"),
349 vec!["server-version-disclosure"], 8),
350 r("http-unicorn", "Unicorn", "tcp", vec![3000, 8080],
351 vec!["Server: Unicorn"],
352 Some(r"Unicorn\s+(\d+\.\d+\.\d+)"),
353 vec!["server-version-disclosure"], 7),
354 r("http-node-express", "Express (Node)", "tcp", vec![3000, 8080],
355 vec!["X-Powered-By: Express"],
356 None, vec!["framework-disclosure"], 8),
357 r("http-aws-elb", "AWS ELB", "tcp", vec![80, 443],
358 vec!["Server: awselb"],
359 Some(r"awselb/(\d+\.\d+)"), vec![], 7),
360 r("http-cloudflare", "Cloudflare", "tcp", vec![80, 443],
361 vec!["Server: cloudflare", "CF-Ray:"],
362 None, vec!["cdn-fronted"], 6),
363 r("http-akamai", "Akamai", "tcp", vec![80, 443],
364 vec!["Server: AkamaiGHost", "AkamaiGHost"],
365 None, vec!["cdn-fronted"], 6),
366 r("http-fastly", "Fastly", "tcp", vec![80, 443],
367 vec!["X-Served-By: cache-", "Via: 1.1 varnish", "Fastly-Debug"],
368 None, vec!["cdn-fronted"], 5),
369 r("http-varnish", "Varnish", "tcp", vec![80, 443, 6081, 6082],
370 vec!["Via: 1.1 varnish", "X-Varnish:"],
371 Some(r"varnish\s+v?(\d+\.\d+\.\d+)"), vec![], 7),
372 r("http-traefik", "Traefik", "tcp", vec![80, 443, 8080],
373 vec!["Server: Traefik"],
374 Some(r"Traefik/(\d+\.\d+\.\d+)"), vec!["server-version-disclosure"], 8),
375 r("http-envoy", "Envoy", "tcp", vec![80, 443, 9901],
376 vec!["Server: envoy", "X-Envoy-Upstream-Service-Time"],
377 None, vec![], 7),
378
379 r("memcached", "Memcached", "tcp", vec![11211],
381 vec!["VERSION ", "STAT pid"],
382 Some(r"VERSION\s+(\d+\.\d+\.\d+)"),
383 vec!["memcached-unauthenticated", "database-exposed"], 10),
384 r("etcd", "etcd", "tcp", vec![2379, 2380],
385 vec!["etcdserver", "/v2/keys", "/version"],
386 Some(r#""etcdserver"\s*:\s*"(\d+\.\d+\.\d+)""#),
387 vec!["etcd-exposed"], 10),
388 r("consul", "Consul", "tcp", vec![8500, 8501, 8600],
389 vec!["X-Consul-Index", "Consul Agent"],
390 Some(r"Consul/(\d+\.\d+\.\d+)"), vec!["consul-exposed"], 10),
391 r("vault", "HashiCorp Vault", "tcp", vec![8200, 8201],
392 vec!["X-Vault-", "vault/"],
393 Some(r"vault/(\d+\.\d+\.\d+)"), vec!["vault-api-exposed"], 10),
394
395 r("couchdb", "CouchDB", "tcp", vec![5984, 6984],
397 vec!["Welcome to Apache CouchDB", "couchdb"],
398 Some(r#""version"\s*:\s*"(\d+\.\d+\.\d+)""#),
399 vec!["database-exposed"], 10),
400 r("cassandra", "Cassandra", "tcp", vec![9042, 7000, 7199],
401 vec!["Apache Cassandra"],
402 Some(r"Cassandra\s+(\d+\.\d+\.\d+)"),
403 vec!["database-exposed"], 9),
404 r("clickhouse", "ClickHouse", "tcp", vec![8123, 9000, 9009],
405 vec!["X-ClickHouse-Server-Display-Name"],
406 Some(r"ClickHouse/(\d+\.\d+\.\d+)"),
407 vec!["database-exposed"], 9),
408 r("influxdb", "InfluxDB", "tcp", vec![8086, 8088],
409 vec!["X-Influxdb-Version"],
410 Some(r"X-Influxdb-Version:\s*(\d+\.\d+\.\d+)"),
411 vec!["database-exposed"], 9),
412
413 r("rabbitmq", "RabbitMQ", "tcp", vec![5672, 15672, 25672],
415 vec!["RabbitMQ", "AMQP\x00\x00"],
416 Some(r"RabbitMQ[/\s](\d+\.\d+\.\d+)"),
417 vec!["message-broker-exposed"], 10),
418 r("rabbitmq-mgmt", "RabbitMQ Management", "tcp", vec![15672],
419 vec!["RabbitMQ Management"],
420 Some(r"RabbitMQ\s+(\d+\.\d+\.\d+)"),
421 vec!["mgmt-ui-exposed"], 9),
422 r("kafka", "Kafka", "tcp", vec![9092, 9093, 9094],
423 vec!["org.apache.kafka", "broker.id"],
424 None, vec!["broker-exposed"], 9),
425 r("zookeeper", "ZooKeeper", "tcp", vec![2181, 2888, 3888],
426 vec!["zookeeper.version", "ZooKeeper"],
427 Some(r"version=(\d+\.\d+\.\d+)"),
428 vec!["zookeeper-exposed"], 10),
429 r("nats", "NATS", "tcp", vec![4222, 6222, 8222],
430 vec!["INFO {", "\"server_name\""],
431 Some(r#""version"\s*:\s*"(\d+\.\d+\.\d+)""#),
432 vec!["nats-exposed"], 9),
433 r("mosquitto", "Mosquitto MQTT", "tcp", vec![1883, 8883, 9001],
434 vec!["mosquitto"],
435 Some(r"mosquitto\s+version\s+(\d+\.\d+\.\d+)"),
436 vec!["mqtt-exposed"], 9),
437 r("pulsar", "Apache Pulsar", "tcp", vec![6650, 8080, 6651],
438 vec!["pulsar://", "Pulsar"],
439 Some(r"Pulsar\s+(\d+\.\d+\.\d+)"),
440 vec!["broker-exposed"], 8),
441
442 r("docker-daemon", "Docker daemon", "tcp", vec![2375, 2376],
444 vec!["Docker/", "ApiVersion", "\"DockerRootDir\""],
445 Some(r"Docker/(\d+\.\d+\.\d+)"),
446 vec!["docker-api-exposed", "rce-on-unauth"], 10),
447 r("kubernetes-api", "Kubernetes API", "tcp", vec![6443, 8443, 10250],
448 vec!["k8s.io", "/api/v1", "\"kind\":\"APIVersions\""],
449 Some(r#""gitVersion"\s*:\s*"v(\d+\.\d+\.\d+)""#),
450 vec!["k8s-api-exposed"], 10),
451 r("portainer", "Portainer", "tcp", vec![9000, 9443],
452 vec!["Portainer", "X-Portainer"],
453 Some(r"Portainer-(\d+\.\d+\.\d+)"), vec!["mgmt-ui-exposed"], 9),
454 r("rancher", "Rancher", "tcp", vec![8443, 80, 443],
455 vec!["X-Rancher-", "rancher"],
456 Some(r"rancher/(\d+\.\d+\.\d+)"), vec!["mgmt-ui-exposed"], 8),
457 r("kubelet", "kubelet", "tcp", vec![10250, 10255, 10256],
458 vec!["/runningpods", "/healthz", "kubelet"],
459 None, vec!["kubelet-exposed", "rce-on-unauth"], 10),
460
461 r("jenkins", "Jenkins", "tcp", vec![8080, 50000, 8443],
463 vec!["X-Jenkins:", "Jenkins-Version"],
464 Some(r"X-Jenkins:\s*(\d+\.\d+(?:\.\d+)?)"),
465 vec!["ci-server-exposed"], 10),
466 r("gitlab", "GitLab", "tcp", vec![80, 443, 8081],
467 vec!["GitLab", "X-Gitlab-Meta"],
468 Some(r"GitLab/(\d+\.\d+\.\d+)"),
469 vec!["scm-server-exposed"], 9),
470 r("gitea", "Gitea", "tcp", vec![3000, 80, 443],
471 vec!["Gitea", "X-Gitea-"],
472 Some(r"Gitea/(\d+\.\d+\.\d+)"),
473 vec!["scm-server-exposed"], 9),
474 r("gerrit", "Gerrit", "tcp", vec![8080, 29418],
475 vec!["Gerrit-", "X-Gerrit-"],
476 Some(r"Gerrit/(\d+\.\d+\.\d+)"),
477 vec!["scm-server-exposed"], 8),
478 r("sonarqube", "SonarQube", "tcp", vec![9000, 9001],
479 vec!["SonarQube", "x-sonar-"],
480 Some(r"SonarQube/(\d+\.\d+\.\d+)"),
481 vec!["mgmt-ui-exposed"], 8),
482 r("nexus", "Sonatype Nexus", "tcp", vec![8081, 8443],
483 vec!["X-Nexus-", "Server: Nexus"],
484 Some(r"Nexus/(\d+\.\d+\.\d+)"),
485 vec!["registry-exposed"], 9),
486 r("artifactory", "JFrog Artifactory", "tcp", vec![8081, 8082],
487 vec!["X-JFrog-Art-", "X-Artifactory-"],
488 Some(r"Artifactory/(\d+\.\d+\.\d+)"),
489 vec!["registry-exposed"], 9),
490 r("teamcity", "JetBrains TeamCity", "tcp", vec![8111, 80, 443],
491 vec!["TeamCity", "X-TeamCity-"],
492 Some(r"TeamCity (\d+\.\d+\.\d+)"),
493 vec!["ci-server-exposed"], 8),
494 r("concourse", "Concourse CI", "tcp", vec![8080],
495 vec!["X-Concourse-", "concourse-version"],
496 Some(r"v(\d+\.\d+\.\d+)"), vec!["ci-server-exposed"], 7),
497 r("drone-ci", "Drone CI", "tcp", vec![80, 443, 8000],
498 vec!["X-Drone", "drone/"], None,
499 vec!["ci-server-exposed"], 7),
500 r("argocd", "ArgoCD", "tcp", vec![80, 443, 8080],
501 vec!["X-ArgoCD-", "Argo CD"], None,
502 vec!["mgmt-ui-exposed"], 8),
503 r("spinnaker", "Spinnaker", "tcp", vec![8084, 9000],
504 vec!["X-Spinnaker-"], None,
505 vec!["mgmt-ui-exposed"], 7),
506 r("harbor", "Harbor", "tcp", vec![80, 443, 4443],
507 vec!["X-Harbor-Version", "Harbor-Api-Version"],
508 Some(r"X-Harbor-Version:\s*v?(\d+\.\d+\.\d+)"),
509 vec!["registry-exposed"], 9),
510 r("quay", "Quay", "tcp", vec![80, 443, 8443],
511 vec!["X-Quay-", "quay-version"], None,
512 vec!["registry-exposed"], 8),
513
514 r("bamboo", "Atlassian Bamboo", "tcp", vec![8085, 80, 443],
516 vec!["X-AUSERNAME", "Bamboo"], None,
517 vec!["ci-server-exposed"], 7),
518 r("bitbucket", "Atlassian Bitbucket", "tcp", vec![7990, 7999],
519 vec!["X-AUSERNAME", "atl_bitbucket_session"], None,
520 vec!["scm-server-exposed"], 7),
521 r("confluence", "Confluence", "tcp", vec![8090, 80, 443],
522 vec!["X-Confluence-Request-Time"], None,
523 vec!["wiki-exposed"], 7),
524 r("jira", "Jira", "tcp", vec![8080, 80, 443],
525 vec!["X-AREQUESTID", "JSESSIONID", "atlassian.xsrf.token"], None,
526 vec!["issue-tracker-exposed"], 7),
527
528 r("grafana", "Grafana", "tcp", vec![3000, 80, 443],
530 vec!["grafana_session", "X-Grafana-"],
531 Some(r"Grafana v(\d+\.\d+\.\d+)"),
532 vec!["mgmt-ui-exposed"], 9),
533 r("prometheus", "Prometheus", "tcp", vec![9090, 9093, 9100],
534 vec!["prometheus_build_info", "/api/v1/query", "Prometheus"],
535 Some(r#"version="(\d+\.\d+\.\d+)""#),
536 vec!["metrics-exposed"], 9),
537 r("alertmanager", "Alertmanager", "tcp", vec![9093, 9094],
538 vec!["Alertmanager"], Some(r"Alertmanager/(\d+\.\d+\.\d+)"),
539 vec!["mgmt-ui-exposed"], 7),
540 r("loki", "Grafana Loki", "tcp", vec![3100],
541 vec!["loki", "/loki/api/v1"], Some(r"loki/(\d+\.\d+\.\d+)"),
542 vec!["log-server-exposed"], 7),
543 r("tempo", "Grafana Tempo", "tcp", vec![3200, 4317, 9411],
544 vec!["tempo", "/api/traces"], None,
545 vec!["trace-server-exposed"], 6),
546 r("kibana", "Kibana", "tcp", vec![5601],
547 vec!["kbn-name", "kibana"],
548 Some(r#""version"\s*:\s*"(\d+\.\d+\.\d+)""#),
549 vec!["mgmt-ui-exposed"], 9),
550 r("fluentd", "Fluentd", "tcp", vec![24224, 9880],
551 vec!["fluentd"], Some(r"fluentd[/-](\d+\.\d+\.\d+)"),
552 vec!["log-server-exposed"], 7),
553 r("logstash", "Logstash", "tcp", vec![5044, 9600],
554 vec!["logstash"], Some(r"logstash[/-](\d+\.\d+\.\d+)"),
555 vec!["log-server-exposed"], 7),
556
557 r("keycloak", "Keycloak", "tcp", vec![8080, 8443],
559 vec!["X-Keycloak-", "kc_restart", "keycloak"],
560 Some(r"Keycloak/(\d+\.\d+\.\d+)"),
561 vec!["auth-server-exposed"], 8),
562 r("openldap", "OpenLDAP", "tcp", vec![389, 636],
563 vec!["LDAP", "objectClass"], None,
564 vec!["directory-exposed"], 8),
565 r("freeradius", "FreeRADIUS", "udp", vec![1812, 1813],
566 vec!["FreeRADIUS"], Some(r"FreeRADIUS\s+Version\s+(\d+\.\d+\.\d+)"),
567 vec!["radius-exposed"], 7),
568
569 r("dovecot", "Dovecot", "tcp", vec![110, 143, 993, 995],
571 vec!["Dovecot ready", "+OK Dovecot"],
572 Some(r"Dovecot\s+\(?(\d+\.\d+\.\d+)"),
573 vec!["mail-version-disclosure"], 9),
574 r("courier-imap", "Courier IMAP", "tcp", vec![143, 993],
575 vec!["Courier-IMAP"], Some(r"Courier-IMAP\s+(\d+\.\d+\.\d+)"),
576 vec!["mail-version-disclosure"], 7),
577 r("sendmail", "Sendmail", "tcp", vec![25, 587],
578 vec!["Sendmail"], Some(r"Sendmail\s+(\d+\.\d+\.\d+)"),
579 vec!["mail-version-disclosure"], 7),
580
581 r("filezilla-server", "FileZilla Server", "tcp", vec![21, 990],
583 vec!["FileZilla Server"],
584 Some(r"FileZilla Server\s+(\d+\.\d+\.\d+)"),
585 vec!["ftp-version-disclosure"], 8),
586 r("pure-ftpd", "Pure-FTPd", "tcp", vec![21],
587 vec!["Pure-FTPd"], Some(r"Pure-FTPd\s+(\d+\.\d+\S*)"),
588 vec!["ftp-version-disclosure"], 8),
589 r("samba", "Samba", "tcp", vec![139, 445],
590 vec!["Samba", "SMB"], Some(r"Samba\s+(\d+\.\d+\.\d+)"),
591 vec!["smb-exposed"], 9),
592 r("nfs", "NFS", "tcp", vec![2049],
593 vec!["NFS_PROGRAM"], None,
594 vec!["nfs-exposed"], 8),
595
596 r("ipmi", "IPMI", "udp", vec![623],
598 vec!["IPMI"], None,
599 vec!["ipmi-exposed", "remote-mgmt-exposed"], 10),
600 r("ilo", "HP iLO", "tcp", vec![80, 443, 17988],
601 vec!["Server: HP-iLO", "iLO"],
602 Some(r"HP-iLO/(\d+\.\d+)"),
603 vec!["remote-mgmt-exposed"], 10),
604 r("idrac", "Dell iDRAC", "tcp", vec![80, 443, 5900],
605 vec!["iDRAC", "Dell Inc."],
606 Some(r"iDRAC[/\s](\d+)"),
607 vec!["remote-mgmt-exposed"], 10),
608 r("supermicro-ipmi", "Supermicro BMC", "tcp", vec![80, 443],
609 vec!["ATEN International", "Supermicro"], None,
610 vec!["remote-mgmt-exposed"], 9),
611
612 r("ipp-cups", "IPP/CUPS", "tcp", vec![631],
614 vec!["IPP/", "CUPS"], Some(r"CUPS/(\d+\.\d+\.\d+)"),
615 vec!["printer-exposed"], 7),
616 r("jetdirect", "HP JetDirect", "tcp", vec![9100],
617 vec!["@PJL", "HP JetDirect"], None,
618 vec!["printer-exposed"], 7),
619
620 r("telnet", "Telnet", "tcp", vec![23, 2323],
625 vec!["login: ", "Password: ", "Welcome to "],
626 None, vec!["cleartext-protocol", "telnet-exposed"], 10),
627 r("snmp", "SNMP", "udp", vec![161, 162],
628 vec!["public", "private"], None,
629 vec!["snmp-exposed"], 9),
630 r("rpcbind", "rpcbind/portmap", "tcp", vec![111],
631 vec!["rpcbind", "program vers"], None,
632 vec!["rpc-exposed"], 7),
633
634 r("powerdns", "PowerDNS", "udp", vec![53, 8081],
636 vec!["PowerDNS"], Some(r"PowerDNS\s+Authoritative\s+Server\s+(\d+\.\d+\.\d+)"),
637 vec!["dns-version-disclosure"], 9),
638 r("nsd", "NLnet NSD", "udp", vec![53],
639 vec!["NSD "], Some(r"NSD\s+(\d+\.\d+\.\d+)"),
640 vec!["dns-version-disclosure"], 8),
641 r("knot-dns", "Knot DNS", "udp", vec![53],
642 vec!["Knot DNS"], Some(r"Knot DNS\s+(\d+\.\d+\.\d+)"),
643 vec!["dns-version-disclosure"], 8),
644 r("unbound", "Unbound", "udp", vec![53],
645 vec!["unbound"], Some(r"unbound\s+(\d+\.\d+\.\d+)"),
646 vec!["dns-version-disclosure"], 7),
647 r("dnsdist", "dnsdist", "tcp", vec![53, 5199],
648 vec!["dnsdist"], Some(r"dnsdist\s+(\d+\.\d+\.\d+)"),
649 vec!["dns-version-disclosure"], 7),
650
651 r("ntp", "NTP", "udp", vec![123],
653 vec!["\x1c"], None, vec![], 6),
654
655 r("openvpn", "OpenVPN", "udp", vec![1194, 443],
657 vec!["P_CONTROL", "OpenVPN"], None,
658 vec!["vpn-exposed"], 8),
659 r("wireguard", "WireGuard", "udp", vec![51820, 443],
660 vec!["WireGuard"], None, vec!["vpn-exposed"], 7),
661 r("pptp", "PPTP", "tcp", vec![1723],
662 vec!["pptp"], None, vec!["legacy-vpn-exposed"], 8),
663 r("strongswan", "strongSwan IPsec", "udp", vec![500, 4500],
664 vec!["strongSwan"], Some(r"strongSwan\s+(\d+\.\d+\.\d+)"),
665 vec!["vpn-exposed"], 7),
666 r("xrdp", "xrdp", "tcp", vec![3389],
667 vec!["xrdp"], Some(r"xrdp\s+(\d+\.\d+\.\d+)"),
668 vec!["rdp-exposed"], 8),
669 r("vnc", "VNC", "tcp", vec![5900, 5901, 5902],
670 vec!["RFB 003.", "RFB"],
671 Some(r"RFB 003\.(\d+)"),
672 vec!["vnc-exposed", "remote-desktop-exposed"], 9),
673
674 r("minecraft", "Minecraft Server", "tcp", vec![25565],
676 vec!["Minecraft Server", "minecraft.net", "Vanilla 1.", "Bukkit on"],
677 None, vec!["game-server-exposed"], 5),
678 r("ts3", "TeamSpeak 3", "udp", vec![9987, 10011, 30033],
679 vec!["TS3", "TeaSpeak"], None, vec![], 5),
680 r("plex", "Plex Media Server", "tcp", vec![32400],
681 vec!["X-Plex-", "Plex Media Server"],
682 Some(r"X-Plex-Version:\s*(\d+\.\d+\.\d+)"),
683 vec!["media-server-exposed"], 6),
684 r("jellyfin", "Jellyfin", "tcp", vec![8096, 8920],
685 vec!["X-Application", "Jellyfin"],
686 Some(r"Jellyfin/(\d+\.\d+\.\d+)"),
687 vec!["media-server-exposed"], 6),
688
689 r("modbus", "Modbus TCP", "tcp", vec![502],
696 vec!["\u{0}\u{0}\u{0}\u{0}\u{0}\u{6}\u{1}"],
697 None, vec!["ics-exposed", "modbus-exposed"], 10),
698 r("siemens-s7", "Siemens S7", "tcp", vec![102],
699 vec!["S7", "ISO-on-TCP"], None,
700 vec!["ics-exposed"], 10),
701
702 r("iscsi", "iSCSI", "tcp", vec![3260],
704 vec!["iSCSI", "iqn."], None,
705 vec!["storage-exposed"], 8),
706 r("minio", "MinIO", "tcp", vec![9000, 9001],
707 vec!["X-Amz-Bucket-Region", "MinIO", "MinIO Console"],
708 Some(r"MinIO/(\S+)"), vec!["object-storage-exposed"], 9),
709
710 r("solr", "Apache Solr", "tcp", vec![8983, 7574],
712 vec!["Solr", "X-Solr-"],
713 Some(r"Solr/(\d+\.\d+\.\d+)"),
714 vec!["search-engine-exposed"], 9),
715 r("opensearch", "OpenSearch", "tcp", vec![9200],
716 vec!["opensearch", "OpenSearch"],
717 Some(r#""number"\s*:\s*"(\d+\.\d+\.\d+)""#),
718 vec!["search-engine-exposed"], 9),
719 r("milvus", "Milvus", "tcp", vec![19530, 9091],
720 vec!["milvus"], Some(r"milvus[/\s](\d+\.\d+\.\d+)"),
721 vec!["vector-db-exposed"], 7),
722 r("qdrant", "Qdrant", "tcp", vec![6333, 6334],
723 vec!["qdrant", "Qdrant"], Some(r"Qdrant/(\d+\.\d+\.\d+)"),
724 vec!["vector-db-exposed"], 7),
725 r("weaviate", "Weaviate", "tcp", vec![8080, 50051],
726 vec!["weaviate"], Some(r"weaviate[/\s](\d+\.\d+\.\d+)"),
727 vec!["vector-db-exposed"], 7),
728
729 r("smb1", "SMBv1", "tcp", vec![139, 445],
731 vec!["SMB 1.0"], None,
732 vec!["smbv1-exposed", "deprecated-protocol"], 10),
733 r("rsync", "rsync", "tcp", vec![873],
734 vec!["@RSYNCD:"], Some(r"@RSYNCD:\s*(\d+\.\d+)"),
735 vec!["rsync-exposed"], 8),
736 ]
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 #[test]
744 fn builtin_rules_not_empty() {
745 let rules = builtin_rules();
746 assert!(rules.len() > 10, "should have substantial rule coverage");
747 }
748
749 #[test]
750 fn all_rules_have_unique_ids() {
751 let rules = builtin_rules();
752 let mut seen = std::collections::HashSet::new();
753 for rule in &rules {
754 assert!(seen.insert(&rule.id), "duplicate rule id: {}", rule.id);
755 }
756 }
757
758 #[test]
759 fn all_rules_have_patterns() {
760 for rule in builtin_rules() {
761 assert!(!rule.patterns.is_empty(), "rule {} has no patterns", rule.id);
762 }
763 }
764}