Skip to content

Commit 00e4a88

Browse files
committed
Land rapid7#7574, Update open_proxy aux module
2 parents cd01b07 + d3adfff commit 00e4a88

File tree

1 file changed

+77
-173
lines changed

1 file changed

+77
-173
lines changed

modules/auxiliary/scanner/http/open_proxy.rb

Lines changed: 77 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class MetasploitModule < Msf::Auxiliary
99

10-
include Msf::Exploit::Remote::Tcp
10+
include Msf::Exploit::Remote::HttpClient
1111
include Msf::Auxiliary::Scanner
1212
include Msf::Auxiliary::WmapScanServer
1313
include Msf::Auxiliary::Report
@@ -16,8 +16,10 @@ def initialize(info = {})
1616
super(update_info(info,
1717
'Name' => 'HTTP Open Proxy Detection',
1818
'Description' => %q{
19-
Checks if an HTTP proxy is open. False positive are avoided
20-
verifing the HTTP return code and matching a pattern.
19+
Checks if an HTTP proxy is open. False positive are avoided
20+
verifying the HTTP return code and matching a pattern.
21+
The CONNECT method is verified only the return code.
22+
HTTP headers are shown regarding the use of proxy or load balancer.
2123
},
2224
'References' =>
2325
[
@@ -31,224 +33,126 @@ def initialize(info = {})
3133
register_options(
3234
[
3335
Opt::RPORT(8080),
34-
OptBool.new('MULTIPORTS', [ false, 'Multiple ports will be used : 80, 1080, 3128, 8080, 8123', false ]),
35-
OptBool.new('RANDOMIZE_PORTS', [ false, 'Randomize the order the ports are probed', false ]),
36-
OptBool.new('VERIFY_CONNECT', [ false, 'Enable test for CONNECT method', false ]),
37-
OptBool.new('VERIFY_HEAD', [ false, 'Enable test for HEAD method', false ]),
38-
OptBool.new('LOOKUP_PUBLIC_ADDRESS', [ false, 'Enable test for retrieve public IP address via RIPE.net', false ]),
39-
OptString.new('SITE', [ true, 'The web site to test via alleged web proxy (default is www.google.com)', 'www.google.com' ]),
40-
OptString.new('ValidCode', [ false, "Valid HTTP code for a successfully request", '200,302' ]),
41-
OptString.new('ValidPattern', [ false, "Valid HTTP server header for a successfully request", 'server: gws' ]),
42-
OptString.new('UserAgent', [ true, 'The HTTP User-Agent sent in the request', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)' ]),
43-
], self.class)
44-
45-
register_advanced_options(
46-
[
47-
OptString.new('RIPE_ADDRESS', [ true, 'www.ripe.net IP address', '193.0.6.139' ]),
36+
OptBool.new('MULTIPORTS', [ false, 'Multiple ports will be used: 80, 443, 1080, 3128, 8000, 8080, 8123', false ]),
37+
OptBool.new('VERIFYCONNECT', [ false, 'Enable CONNECT HTTP method check', false ]),
38+
OptString.new('CHECKURL', [ true, 'The web site to test via alleged web proxy', 'http://www.google.com' ]),
39+
OptString.new('VALIDCODES', [ true, "Valid HTTP code for a successfully request", '200,302' ]),
40+
OptString.new('VALIDPATTERN', [ true, "Valid pattern match (case-sensitive into the headers and HTML body) for a successfully request", '<TITLE>302 Moved</TITLE>' ]),
4841
], self.class)
4942

5043
register_wmap_options({
51-
'OrderID' => 1,
52-
'Require' => {},
53-
})
44+
'OrderID' => 1,
45+
'Require' => {},
46+
})
5447
end
5548

5649
def run_host(target_host)
5750

58-
target_ports = []
51+
check_url = datastore['CHECKURL']
5952

60-
if datastore['MULTIPORTS']
61-
target_ports = [ 80, 1080, 3128, 8080, 8123 ]
53+
if datastore['VERIFYCONNECT']
54+
target_method = 'CONNECT'
55+
# CONNECT doesn't need <scheme> but need port
56+
check_url = check_url.gsub(/[http:\/\/|https:\/\/]/, '')
57+
if check_url !~ /:443$/
58+
check_url = check_url + ":443"
59+
end
60+
else
61+
target_method = 'GET'
62+
# GET only http request
63+
check_url = check_url.gsub(/https:\/\//, '')
64+
if check_url !~ /^http:\/\//i
65+
check_url = 'http://' + check_url
66+
end
6267
end
6368

64-
target_ports.push(datastore['RPORT'].to_i)
69+
target_ports = []
6570

66-
if datastore['RANDOMIZE_PORTS']
67-
target_ports = target_ports.sort_by { rand }
71+
if datastore['MULTIPORTS']
72+
target_ports = [ 80, 443, 1080, 3128, 8000, 8080, 8123 ]
73+
else
74+
target_ports.push(datastore['RPORT'].to_i)
6875
end
6976

70-
target_ports = target_ports.uniq
71-
72-
site = datastore['SITE']
73-
user_agent = datastore['UserAgent']
77+
target_proxy_headers = [ 'Forwarded', 'Front-End-Https', 'Max-Forwards', 'Via', 'X-Cache', 'X-Cache-Lookup', 'X-Client-IP', 'X-Forwarded-For', 'X-Forwarded-Host' ]
7478

7579
target_ports.each do |target_port|
76-
datastore['RPORT'] = target_port
77-
if target_host == site
78-
print_error("Target is the same as proxy site.")
79-
else
80-
check_host(target_host,target_port,site,user_agent)
81-
end
80+
verify_target(target_host,target_port,target_method,check_url,target_proxy_headers)
8281
end
8382

8483
end
8584

86-
def check_pattern(res,pattern)
85+
def verify_target(target_host,target_port,target_method,check_url,target_proxy_headers)
8786

88-
if (res =~ /#{pattern}/i)
89-
return 1
90-
else
91-
return 0
92-
end
93-
94-
end
87+
vprint_status("#{peer} - Sending a web request... [#{target_method}][#{check_url}]")
9588

96-
def write_request(method,site,user_agent)
97-
98-
request = method + " http://" + site + "/ HTTP/1.1" + "\r\n" +
99-
"Host: " + site + "\r\n" +
100-
"Connection: close" + "\r\n" +
101-
"User-Agent: #{user_agent}" + "\r\n" +
102-
"Accept-Encoding: *" + "\r\n" +
103-
"Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7" + "\r\n" +
104-
"Cache-Control: no" + "\r\n" +
105-
"Accept-Language: de,en;q=0.7,en-us;q=0.3" + "\r\n" +
106-
"\r\n"
107-
108-
return request
109-
110-
end
111-
112-
def send_request(site,user_agent)
89+
datastore['RPORT'] = target_port
11390

11491
begin
115-
connect
116-
117-
request = write_request('GET',site,user_agent)
118-
sock.put(request)
119-
res = sock.get_once(-1, 10)
120-
121-
disconnect
122-
123-
validcodes = datastore['ValidCode'].split(/,/)
124-
125-
is_valid = 0
126-
retcode = 0
127-
retvia = 'n/a'
128-
retsrv = 'n/a'
129-
130-
if (res and res.match(/^HTTP\/1\.[01]\s+([^\s]+)\s+(.*)/))
131-
132-
retcode = $1
92+
res = send_request_cgi(
93+
'uri' => check_url,
94+
'method' => target_method,
95+
'version' => '1.1'
96+
)
13397

134-
if (res.match(/Server: (.*)/))
135-
retsrv = $1.chomp
136-
end
98+
return if not res
13799

138-
if (res.match(/Via: (.*)\((.*)\)/))
139-
retvia = $2
140-
end
100+
vprint_status("#{peer} - Returns with '#{res.code}' status code [#{target_method}][#{check_url}]")
141101

142-
validcodes.each do |validcode|
143-
if (retcode.to_i == validcode.to_i)
144-
is_valid += 1
145-
end
146-
end
102+
valid_codes = datastore['VALIDCODES'].split(/,/)
147103

148-
if (check_pattern(res,datastore['ValidPattern']) == 1)
149-
is_valid += 1
104+
target_proxy_headers_results = []
105+
target_proxy_headers.each do |proxy_header|
106+
if (res.headers.to_s.match(/#{proxy_header}: (.*)/))
107+
proxy_header_value = $1
108+
# Ok...I don't like it but works...
109+
target_proxy_headers_results.push("\n |_ #{proxy_header}: #{proxy_header_value}")
150110
end
151111
end
152112

153-
retres = [ is_valid, retcode, retvia, retsrv ]
154-
155-
return retres
156-
157-
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
158-
rescue ::Timeout::Error, ::Errno::EPIPE
159-
end
160-
end
161-
162-
def send_request_ripe(user_agent)
163-
164-
ripe_address = datastore['RIPE_ADDRESS']
165-
166-
begin
167-
connect
168-
169-
request = write_request('GET',ripe_address,user_agent)
170-
sock.put(request)
171-
res = sock.get_once(-1, 10)
172-
173-
disconnect
174-
175-
retres = 0
176-
177-
if (res and res.match(/^HTTP\/1\.[01]\s+([^\s]+)\s+(.*)/))
178-
179-
retcode = $1
180-
181-
if (retcode.to_i == 200)
182-
res.match(/Your IP Address is: <strong>(\s+)([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})(\s+)<\/strong>/m)
183-
retres = "#{$2}.#{$3}.#{$4}.#{$5}"
184-
end
113+
if target_proxy_headers_results.any?
114+
proxy_headers = target_proxy_headers_results.join()
185115
end
186116

187-
return retres
188-
189-
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
190-
rescue ::Timeout::Error, ::Errno::EPIPE
191-
end
192-
end
193-
194-
def check_host(target_host,target_port,site,user_agent)
195-
vprint_status("Checking #{target_host}:#{target_port} [#{site}]")
196-
197-
is_valid,retcode,retvia,retsrv = send_request(site,user_agent)
117+
if datastore['VERIFYCONNECT']
118+
# Verifiying CONNECT we check only the return code
119+
if valid_codes.include?(res.code.to_s)
198120

199-
if (is_valid == 2)
200-
201-
print_status("#{target_host}:#{target_port} is a potentially OPEN proxy [#{retcode}] (#{retvia})")
202-
203-
report_note(
204-
:host => target_host,
205-
:port => target_port,
206-
:method => 'GET',
207-
:proto => 'tcp',
208-
:sname => (ssl ? 'https' : 'http'),
209-
:type => 'OPEN PROXY',
210-
:data => 'Open proxy'
211-
)
212-
213-
if (datastore['VERIFY_CONNECT'])
214-
215-
permit_connect,retcode,retvia,retsrv = send_request(site,user_agent)
216-
217-
if (permit_connect == 2)
218-
print_status("#{target_host}:#{target_port} CONNECT method successfully tested")
121+
print_good("#{peer} - Potentially open proxy [#{res.code}][#{target_method}]#{proxy_headers}")
219122

220123
report_note(
221124
:host => target_host,
222125
:port => target_port,
223-
:method => 'CONNECT'
126+
:method => target_method,
127+
:proto => 'tcp',
128+
:sname => (ssl ? 'https' : 'http'),
129+
:type => 'OPEN HTTP PROXY',
130+
:data => 'Open http proxy (CONNECT)'
224131
)
225-
end
226-
end
227-
228-
if (datastore['VERIFY_HEAD'])
229132

230-
permit_connect,retcode,retvia,retsrv = send_request(site,user_agent)
133+
end
134+
else
135+
# Verify return code && (headers.pattern or body.pattern)
136+
if valid_codes.include?(res.code.to_s) && (res.headers.include?(datastore['VALIDPATTERN']) || res.body.include?(datastore['VALIDPATTERN']))
231137

232-
if (permit_connect == 2)
233-
print_status("#{target_host}:#{target_port} HEAD method successfully tested")
138+
print_good("#{peer} - Potentially open proxy [#{res.code}][#{target_method}]#{proxy_headers}")
234139

235140
report_note(
236141
:host => target_host,
237142
:port => target_port,
238-
:method => 'HEAD'
143+
:method => target_method,
144+
:proto => 'tcp',
145+
:sname => (ssl ? 'https' : 'http'),
146+
:type => 'OPEN HTTP PROXY',
147+
:data => 'Open http proxy (GET)'
239148
)
240-
end
241-
end
242149

243-
if (datastore['LOOKUP_PUBLIC_ADDRESS'])
244-
245-
retres = send_request_ripe(user_agent)
246-
247-
if (retres != 0)
248-
print_status("#{target_host}:#{target_port} using #{retres} public IP address")
249150
end
250151
end
251-
end
252152

153+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e
154+
vprint_error("#{peer} - The port '#{target_port}' is unreachable!")
155+
return nil
156+
end
253157
end
254158
end

0 commit comments

Comments
 (0)