@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.
Files changed (112) hide show
  1. package/.claude-plugin/plugin.json +8 -3
  2. package/README.md +8 -0
  3. package/commands/pentest.md +5 -0
  4. package/package.json +8 -3
  5. package/skills/analyzing-tls-config/SKILL.md +221 -0
  6. package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
  7. package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
  8. package/skills/analyzing-tls-config/references/THEORY.md +128 -0
  9. package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
  10. package/skills/auditing-cors-policy/SKILL.md +186 -0
  11. package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
  12. package/skills/auditing-cors-policy/references/THEORY.md +142 -0
  13. package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
  14. package/skills/auditing-npm-dependencies/SKILL.md +254 -0
  15. package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
  16. package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
  17. package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
  18. package/skills/auditing-python-dependencies/SKILL.md +251 -0
  19. package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
  20. package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
  21. package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
  22. package/skills/checking-http-security-headers/SKILL.md +176 -0
  23. package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
  24. package/skills/checking-http-security-headers/references/THEORY.md +137 -0
  25. package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
  26. package/skills/checking-license-compliance/SKILL.md +225 -0
  27. package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
  28. package/skills/checking-license-compliance/references/THEORY.md +152 -0
  29. package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
  30. package/skills/composing-vulnerability-report/SKILL.md +212 -0
  31. package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
  32. package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
  33. package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
  34. package/skills/confirming-pentest-authorization/SKILL.md +247 -0
  35. package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
  36. package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
  37. package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
  38. package/skills/defining-pentest-scope/SKILL.md +227 -0
  39. package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
  40. package/skills/defining-pentest-scope/references/THEORY.md +170 -0
  41. package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
  42. package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
  43. package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
  44. package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
  45. package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
  46. package/skills/detecting-debug-endpoints/SKILL.md +207 -0
  47. package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
  48. package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
  49. package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
  50. package/skills/detecting-directory-listing/SKILL.md +206 -0
  51. package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
  52. package/skills/detecting-directory-listing/references/THEORY.md +203 -0
  53. package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
  54. package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
  55. package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
  56. package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
  57. package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
  58. package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
  59. package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
  60. package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
  61. package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
  62. package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
  63. package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
  64. package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
  65. package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
  66. package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
  67. package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
  68. package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
  69. package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
  70. package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
  71. package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
  72. package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
  73. package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
  74. package/skills/detecting-weak-cryptography/SKILL.md +147 -0
  75. package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
  76. package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
  77. package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
  78. package/skills/fingerprinting-server-software/SKILL.md +191 -0
  79. package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
  80. package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
  81. package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
  82. package/skills/generating-executive-summary/SKILL.md +261 -0
  83. package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
  84. package/skills/generating-executive-summary/references/THEORY.md +195 -0
  85. package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
  86. package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
  87. package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
  88. package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
  89. package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
  90. package/skills/performing-penetration-testing/SKILL.md +282 -190
  91. package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
  92. package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
  93. package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
  94. package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
  95. package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
  96. package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
  97. package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
  98. package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
  99. package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
  100. package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
  101. package/skills/recording-pentest-engagement/SKILL.md +253 -0
  102. package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
  103. package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
  104. package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
  105. package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
  106. package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
  107. package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
  108. package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
  109. package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
  110. package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
  111. package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
  112. 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())