Skip to content

Commit fc35a8a

Browse files
authored
Update redoc_exposed.rb
1 parent c2f554b commit fc35a8a

File tree

1 file changed

+52
-42
lines changed

1 file changed

+52
-42
lines changed

modules/auxiliary/scanner/http/redoc_exposed.rb

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,84 @@
1-
require 'msf/core'
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
25

36
class MetasploitModule < Msf::Auxiliary
4-
include Msf::Exploit::Remote::HttpClient
57
include Msf::Auxiliary::Scanner
6-
include Msf::Auxiliary::Report
8+
include Msf::Exploit::Remote::HttpClient
79

810
def initialize(info = {})
911
super(
1012
update_info(
1113
info,
1214
'Name' => 'ReDoc API Docs UI Exposed',
1315
'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+
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.
1619
},
1720
'Author' => [
18-
'Hamza Sahin <[email protected]>'
21+
'Hamza Sahin (@hamzasahin61)'
1922
],
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'
23+
'License' => MSF_LICENSE
2624
)
2725
)
2826

29-
register_advanced_options([
30-
OptString.new('REDOC_PATHS', [ true, 'Comma-separated paths to probe', '/redoc,/docs,/api/docs,/openapi,/redoc/' ])
31-
])
27+
register_options(
28+
[
29+
Opt::RPORT(80),
30+
OptBool.new('SSL', [true, 'Negotiate SSL/TLS for outgoing connections', false]),
31+
OptString.new('REDOC_PATHS', [
32+
false,
33+
'Comma-separated list of paths to probe (overrides defaults)',
34+
nil
35+
])
36+
]
37+
)
3238
end
3339

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?
40+
# returns true if the response looks like a ReDoc page
41+
def redoc_like?(res)
42+
return false unless res && res.code.between?(200, 403)
43+
44+
# Prefer DOM checks
45+
doc = res.get_html_document
46+
if doc
47+
return true if doc.at_css('redoc, redoc-, #redoc')
48+
return true if doc.css('script[src*="redoc"]').any?
49+
return true if doc.css('script[src*="redoc.standalone"]').any?
50+
end
3851

39-
code_ok = res.code && res.code.between?(200, 403)
40-
body = res.body.to_s
52+
# Fallback to body/title heuristics
53+
title = res.get_html_title.to_s
54+
body = res.body.to_s
4155

42-
title_hit = body =~ /<\s*title[^>]*>[^<]*redoc[^<]*<\/\s*title\s*>/i
43-
redoc_hit = body =~ /(redoc(?:\.standalone)?\.js|<\s*redoc-?)/i
56+
return true if title =~ /redoc/i
57+
return true if body =~ /<redoc-?/i
58+
return true if body =~ /redoc(\.standalone)?\.js/i
4459

45-
return :yes if code_ok && (title_hit || redoc_hit)
46-
:no
60+
false
61+
end
62+
63+
def check_path(path)
64+
res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(path) })
65+
redoc_like?(res)
4766
end
4867

4968
def run_host(ip)
5069
vprint_status("#{ip} - scanning for ReDoc")
5170

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 }
71+
paths =
72+
if (ds = datastore['REDOC_PATHS']) && !ds.empty?
73+
ds.split(',').map(&:strip)
74+
else
75+
['/redoc', '/redoc/', '/docs', '/api/docs', '/openapi']
76+
end
5677

78+
hit = paths.find { |p| check_path(p) }
5779
if hit
5880
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-
)
81+
report_service(host: ip, port: rport, proto: 'tcp', name: 'http')
7282
else
7383
vprint_status("#{ip} - no ReDoc found")
7484
end

0 commit comments

Comments
 (0)