|
1 | | -## |
2 | | -# This module requires Metasploit: https://metasploit.com/download |
3 | | -# Current source: https://github.com/rapid7/metasploit-framework |
4 | | -## |
5 | | - |
6 | | -class MetasploitModule < Msf::Auxiliary |
7 | | - include Msf::Auxiliary::Scanner |
8 | | - include Msf::Exploit::Remote::HttpClient |
9 | | - |
10 | | - def initialize(info = {}) |
11 | | - super( |
12 | | - update_info( |
13 | | - info, |
14 | | - 'Name' => 'ReDoc API Docs UI Exposed', |
15 | | - 'Description' => %q{ |
16 | | - Detects publicly exposed ReDoc API documentation pages. |
17 | | - The module performs safe, read-only GET requests and reports likely |
18 | | - ReDoc instances based on HTML markers. |
19 | | - }, |
20 | | - 'Author' => [ |
21 | | - 'Hamza Sahin (@hamzasahin61)' |
22 | | - ], |
23 | | - 'License' => MSF_LICENSE, |
24 | | - 'Notes' => { |
25 | | - 'Stability' => [CRASH_SAFE], # GET requests only; should not crash or disrupt the target service |
26 | | - 'Reliability' => [], # Does not establish sessions; leaving this empty is acceptable |
27 | | - 'SideEffects' => [] # Add IOC_IN_LOGS if server logs may record these requests |
28 | | - } |
29 | | - ) |
30 | | - ) |
31 | | - |
32 | | - register_options( |
33 | | - [ |
34 | | - Opt::RPORT(80), |
35 | | - OptBool.new('SSL', [true, 'Negotiate SSL/TLS for outgoing connections', false]), |
36 | | - OptString.new('REDOC_PATHS', [ |
37 | | - false, |
38 | | - 'Comma-separated list of paths to probe (overrides defaults)', |
39 | | - nil |
40 | | - ]) |
41 | | - ] |
42 | | - ) |
43 | | - end |
44 | | - |
45 | | - # returns true if the response looks like a ReDoc page |
46 | | - def redoc_like?(res) |
47 | | - return false unless res && res.code.between?(200, 403) |
48 | | - |
49 | | - # Prefer DOM checks |
50 | | - doc = res.get_html_document |
51 | | - if doc |
52 | | - return true if doc.at_css('redoc, redoc-, #redoc') |
53 | | - return true if doc.css('script[src*="redoc"]').any? |
54 | | - return true if doc.css('script[src*="redoc.standalone"]').any? |
55 | | - end |
56 | | - |
57 | | - # Fallback to body/title heuristics |
58 | | - title = res.get_html_title.to_s |
59 | | - body = res.body.to_s |
60 | | - |
61 | | - return true if title =~ /redoc/i |
62 | | - return true if body =~ /<redoc-?/i |
63 | | - return true if body =~ /redoc(\.standalone)?\.js/i |
64 | | - |
65 | | - false |
66 | | - end |
67 | | - |
68 | | - def check_path(path) |
69 | | - res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(path) }) |
70 | | - redoc_like?(res) |
71 | | - end |
72 | | - |
73 | | - def run_host(ip) |
74 | | - vprint_status("#{ip} - scanning for ReDoc") |
75 | | - |
76 | | - paths = |
77 | | - if (ds = datastore['REDOC_PATHS']) && !ds.empty? |
78 | | - ds.split(',').map(&:strip) |
79 | | - else |
80 | | - ['/redoc', '/redoc/', '/docs', '/api/docs', '/openapi'] |
81 | | - end |
82 | | - |
83 | | - hit = paths.find { |p| check_path(p) } |
84 | | - if hit |
85 | | - print_good("#{ip} - ReDoc likely exposed at #{hit}") |
86 | | - report_service(host: ip, port: rport, proto: 'tcp', name: 'http') |
87 | | - else |
88 | | - vprint_status("#{ip} - no ReDoc found") |
89 | | - end |
90 | | - end |
91 | | -end |
| 1 | +## Summary |
| 2 | +This module detects publicly exposed **ReDoc** API documentation pages. |
| 3 | +It performs safe, read-only HTTP GET requests and reports likely ReDoc instances based on common HTML markers. |
| 4 | + |
| 5 | +## Module name |
| 6 | +`auxiliary/scanner/http/redoc_exposed` |
| 7 | + |
| 8 | +## Options |
| 9 | +* **RPORT** – Target TCP port (default: 80) |
| 10 | +* **SSL** – Enable TLS (default: false) |
| 11 | +* **REDOC_PATHS** – Optional comma-separated list of paths to probe. When unset, the module probes: `/redoc, /redoc/, /docs, /api/docs, /openapi`. |
| 12 | + |
| 13 | +## Verification steps |
| 14 | +1. Start `msfconsole` |
| 15 | +2. `use auxiliary/scanner/http/redoc_exposed` |
| 16 | +3. `set RHOSTS <target or file:/path/to/targets.txt>` |
| 17 | +4. (Optional) `set REDOC_PATHS /redoc,/docs` |
| 18 | +5. (Optional) `set RPORT <port>` and/or `set SSL true` |
| 19 | +6. `run` |
| 20 | + |
| 21 | +### Expected |
| 22 | + |
| 23 | +`[+] <ip> - ReDoc likely exposed at <path>` |
| 24 | + |
| 25 | +### Scanning notes |
| 26 | +- DOM-driven checks via `get_html_document`: |
| 27 | + - `<redoc>` / `redoc-` custom elements |
| 28 | + - `#redoc` container |
| 29 | + - `<script src="...redoc(.standalone).js">` |
| 30 | +- Falls back to body/title heuristics if DOM parsing is unavailable. |
| 31 | +- No intrusive actions; **read-only** HTTP GET requests only. |
| 32 | + |
| 33 | +### Example session |
| 34 | + |
| 35 | +use auxiliary/scanner/http/redoc_exposed |
| 36 | +set RHOSTS 127.0.0.1 |
| 37 | +set RPORT 8001 |
| 38 | +set SSL false |
| 39 | +run |
0 commit comments