Skip to content

Commit c2f554b

Browse files
committed
auxiliary(scanner/http/redoc_exposed): detect exposed ReDoc API docs UI
1 parent 05c854b commit c2f554b

File tree

1 file changed

+76
-0
lines changed

1 file changed

+76
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
require 'msf/core'
2+
3+
class MetasploitModule < Msf::Auxiliary
4+
include Msf::Exploit::Remote::HttpClient
5+
include Msf::Auxiliary::Scanner
6+
include Msf::Auxiliary::Report
7+
8+
def initialize(info = {})
9+
super(
10+
update_info(
11+
info,
12+
'Name' => 'ReDoc API Docs UI Exposed',
13+
'Description' => %q{
14+
Detects publicly exposed ReDoc API documentation pages which may reveal API surface,
15+
endpoints, request/response models, and other implementation details useful to attackers.
16+
},
17+
'Author' => [
18+
'Hamza Sahin <[email protected]>'
19+
],
20+
'License' => MSF_LICENSE,
21+
'References' => [
22+
[ 'URL', 'https://redocly.com/docs/redoc/' ]
23+
],
24+
'Actions' => [ [ 'Scan', { 'Description' => 'Scan for exposed ReDoc UI' } ] ],
25+
'DefaultAction' => 'Scan'
26+
)
27+
)
28+
29+
register_advanced_options([
30+
OptString.new('REDOC_PATHS', [ true, 'Comma-separated paths to probe', '/redoc,/docs,/api/docs,/openapi,/redoc/' ])
31+
])
32+
end
33+
34+
# Returns :yes if the path looks like a ReDoc page, else :no
35+
def check_path(path)
36+
res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(path) }, 10)
37+
return :no if res.nil?
38+
39+
code_ok = res.code && res.code.between?(200, 403)
40+
body = res.body.to_s
41+
42+
title_hit = body =~ /<\s*title[^>]*>[^<]*redoc[^<]*<\/\s*title\s*>/i
43+
redoc_hit = body =~ /(redoc(?:\.standalone)?\.js|<\s*redoc-?)/i
44+
45+
return :yes if code_ok && (title_hit || redoc_hit)
46+
:no
47+
end
48+
49+
def run_host(ip)
50+
vprint_status("#{ip} - scanning for ReDoc")
51+
52+
paths = (datastore['REDOC_PATHS'] || '').split(',').map(&:strip)
53+
paths = ['/redoc', '/docs', '/api/docs', '/openapi', '/redoc/'] if paths.empty?
54+
55+
hit = paths.find { |p| check_path(p) == :yes }
56+
57+
if hit
58+
print_good("#{ip} - ReDoc likely exposed at #{hit}")
59+
report_service(
60+
host: ip,
61+
port: rport,
62+
proto: 'tcp',
63+
name: (ssl ? 'https' : 'http')
64+
)
65+
report_note(
66+
host: ip,
67+
port: rport,
68+
proto: 'tcp',
69+
type: 'http.redoc.exposed',
70+
data: { path: hit }
71+
)
72+
else
73+
vprint_status("#{ip} - no ReDoc found")
74+
end
75+
end
76+
end

0 commit comments

Comments
 (0)