@intentsolutionsio/penetration-tester 2.0.0 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +8 -3
- package/README.md +8 -0
- package/commands/pentest.md +5 -0
- package/package.json +8 -3
- package/skills/analyzing-tls-config/SKILL.md +221 -0
- package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
- package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
- package/skills/analyzing-tls-config/references/THEORY.md +128 -0
- package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
- package/skills/auditing-cors-policy/SKILL.md +186 -0
- package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
- package/skills/auditing-cors-policy/references/THEORY.md +142 -0
- package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
- package/skills/auditing-npm-dependencies/SKILL.md +254 -0
- package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
- package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
- package/skills/auditing-python-dependencies/SKILL.md +251 -0
- package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
- package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
- package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
- package/skills/checking-http-security-headers/SKILL.md +176 -0
- package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
- package/skills/checking-http-security-headers/references/THEORY.md +137 -0
- package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
- package/skills/checking-license-compliance/SKILL.md +225 -0
- package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
- package/skills/checking-license-compliance/references/THEORY.md +152 -0
- package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
- package/skills/composing-vulnerability-report/SKILL.md +212 -0
- package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
- package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
- package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
- package/skills/confirming-pentest-authorization/SKILL.md +247 -0
- package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
- package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
- package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
- package/skills/defining-pentest-scope/SKILL.md +227 -0
- package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
- package/skills/defining-pentest-scope/references/THEORY.md +170 -0
- package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
- package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
- package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
- package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
- package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
- package/skills/detecting-debug-endpoints/SKILL.md +207 -0
- package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
- package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
- package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
- package/skills/detecting-directory-listing/SKILL.md +206 -0
- package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
- package/skills/detecting-directory-listing/references/THEORY.md +203 -0
- package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
- package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
- package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
- package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
- package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
- package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
- package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
- package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
- package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
- package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
- package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
- package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
- package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
- package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
- package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
- package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
- package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
- package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
- package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
- package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
- package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
- package/skills/detecting-weak-cryptography/SKILL.md +147 -0
- package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
- package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
- package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
- package/skills/fingerprinting-server-software/SKILL.md +191 -0
- package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
- package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
- package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
- package/skills/generating-executive-summary/SKILL.md +261 -0
- package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
- package/skills/generating-executive-summary/references/THEORY.md +195 -0
- package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
- package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
- package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
- package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
- package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
- package/skills/performing-penetration-testing/SKILL.md +282 -190
- package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
- package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
- package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
- package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
- package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
- package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
- package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
- package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
- package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
- package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
- package/skills/recording-pentest-engagement/SKILL.md +253 -0
- package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
- package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
- package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
- package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
- package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
- package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
- package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
- package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
- package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
- package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
- package/skills/tracing-transitive-vulnerabilities/scripts/trace_vulns.py +484 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Probe for accidentally-public admin / debug / introspection endpoints.
|
|
3
|
+
|
|
4
|
+
Companion to skill `detecting-debug-endpoints`. Sends a GET for each
|
|
5
|
+
path in a curated 40+ probe set covering Spring Boot Actuator, Apache
|
|
6
|
+
mod_status, Prometheus metrics, GraphQL playground, Swagger UI,
|
|
7
|
+
phpMyAdmin, Jolokia, Elasticsearch _cat, and generic admin/debug
|
|
8
|
+
panels.
|
|
9
|
+
|
|
10
|
+
Each 200 response is fingerprinted against the framework-specific
|
|
11
|
+
body pattern (Actuator returns `{"_links":...}`, mod_status returns
|
|
12
|
+
HTML containing "Apache Server Status", Prometheus returns
|
|
13
|
+
`# HELP`/`# TYPE` lines, etc.) so SPA index pages that 200 on every
|
|
14
|
+
route don't generate false positives.
|
|
15
|
+
|
|
16
|
+
References:
|
|
17
|
+
OWASP WSTG v4.2 § 4.2.4 Enumerate Infrastructure
|
|
18
|
+
OWASP A05:2021 Security Misconfiguration
|
|
19
|
+
CWE-749 Exposed Dangerous Method
|
|
20
|
+
CWE-285 Improper Authorization
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
_PLUGIN_ROOT = Path(__file__).resolve().parents[3]
|
|
31
|
+
if str(_PLUGIN_ROOT) not in sys.path:
|
|
32
|
+
sys.path.insert(0, str(_PLUGIN_ROOT))
|
|
33
|
+
|
|
34
|
+
from lib.authz_check import require_authorization # noqa: E402
|
|
35
|
+
from lib.finding import Finding, Severity # noqa: E402
|
|
36
|
+
from lib.http_client import make_session, safe_get # noqa: E402
|
|
37
|
+
from lib.report import emit, exit_code # noqa: E402
|
|
38
|
+
|
|
39
|
+
SKILL_ID = "detecting-debug-endpoints"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Probe set. Each entry: (path, title, severity, fingerprint_regex_or_None, control_id, cwe)
|
|
43
|
+
# fingerprint_regex applied case-insensitively against first 4 KiB of body.
|
|
44
|
+
PROBES = [
|
|
45
|
+
# Critical — Spring Boot Actuator with sensitive endpoints exposed
|
|
46
|
+
(
|
|
47
|
+
"actuator/env",
|
|
48
|
+
"Spring Boot Actuator /env exposed (leaks every environment variable)",
|
|
49
|
+
Severity.CRITICAL,
|
|
50
|
+
r'"propertySources"|"systemProperties"|"applicationConfig"',
|
|
51
|
+
"OWASP A05:2021",
|
|
52
|
+
"CWE-200",
|
|
53
|
+
),
|
|
54
|
+
(
|
|
55
|
+
"actuator/heapdump",
|
|
56
|
+
"Spring Boot Actuator /heapdump exposed (live heap snapshot)",
|
|
57
|
+
Severity.CRITICAL,
|
|
58
|
+
None, # body is binary multi-MB; presence of 200 + content-length large is the signal
|
|
59
|
+
"OWASP A05:2021",
|
|
60
|
+
"CWE-200",
|
|
61
|
+
),
|
|
62
|
+
(
|
|
63
|
+
"actuator/jolokia",
|
|
64
|
+
"Spring Boot Actuator /jolokia exposed (JMX-over-HTTP, potential RCE)",
|
|
65
|
+
Severity.CRITICAL,
|
|
66
|
+
r'"agent"\s*:\s*"jolokia"|"protocol"\s*:\s*"jolokia"',
|
|
67
|
+
"OWASP A05:2021",
|
|
68
|
+
"CWE-749",
|
|
69
|
+
),
|
|
70
|
+
(
|
|
71
|
+
"actuator/threaddump",
|
|
72
|
+
"Spring Boot Actuator /threaddump exposed (live thread state)",
|
|
73
|
+
Severity.HIGH,
|
|
74
|
+
r'"threads"\s*:\s*\[',
|
|
75
|
+
"OWASP A05:2021",
|
|
76
|
+
"CWE-200",
|
|
77
|
+
),
|
|
78
|
+
(
|
|
79
|
+
"actuator/loggers",
|
|
80
|
+
"Spring Boot Actuator /loggers exposed (can change log levels remotely)",
|
|
81
|
+
Severity.HIGH,
|
|
82
|
+
r'"loggers"\s*:\s*\{|"configuredLevel"',
|
|
83
|
+
"OWASP A05:2021",
|
|
84
|
+
"CWE-749",
|
|
85
|
+
),
|
|
86
|
+
(
|
|
87
|
+
"actuator/configprops",
|
|
88
|
+
"Spring Boot Actuator /configprops exposed (app config disclosure)",
|
|
89
|
+
Severity.HIGH,
|
|
90
|
+
r'"contexts"\s*:\s*\{|"beans"\s*:\s*\{',
|
|
91
|
+
"OWASP A05:2021",
|
|
92
|
+
"CWE-200",
|
|
93
|
+
),
|
|
94
|
+
(
|
|
95
|
+
"actuator/mappings",
|
|
96
|
+
"Spring Boot Actuator /mappings exposed (full route table)",
|
|
97
|
+
Severity.MEDIUM,
|
|
98
|
+
r'"mappings"\s*:|"dispatcherServlets"',
|
|
99
|
+
"OWASP A05:2021",
|
|
100
|
+
"CWE-200",
|
|
101
|
+
),
|
|
102
|
+
(
|
|
103
|
+
"actuator/beans",
|
|
104
|
+
"Spring Boot Actuator /beans exposed (bean introspection)",
|
|
105
|
+
Severity.MEDIUM,
|
|
106
|
+
r'"beans"\s*:\s*\{|"applicationContext"',
|
|
107
|
+
"OWASP A05:2021",
|
|
108
|
+
"CWE-200",
|
|
109
|
+
),
|
|
110
|
+
(
|
|
111
|
+
"actuator/metrics",
|
|
112
|
+
"Spring Boot Actuator /metrics exposed",
|
|
113
|
+
Severity.MEDIUM,
|
|
114
|
+
r'"names"\s*:\s*\[',
|
|
115
|
+
"OWASP A05:2021",
|
|
116
|
+
"CWE-200",
|
|
117
|
+
),
|
|
118
|
+
(
|
|
119
|
+
"actuator/health",
|
|
120
|
+
"Spring Boot Actuator /health exposed (reveals dependency states)",
|
|
121
|
+
Severity.MEDIUM,
|
|
122
|
+
r'"status"\s*:\s*"(UP|DOWN|OUT_OF_SERVICE)"',
|
|
123
|
+
"OWASP A05:2021",
|
|
124
|
+
"CWE-200",
|
|
125
|
+
),
|
|
126
|
+
(
|
|
127
|
+
"actuator",
|
|
128
|
+
"Spring Boot Actuator index page exposed",
|
|
129
|
+
Severity.HIGH,
|
|
130
|
+
r'"_links"\s*:\s*\{',
|
|
131
|
+
"OWASP A05:2021",
|
|
132
|
+
"CWE-200",
|
|
133
|
+
),
|
|
134
|
+
# phpMyAdmin — DB admin GUI
|
|
135
|
+
(
|
|
136
|
+
"phpmyadmin/",
|
|
137
|
+
"phpMyAdmin reachable (if unauthed: full DB access)",
|
|
138
|
+
Severity.CRITICAL,
|
|
139
|
+
r"phpMyAdmin|pma_navigation|pma_username",
|
|
140
|
+
"OWASP A07:2021",
|
|
141
|
+
"CWE-285",
|
|
142
|
+
),
|
|
143
|
+
(
|
|
144
|
+
"pma/",
|
|
145
|
+
"phpMyAdmin reachable at /pma/",
|
|
146
|
+
Severity.CRITICAL,
|
|
147
|
+
r"phpMyAdmin|pma_navigation",
|
|
148
|
+
"OWASP A07:2021",
|
|
149
|
+
"CWE-285",
|
|
150
|
+
),
|
|
151
|
+
# Apache mod_status
|
|
152
|
+
(
|
|
153
|
+
"server-status",
|
|
154
|
+
"Apache mod_status /server-status exposed (reveals internal request URLs + IPs)",
|
|
155
|
+
Severity.HIGH,
|
|
156
|
+
r"Apache Server Status|<title>Apache Status</title>",
|
|
157
|
+
"OWASP A05:2021",
|
|
158
|
+
"CWE-200",
|
|
159
|
+
),
|
|
160
|
+
(
|
|
161
|
+
"server-info",
|
|
162
|
+
"Apache mod_info /server-info exposed (full config disclosure)",
|
|
163
|
+
Severity.HIGH,
|
|
164
|
+
r"Apache Server Information|Server Settings",
|
|
165
|
+
"OWASP A05:2021",
|
|
166
|
+
"CWE-200",
|
|
167
|
+
),
|
|
168
|
+
# nginx status
|
|
169
|
+
(
|
|
170
|
+
"nginx_status",
|
|
171
|
+
"nginx stub_status exposed",
|
|
172
|
+
Severity.MEDIUM,
|
|
173
|
+
r"Active connections:\s*\d+\nserver accepts handled",
|
|
174
|
+
"OWASP A05:2021",
|
|
175
|
+
"CWE-200",
|
|
176
|
+
),
|
|
177
|
+
# Prometheus
|
|
178
|
+
(
|
|
179
|
+
"metrics",
|
|
180
|
+
"Prometheus /metrics endpoint exposed (operational telemetry, may contain credentials in labels)",
|
|
181
|
+
Severity.HIGH,
|
|
182
|
+
r"^# HELP\s|^# TYPE\s",
|
|
183
|
+
"OWASP A05:2021",
|
|
184
|
+
"CWE-200",
|
|
185
|
+
),
|
|
186
|
+
(
|
|
187
|
+
"prometheus/api/v1/query",
|
|
188
|
+
"Prometheus query API exposed",
|
|
189
|
+
Severity.HIGH,
|
|
190
|
+
r'"status"\s*:\s*"(success|error)"',
|
|
191
|
+
"OWASP A05:2021",
|
|
192
|
+
"CWE-200",
|
|
193
|
+
),
|
|
194
|
+
# Elasticsearch
|
|
195
|
+
(
|
|
196
|
+
"_cat/indices",
|
|
197
|
+
"Elasticsearch /_cat/indices exposed (no auth)",
|
|
198
|
+
Severity.HIGH,
|
|
199
|
+
r"^(green|yellow|red)\s+(open|close)",
|
|
200
|
+
"OWASP A05:2021",
|
|
201
|
+
"CWE-200",
|
|
202
|
+
),
|
|
203
|
+
(
|
|
204
|
+
"_cluster/health",
|
|
205
|
+
"Elasticsearch /_cluster/health exposed",
|
|
206
|
+
Severity.HIGH,
|
|
207
|
+
r'"cluster_name"\s*:|"number_of_nodes"',
|
|
208
|
+
"OWASP A05:2021",
|
|
209
|
+
"CWE-200",
|
|
210
|
+
),
|
|
211
|
+
(
|
|
212
|
+
"_search",
|
|
213
|
+
"Elasticsearch /_search exposed (no auth)",
|
|
214
|
+
Severity.CRITICAL,
|
|
215
|
+
r'"hits"\s*:\s*\{|"took"\s*:\s*\d+',
|
|
216
|
+
"OWASP A05:2021",
|
|
217
|
+
"CWE-285",
|
|
218
|
+
),
|
|
219
|
+
# Kibana / Grafana / Eureka / Consul panels
|
|
220
|
+
(
|
|
221
|
+
"kibana/",
|
|
222
|
+
"Kibana UI reachable",
|
|
223
|
+
Severity.MEDIUM,
|
|
224
|
+
r"kbn-name|Kibana|kbn-version",
|
|
225
|
+
"OWASP A05:2021",
|
|
226
|
+
"CWE-200",
|
|
227
|
+
),
|
|
228
|
+
(
|
|
229
|
+
"grafana/api/health",
|
|
230
|
+
"Grafana exposed at /grafana/",
|
|
231
|
+
Severity.MEDIUM,
|
|
232
|
+
r'"database"\s*:\s*"(ok|fail)"|"version"',
|
|
233
|
+
"OWASP A05:2021",
|
|
234
|
+
"CWE-200",
|
|
235
|
+
),
|
|
236
|
+
(
|
|
237
|
+
"eureka/apps",
|
|
238
|
+
"Spring Cloud Eureka registry exposed",
|
|
239
|
+
Severity.HIGH,
|
|
240
|
+
r"<applications>|EUREKA",
|
|
241
|
+
"OWASP A05:2021",
|
|
242
|
+
"CWE-200",
|
|
243
|
+
),
|
|
244
|
+
(
|
|
245
|
+
"consul/v1/catalog/services",
|
|
246
|
+
"Consul catalog API exposed",
|
|
247
|
+
Severity.HIGH,
|
|
248
|
+
r"^\s*\{|consul",
|
|
249
|
+
"OWASP A05:2021",
|
|
250
|
+
"CWE-200",
|
|
251
|
+
),
|
|
252
|
+
(
|
|
253
|
+
"v1/agent/checks",
|
|
254
|
+
"Consul agent checks API exposed",
|
|
255
|
+
Severity.HIGH,
|
|
256
|
+
r'"Checks"\s*:|^\s*\{',
|
|
257
|
+
"OWASP A05:2021",
|
|
258
|
+
"CWE-200",
|
|
259
|
+
),
|
|
260
|
+
# GraphQL
|
|
261
|
+
(
|
|
262
|
+
"graphql",
|
|
263
|
+
"GraphQL endpoint reachable",
|
|
264
|
+
Severity.LOW,
|
|
265
|
+
r'"data"\s*:|"errors"\s*:',
|
|
266
|
+
"OWASP A05:2021",
|
|
267
|
+
"CWE-200",
|
|
268
|
+
),
|
|
269
|
+
(
|
|
270
|
+
"graphiql",
|
|
271
|
+
"GraphiQL IDE reachable on prod",
|
|
272
|
+
Severity.MEDIUM,
|
|
273
|
+
r"GraphiQL|graphql-playground",
|
|
274
|
+
"OWASP A05:2021",
|
|
275
|
+
"CWE-200",
|
|
276
|
+
),
|
|
277
|
+
(
|
|
278
|
+
"playground",
|
|
279
|
+
"GraphQL Playground reachable on prod",
|
|
280
|
+
Severity.MEDIUM,
|
|
281
|
+
r"GraphQLPlayground|graphql-playground",
|
|
282
|
+
"OWASP A05:2021",
|
|
283
|
+
"CWE-200",
|
|
284
|
+
),
|
|
285
|
+
# OpenAPI / Swagger
|
|
286
|
+
(
|
|
287
|
+
"swagger-ui/",
|
|
288
|
+
"Swagger UI reachable on prod",
|
|
289
|
+
Severity.MEDIUM,
|
|
290
|
+
r"swagger-ui|SwaggerUI",
|
|
291
|
+
"OWASP A05:2021",
|
|
292
|
+
"CWE-200",
|
|
293
|
+
),
|
|
294
|
+
(
|
|
295
|
+
"swagger-ui.html",
|
|
296
|
+
"Swagger UI reachable (Spring conventional path)",
|
|
297
|
+
Severity.MEDIUM,
|
|
298
|
+
r"swagger-ui|SwaggerUI",
|
|
299
|
+
"OWASP A05:2021",
|
|
300
|
+
"CWE-200",
|
|
301
|
+
),
|
|
302
|
+
(
|
|
303
|
+
"api-docs",
|
|
304
|
+
"OpenAPI /api-docs JSON reachable",
|
|
305
|
+
Severity.LOW,
|
|
306
|
+
r'"openapi"\s*:|"swagger"\s*:',
|
|
307
|
+
"OWASP A05:2021",
|
|
308
|
+
"CWE-200",
|
|
309
|
+
),
|
|
310
|
+
(
|
|
311
|
+
"openapi.json",
|
|
312
|
+
"OpenAPI spec /openapi.json reachable",
|
|
313
|
+
Severity.LOW,
|
|
314
|
+
r'"openapi"\s*:',
|
|
315
|
+
"OWASP A05:2021",
|
|
316
|
+
"CWE-200",
|
|
317
|
+
),
|
|
318
|
+
(
|
|
319
|
+
"v3/api-docs",
|
|
320
|
+
"Spring v3 OpenAPI /v3/api-docs reachable",
|
|
321
|
+
Severity.LOW,
|
|
322
|
+
r'"openapi"\s*:',
|
|
323
|
+
"OWASP A05:2021",
|
|
324
|
+
"CWE-200",
|
|
325
|
+
),
|
|
326
|
+
# Generic admin panels (FP-prone — fingerprint by HTML title/form patterns)
|
|
327
|
+
(
|
|
328
|
+
"admin/",
|
|
329
|
+
"Generic /admin/ reachable",
|
|
330
|
+
Severity.HIGH,
|
|
331
|
+
r"<title>[^<]*(admin|dashboard)[^<]*</title>|name=['\"](username|email|password)['\"]",
|
|
332
|
+
"OWASP A07:2021",
|
|
333
|
+
"CWE-285",
|
|
334
|
+
),
|
|
335
|
+
(
|
|
336
|
+
"administrator/",
|
|
337
|
+
"Joomla-style /administrator/ reachable",
|
|
338
|
+
Severity.HIGH,
|
|
339
|
+
r"Joomla|administrator",
|
|
340
|
+
"OWASP A07:2021",
|
|
341
|
+
"CWE-285",
|
|
342
|
+
),
|
|
343
|
+
(
|
|
344
|
+
"wp-admin/",
|
|
345
|
+
"WordPress /wp-admin/ reachable",
|
|
346
|
+
Severity.MEDIUM,
|
|
347
|
+
r"WordPress|wp-login",
|
|
348
|
+
"OWASP A07:2021",
|
|
349
|
+
"CWE-285",
|
|
350
|
+
),
|
|
351
|
+
# PHP debug
|
|
352
|
+
(
|
|
353
|
+
"phpinfo.php",
|
|
354
|
+
"phpinfo() output reachable",
|
|
355
|
+
Severity.MEDIUM,
|
|
356
|
+
r"PHP Version|phpinfo\(\)",
|
|
357
|
+
"OWASP A05:2021",
|
|
358
|
+
"CWE-200",
|
|
359
|
+
),
|
|
360
|
+
(
|
|
361
|
+
"info.php",
|
|
362
|
+
"PHP info reachable",
|
|
363
|
+
Severity.MEDIUM,
|
|
364
|
+
r"PHP Version|phpinfo\(\)",
|
|
365
|
+
"OWASP A05:2021",
|
|
366
|
+
"CWE-200",
|
|
367
|
+
),
|
|
368
|
+
# Misc framework debug
|
|
369
|
+
(
|
|
370
|
+
"_debug/",
|
|
371
|
+
"Django _debug toolbar reachable",
|
|
372
|
+
Severity.HIGH,
|
|
373
|
+
r"djDebug|debug-toolbar",
|
|
374
|
+
"OWASP A05:2021",
|
|
375
|
+
"CWE-200",
|
|
376
|
+
),
|
|
377
|
+
(
|
|
378
|
+
"debug/vars",
|
|
379
|
+
"Go expvar /debug/vars reachable",
|
|
380
|
+
Severity.MEDIUM,
|
|
381
|
+
r'"cmdline":|"memstats":',
|
|
382
|
+
"OWASP A05:2021",
|
|
383
|
+
"CWE-200",
|
|
384
|
+
),
|
|
385
|
+
(
|
|
386
|
+
"debug/pprof/",
|
|
387
|
+
"Go pprof /debug/pprof/ reachable (heap/CPU profiles)",
|
|
388
|
+
Severity.HIGH,
|
|
389
|
+
r"/debug/pprof/|profiles:",
|
|
390
|
+
"OWASP A05:2021",
|
|
391
|
+
"CWE-200",
|
|
392
|
+
),
|
|
393
|
+
# Robots.txt admin disclosure (low — informational)
|
|
394
|
+
(
|
|
395
|
+
"robots.txt",
|
|
396
|
+
"robots.txt discloses admin paths (information leak)",
|
|
397
|
+
Severity.LOW,
|
|
398
|
+
r"^Disallow:\s*/(admin|api/admin|internal|wp-admin|phpmyadmin)",
|
|
399
|
+
"OWASP A05:2021",
|
|
400
|
+
"CWE-200",
|
|
401
|
+
),
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _verify_fingerprint(body_text: str, fingerprint_re: str | None, content_type: str, content_length: int) -> bool:
|
|
406
|
+
"""Return True if response body looks like the expected admin/debug page."""
|
|
407
|
+
if fingerprint_re is None:
|
|
408
|
+
# No body fingerprint (binary heap dumps etc.); accept any large 200
|
|
409
|
+
# with non-HTML content-type as a signal
|
|
410
|
+
if content_length > 100_000 and "text/html" not in content_type.lower():
|
|
411
|
+
return True
|
|
412
|
+
return False
|
|
413
|
+
sample = body_text[:4096]
|
|
414
|
+
if re.search(fingerprint_re, sample, re.MULTILINE | re.IGNORECASE):
|
|
415
|
+
return True
|
|
416
|
+
return False
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def main(argv: list[str] | None = None) -> int:
|
|
420
|
+
parser = argparse.ArgumentParser(description="Probe for admin / debug / introspection endpoints")
|
|
421
|
+
parser.add_argument("url")
|
|
422
|
+
parser.add_argument("--authorized", action="store_true")
|
|
423
|
+
parser.add_argument("--output", default=None)
|
|
424
|
+
parser.add_argument("--format", choices=("json", "jsonl", "markdown"), default="markdown")
|
|
425
|
+
parser.add_argument("--min-severity", choices=("critical", "high", "medium", "low", "info"), default="info")
|
|
426
|
+
parser.add_argument("--timeout", type=float, default=10.0)
|
|
427
|
+
parser.add_argument("--paths-file", default=None, help="Custom probe set (one path per line); replaces default")
|
|
428
|
+
parser.add_argument(
|
|
429
|
+
"--include-redirects",
|
|
430
|
+
action="store_true",
|
|
431
|
+
help="Flag 302/303 to login as exposure (panel exists, auth gates it)",
|
|
432
|
+
)
|
|
433
|
+
args = parser.parse_args(argv)
|
|
434
|
+
|
|
435
|
+
require_authorization(args.url, args.authorized)
|
|
436
|
+
|
|
437
|
+
base = args.url.rstrip("/") + "/"
|
|
438
|
+
sess = make_session(timeout=args.timeout)
|
|
439
|
+
findings: list[Finding] = []
|
|
440
|
+
|
|
441
|
+
probe_set = PROBES
|
|
442
|
+
if args.paths_file:
|
|
443
|
+
paths = Path(args.paths_file).read_text().splitlines()
|
|
444
|
+
probe_set = [
|
|
445
|
+
(p.strip(), f"Custom path reachable: {p.strip()}", Severity.MEDIUM, None, "custom", "CWE-200")
|
|
446
|
+
for p in paths
|
|
447
|
+
if p.strip()
|
|
448
|
+
]
|
|
449
|
+
|
|
450
|
+
for path, title, sev, fingerprint, control, cwe in probe_set:
|
|
451
|
+
url = base + path.lstrip("/")
|
|
452
|
+
resp = safe_get(sess, url, timeout=args.timeout, allow_redirects=False)
|
|
453
|
+
if resp is None:
|
|
454
|
+
continue
|
|
455
|
+
|
|
456
|
+
is_redirect_to_login = resp.status_code in (301, 302, 303, 307, 308) and re.search(
|
|
457
|
+
r"/(login|sign[-_]in|auth)", resp.headers.get("Location", ""), re.I
|
|
458
|
+
)
|
|
459
|
+
if is_redirect_to_login and args.include_redirects:
|
|
460
|
+
findings.append(
|
|
461
|
+
Finding(
|
|
462
|
+
skill_id=SKILL_ID,
|
|
463
|
+
title=f"{title} (auth-gated: redirects to login)",
|
|
464
|
+
severity=Severity.LOW,
|
|
465
|
+
target=url,
|
|
466
|
+
detail=(
|
|
467
|
+
f"GET {url} redirected to {resp.headers.get('Location')!r}. "
|
|
468
|
+
"The endpoint exists; authentication is in front of it. "
|
|
469
|
+
"Operationally fine, but discloses framework presence to attackers."
|
|
470
|
+
),
|
|
471
|
+
remediation=(
|
|
472
|
+
"If the endpoint is not needed in production, disable it "
|
|
473
|
+
"rather than gating with auth. See references/PLAYBOOK.md."
|
|
474
|
+
),
|
|
475
|
+
cwe_id=cwe,
|
|
476
|
+
affected_control=control,
|
|
477
|
+
)
|
|
478
|
+
)
|
|
479
|
+
continue
|
|
480
|
+
|
|
481
|
+
if resp.status_code != 200:
|
|
482
|
+
continue
|
|
483
|
+
body = resp.text or ""
|
|
484
|
+
ctype = resp.headers.get("Content-Type", "")
|
|
485
|
+
clen = len(resp.content or b"")
|
|
486
|
+
if not _verify_fingerprint(body, fingerprint, ctype, clen):
|
|
487
|
+
continue
|
|
488
|
+
findings.append(
|
|
489
|
+
Finding(
|
|
490
|
+
skill_id=SKILL_ID,
|
|
491
|
+
title=title,
|
|
492
|
+
severity=sev,
|
|
493
|
+
target=url,
|
|
494
|
+
detail=(
|
|
495
|
+
f"GET {url} returned 200 with content matching the "
|
|
496
|
+
f"framework-specific fingerprint for {path!r}. The "
|
|
497
|
+
"endpoint is publicly reachable."
|
|
498
|
+
),
|
|
499
|
+
remediation=(
|
|
500
|
+
f"Take {path!r} behind authentication, restrict by IP allow-list, "
|
|
501
|
+
"or disable the framework feature entirely if not needed in "
|
|
502
|
+
"production. See references/PLAYBOOK.md for per-framework snippets."
|
|
503
|
+
),
|
|
504
|
+
cwe_id=cwe,
|
|
505
|
+
affected_control=control,
|
|
506
|
+
evidence=(("status_code", 200), ("content_length", clen), ("content_type", ctype)),
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
floor = Severity(args.min_severity)
|
|
511
|
+
findings = [f for f in findings if f.severity.numeric >= floor.numeric]
|
|
512
|
+
|
|
513
|
+
emit(findings, args.output, args.format, args.url)
|
|
514
|
+
return exit_code(findings)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
if __name__ == "__main__":
|
|
518
|
+
sys.exit(main())
|