7
7
8
8
class MetasploitModule < Msf ::Auxiliary
9
9
10
- include Msf ::Exploit ::Remote ::Tcp
10
+ include Msf ::Exploit ::Remote ::HttpClient
11
11
include Msf ::Auxiliary ::Scanner
12
12
include Msf ::Auxiliary ::WmapScanServer
13
13
include Msf ::Auxiliary ::Report
@@ -16,8 +16,10 @@ def initialize(info = {})
16
16
super ( update_info ( info ,
17
17
'Name' => 'HTTP Open Proxy Detection' ,
18
18
'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.
21
23
} ,
22
24
'References' =>
23
25
[
@@ -31,224 +33,126 @@ def initialize(info = {})
31
33
register_options (
32
34
[
33
35
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>' ] ) ,
48
41
] , self . class )
49
42
50
43
register_wmap_options ( {
51
- 'OrderID' => 1 ,
52
- 'Require' => { } ,
53
- } )
44
+ 'OrderID' => 1 ,
45
+ 'Require' => { } ,
46
+ } )
54
47
end
55
48
56
49
def run_host ( target_host )
57
50
58
- target_ports = [ ]
51
+ check_url = datastore [ 'CHECKURL' ]
59
52
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
62
67
end
63
68
64
- target_ports . push ( datastore [ 'RPORT' ] . to_i )
69
+ target_ports = [ ]
65
70
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 )
68
75
end
69
76
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' ]
74
78
75
79
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 )
82
81
end
83
82
84
83
end
85
84
86
- def check_pattern ( res , pattern )
85
+ def verify_target ( target_host , target_port , target_method , check_url , target_proxy_headers )
87
86
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 } ]" )
95
88
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
113
90
114
91
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
+ )
133
97
134
- if ( res . match ( /Server: (.*)/ ) )
135
- retsrv = $1. chomp
136
- end
98
+ return if not res
137
99
138
- if ( res . match ( /Via: (.*)\( (.*)\) / ) )
139
- retvia = $2
140
- end
100
+ vprint_status ( "#{ peer } - Returns with '#{ res . code } ' status code [#{ target_method } ][#{ check_url } ]" )
141
101
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 ( /,/ )
147
103
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 } " )
150
110
end
151
111
end
152
112
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 ( )
185
115
end
186
116
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 )
198
120
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 } " )
219
122
220
123
report_note (
221
124
:host => target_host ,
222
125
: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)'
224
131
)
225
- end
226
- end
227
-
228
- if ( datastore [ 'VERIFY_HEAD' ] )
229
132
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' ] ) )
231
137
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 } " )
234
139
235
140
report_note (
236
141
:host => target_host ,
237
142
: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)'
239
148
)
240
- end
241
- end
242
149
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" )
249
150
end
250
151
end
251
- end
252
152
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
253
157
end
254
158
end
0 commit comments