CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/http/request.rb
Views: 11704
1
# -*- coding: binary -*-
2
require 'uri'
3
4
5
module Rex
6
module Proto
7
module Http
8
9
###
10
#
11
# HTTP request class.
12
#
13
###
14
class Request < Packet
15
16
PostRequests = ['POST', 'SEARCH']
17
18
##
19
#
20
# Some individual request types.
21
#
22
##
23
24
#
25
# HTTP GET request class wrapper.
26
#
27
class Get < Request
28
def initialize(uri = '/', proto = DefaultProtocol)
29
super('GET', uri, proto)
30
end
31
end
32
33
#
34
# HTTP POST request class wrapper.
35
#
36
class Post < Request
37
def initialize(uri = '/', proto = DefaultProtocol)
38
super('POST', uri, proto)
39
end
40
end
41
42
#
43
# HTTP PUT request class wrapper.
44
#
45
class Put < Request
46
def initialize(uri = '/', proto = DefaultProtocol)
47
super('PUT', uri, proto)
48
end
49
end
50
51
#
52
# Initializes an instance of an HTTP request with the supplied method, URI,
53
# and protocol.
54
#
55
def initialize(method = 'GET', uri = '/', proto = DefaultProtocol)
56
super()
57
58
self.method = method
59
self.raw_uri = uri
60
self.uri_parts = {}
61
self.proto = proto || DefaultProtocol
62
self.chunk_min_size = 1
63
self.chunk_max_size = 10
64
self.uri_encode_mode = 'hex-normal'
65
66
if self.method == 'GET' || self.method == 'CONNECT'
67
self.auto_cl = false
68
end
69
70
update_uri_parts
71
end
72
73
#
74
# Updates the command parts for this specific packet type.
75
#
76
def update_cmd_parts(str)
77
if (md = str.match(/^(.+?)\s+(.+?)\s+HTTP\/(.+?)\r?\n?$/i))
78
self.method = md[1]
79
self.raw_uri = CGI.unescape(md[2])
80
self.proto = md[3]
81
82
update_uri_parts
83
else
84
raise RuntimeError, "Invalid request command string", caller
85
end
86
end
87
88
#
89
# Split the URI into the resource being requested and its query string.
90
#
91
def update_uri_parts
92
# If it has a query string, get the parts.
93
if ((self.raw_uri) and (md = self.raw_uri.match(/(.+?)\?(.*)$/)))
94
self.uri_parts['QueryString'] = parse_cgi_qstring(md[2])
95
self.uri_parts['Resource'] = md[1]
96
# Otherwise, just assume that the URI is equal to the resource being
97
# requested.
98
else
99
self.uri_parts['QueryString'] = {}
100
self.uri_parts['Resource'] = self.raw_uri
101
end
102
103
self.normalize!(resource)
104
# Set the relative resource to the actual resource.
105
self.relative_resource = resource
106
end
107
108
# normalize out multiple slashes, directory traversal, and self referrential directories
109
def normalize!(str)
110
i = 0
111
while (str.gsub!(/(\/\.\/|\/\w+\/\.\.\/|\/\/)/,'/')); i += 1; end
112
i
113
end
114
115
# Puts a URI back together based on the URI parts
116
def uri
117
str = self.uri_parts['Resource'].dup || '/'
118
119
# /././././
120
if self.junk_self_referring_directories
121
str.gsub!(/\//) {
122
'/.' * (rand(3) + 1) + '/'
123
}
124
end
125
126
# /%3faaa=bbbbb
127
# which could possibly decode to "/?aaa=bbbbb", which if the IDS normalizes first, then splits the URI on ?, then it can be bypassed
128
if self.junk_param_start
129
str.sub!(/\//, '/%3f' + Rex::Text.rand_text_alpha(rand(5) + 1) + '=' + Rex::Text.rand_text_alpha(rand(10) + 1) + '/../')
130
end
131
132
# /RAND/../RAND../
133
if self.junk_directories
134
str.gsub!(/\//) {
135
dirs = ''
136
(rand(5)+5).times {
137
dirs << '/' + Rex::Text.rand_text_alpha(rand(5) + 1) + '/..'
138
}
139
dirs + '/'
140
}
141
end
142
143
# ////
144
#
145
# NOTE: this must be done after all other odd directory junk, since they would cancel this out, except junk_end_of_uri, since that a specific slash in a specific place
146
if self.junk_slashes
147
str.gsub!(/\//) {
148
'/' * (rand(3) + 2)
149
}
150
str.sub!(/^[\/]+/, '/') # only one beginning slash!
151
end
152
153
# /%20HTTP/1.0%0d%0a/../../
154
# which decodes to "/ HTTP/1.0\r\n"
155
if self.junk_end_of_uri
156
str.sub!(/^\//, '/%20HTTP/1.0%0d%0a/../../')
157
end
158
159
Rex::Text.uri_encode(str, self.uri_encode_mode)
160
161
if !PostRequests.include?(self.method)
162
if param_string.size > 0
163
str << '?' + param_string
164
end
165
end
166
str
167
end
168
169
def param_string
170
params=[]
171
self.uri_parts['QueryString'].each_pair { |param, value|
172
# inject a random number of params in between each param
173
if self.junk_params
174
rand(10)+5.times {
175
params.push(Rex::Text.rand_text_alpha(rand(16) + 5) + '=' + Rex::Text.rand_text_alpha(rand(10) + 1))
176
}
177
end
178
if value.kind_of?(Array)
179
value.each { |subvalue|
180
params.push(Rex::Text.uri_encode(param, self.uri_encode_mode) + '=' + Rex::Text.uri_encode(subvalue, self.uri_encode_mode))
181
}
182
else
183
if !value.nil?
184
params.push(Rex::Text.uri_encode(param, self.uri_encode_mode) + '=' + Rex::Text.uri_encode(value, self.uri_encode_mode))
185
else
186
params.push(Rex::Text.uri_encode(param, self.uri_encode_mode))
187
end
188
end
189
}
190
191
# inject some junk params at the end of the param list, just to be sure :P
192
if self.junk_params
193
rand(10)+5.times {
194
params.push(Rex::Text.rand_text_alpha(rand(32) + 5) + '=' + Rex::Text.rand_text_alpha(rand(64) + 5))
195
}
196
end
197
params.join('&')
198
end
199
200
# Updates the underlying URI structure
201
def uri=(str)
202
self.raw_uri = str
203
update_uri_parts
204
end
205
206
# Returns a request packet
207
def to_s
208
str = ''
209
if self.junk_pipeline
210
host = ''
211
if self.headers['Host']
212
host = "Host: #{self.headers['Host']}\r\n"
213
end
214
str << "GET / HTTP/1.1\r\n#{host}Connection: Keep-Alive\r\n\r\n" * self.junk_pipeline
215
self.headers['Connection'] = 'Closed'
216
end
217
str + super
218
end
219
220
def body
221
str = super || ''
222
if str.length > 0
223
return str
224
end
225
226
if PostRequests.include?(self.method)
227
return param_string
228
end
229
''
230
end
231
232
#
233
# Returns the command string derived from the three values.
234
#
235
def cmd_string
236
proto_str = (self.proto =~ /^\d/) ? "HTTP/#{self.proto}" : self.proto
237
238
"#{self.method} #{self.uri} #{proto_str}\r\n"
239
end
240
241
#
242
# Returns the resource that is being requested.
243
#
244
def resource
245
self.uri_parts['Resource']
246
end
247
248
#
249
# Changes the resource URI. This is used when making a request relative to
250
# a given mount point.
251
#
252
def resource=(rsrc)
253
self.uri_parts['Resource'] = rsrc
254
end
255
256
#
257
# If there were CGI parameters in the URI, this will hold a hash of each
258
# variable to value. If there is more than one value for a given variable,
259
# an array of each value is returned.
260
#
261
def qstring
262
self.uri_parts['QueryString']
263
end
264
265
#
266
# Returns a hash of variables that contain information about the request,
267
# such as the remote host information.
268
#
269
# TODO
270
#
271
def meta_vars
272
end
273
274
#
275
# The method being used for the request (e.g. GET).
276
#
277
attr_accessor :method
278
#
279
# The raw URI being requested, before any mucking gets to it
280
#
281
attr_accessor :raw_uri
282
283
#
284
# The split up parts of the URI.
285
#
286
attr_accessor :uri_parts
287
#
288
# The protocol to be sent with the request.
289
#
290
attr_accessor :proto
291
292
#
293
# The resource path relative to the root of a server mount point.
294
#
295
attr_accessor :relative_resource
296
297
# add junk directories
298
attr_accessor :junk_directories
299
300
# add junk slashes
301
attr_accessor :junk_slashes
302
303
# add junk self referring directories (aka /././././)
304
attr_accessor :junk_self_referring_directories
305
306
# add junk params
307
attr_accessor :junk_params
308
309
# add junk pipeline requests
310
attr_accessor :junk_pipeline
311
312
# add junk start of params
313
attr_accessor :junk_param_start
314
315
# add junk end of URI
316
attr_accessor :junk_end_of_uri
317
318
# encoding uri
319
attr_accessor :uri_encode_mode
320
321
#
322
# Parses a CGI query string into the var/val combinations.
323
#
324
def parse_cgi_qstring(str)
325
qstring = {}
326
327
# Delimit on each variable
328
str.split(/[;&]/).each { |vv|
329
var = vv
330
val = ''
331
332
if (md = vv.match(/(.+?)=(.*)/))
333
var = md[1]
334
val = md[2]
335
end
336
337
# Add the item to the hash with logic to convert values to an array
338
# if so desired.
339
if (qstring.include?(var))
340
if (qstring[var].kind_of?(Array))
341
qstring[var] << val
342
else
343
curr = self.qstring[var]
344
qstring[var] = [ curr, val ]
345
end
346
else
347
qstring[var] = val
348
end
349
}
350
351
return qstring
352
end
353
354
end
355
356
end
357
end
358
end
359
360