CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/fuzzers/http/http_form_field.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Exploit::Remote::HttpClient
8
9
def initialize(info = {})
10
super(update_info(info,
11
'Name' => 'HTTP Form Field Fuzzer',
12
'Description' => %q{
13
This module will grab all fields from a form,
14
and launch a series of POST actions, fuzzing the contents
15
of the form fields. You can optionally fuzz headers too
16
(option is enabled by default)
17
},
18
'Author' => [
19
'corelanc0d3r',
20
'Paulino Calderon <calderon[at]websec.mx>' #Added cookie handling
21
],
22
'License' => MSF_LICENSE,
23
'References' =>
24
[
25
['URL','http://www.corelan.be:8800/index.php/2010/11/12/metasploit-module-http-form-field-fuzzer'],
26
]
27
))
28
29
register_options(
30
[
31
OptString.new('URL', [ false, "The URL that contains the form", "/"]),
32
OptString.new('FORM', [ false, "The name of the form to use. Leave empty to fuzz all forms","" ] ),
33
OptString.new('FIELDS', [ false, "Name of the fields to fuzz. Leave empty to fuzz all fields","" ] ),
34
OptString.new('ACTION', [ false, "Form action full URI. Leave empty to autodetect","" ] ),
35
OptInt.new('STARTSIZE', [ true, "Fuzzing string startsize.",1000]),
36
OptInt.new('ENDSIZE', [ true, "Max Fuzzing string size.",40000]),
37
OptInt.new('STEPSIZE', [ true, "Increment fuzzing string each attempt.",1000]),
38
OptInt.new('TIMEOUT', [ true, "Number of seconds to wait for response on GET or POST",15]),
39
OptInt.new('DELAY', [ true, "Number of seconds to wait between 2 actions",0]),
40
OptInt.new('STOPAFTER', [ false, "Stop after x number of consecutive errors",2]),
41
OptBool.new('CYCLIC', [ true, "Use Cyclic pattern instead of A's (fuzzing payload).",true]),
42
OptBool.new('FUZZHEADERS', [ true, "Fuzz headers",true]),
43
OptString.new('HEADERFIELDS', [ false, "Name of the headerfields to fuzz. Leave empty to fuzz all fields","" ] ),
44
OptString.new('TYPES', [ true, "Field types to fuzz","text,password,inputtextbox"]),
45
OptString.new('CODE', [ true, "Response code(s) indicating OK", "200,301,302,303" ] ),
46
OptBool.new('HANDLECOOKIES', [ true, "Appends cookies with every request.",false])
47
])
48
end
49
50
def init_vars
51
proto = "http://"
52
if datastore['SSL']
53
proto = "https://"
54
end
55
56
@send_data = {
57
:uri => '',
58
:version => '1.1',
59
:method => 'POST',
60
:headers => {
61
'Content-Length' => 100,
62
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
63
'Accept-Language' => 'en-us,en;q=0.5',
64
'Accept-Encoding' => 'gzip,deflate',
65
'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
66
'Keep-Alive' => '300',
67
'Connection' => 'keep-alive',
68
'Referer' => proto + datastore['RHOST'] + ":" + datastore['RPORT'].to_s,
69
'Content-Type' => 'application/x-www-form-urlencoded'
70
}
71
}
72
@get_data_headers = {
73
'Referer' => proto + datastore['RHOST'] + ":" + datastore['RPORT'].to_s,
74
}
75
end
76
77
def init_fuzzdata
78
@fuzzsize = datastore['STARTSIZE']
79
@endsize = datastore['ENDSIZE']
80
set_fuzz_payload()
81
@nrerrors = 0
82
end
83
84
def incr_fuzzsize
85
@stepsize = datastore['STEPSIZE'].to_i
86
@fuzzsize = @fuzzsize + @stepsize
87
end
88
89
def set_fuzz_payload
90
if datastore['CYCLIC']
91
@fuzzdata = Rex::Text.pattern_create(@fuzzsize)
92
else
93
@fuzzdata = "A" * @fuzzsize
94
end
95
end
96
97
def is_error_code(code)
98
okcode = false
99
checkcodes = datastore['CODE'].split(",")
100
checkcodes.each do | testcode |
101
testcode = testcode.upcase.gsub(" ","")
102
if testcode == code.to_s().upcase.gsub(" ","")
103
okcode = true
104
end
105
end
106
return okcode
107
end
108
109
def fuzz_this_field(fieldname,fieldtype)
110
fuzzcommands = datastore['FIELDS'].split(",")
111
fuzzme = 0
112
if fuzzcommands.size > 0
113
fuzzcommands.each do |thiscmd|
114
thiscmd = thiscmd.strip
115
if ((fieldname.upcase == thiscmd.upcase) || (thiscmd == "")) && (fuzzme == 0)
116
fuzzme = 1
117
end
118
end
119
else
120
fuzztypes = datastore['TYPES'].split(",")
121
fuzztypes.each do | thistype |
122
if (fieldtype.upcase.strip == thistype.upcase.strip)
123
fuzzme = 1
124
end
125
end
126
end
127
if fuzzme == 1
128
set_fuzz_payload()
129
end
130
return fuzzme
131
end
132
133
def fuzz_this_headerfield(fieldname)
134
fuzzheaderfields = datastore['HEADERFIELDS'].split(",")
135
fuzzme = 0
136
if fuzzheaderfields.size > 0
137
fuzzheaderfields.each do |thisfield|
138
thisfield = thisfield.strip
139
if ((fieldname.upcase == thisfield.upcase) || (thisfield == "")) && (fuzzme == 0)
140
fuzzme = 1
141
end
142
end
143
else
144
fuzzme = 1
145
end
146
if fuzzme == 1
147
set_fuzz_payload()
148
end
149
return fuzzme
150
end
151
152
def do_fuzz_headers(form,headers)
153
headercnt = 0
154
datastr = ""
155
form[:fields].each do | thisfield |
156
normaldata = "blah&"
157
if thisfield[:value]
158
if thisfield[:value] != ""
159
normaldata = thisfield[:value].strip + "&"
160
end
161
end
162
datastr << thisfield[:name].downcase.strip + "=" + normaldata
163
end
164
if datastr.length > 0
165
datastr=datastr[0,datastr.length-1] + "\r\n"
166
else
167
datastr = "\r\n"
168
end
169
# first, check the original header fields and add some others - just for fun
170
myheaders = @send_data[:headers]
171
mysendheaders = @send_data[:headers].dup
172
# get or post ?
173
mysendheaders[:method] = form[:method].upcase
174
myheaders.each do | thisheader |
175
if not headers[thisheader[0]]
176
# add header if needed
177
mysendheaders[thisheader[0]]= thisheader[1]
178
end
179
end
180
nrheaderstofuzz = mysendheaders.size
181
mysendheaders.each do | thisheader|
182
@fuzzheader = mysendheaders.dup
183
@nrerrors = 0
184
fuzzpacket = @send_data.dup
185
fuzzpacket[:method] = mysendheaders[:method]
186
headername = thisheader[0]
187
if fuzz_this_headerfield(headername.to_s().upcase) == 1
188
print_status(" - Fuzzing header '#{headername}' (#{headercnt+1}/#{nrheaderstofuzz})")
189
init_fuzzdata()
190
while @fuzzsize <= @endsize+1
191
@fuzzheader[headername] = @fuzzdata
192
fuzzpacket[:headers] = @fuzzheader
193
response = send_fuzz(fuzzpacket,datastr)
194
if not process_response(response,headername,"header")
195
@fuzzsize = @endsize+2
196
end
197
if datastore['DELAY'] > 0
198
print_status(" (Sleeping for #{datastore['DELAY']} seconds...)")
199
select(nil,nil,nil,datastore['DELAY'])
200
end
201
incr_fuzzsize()
202
end
203
else
204
print_status(" - Skipping header '#{headername}' (#{headercnt+1}/#{nrheaderstofuzz})")
205
end
206
headercnt += 1
207
end
208
end
209
210
def do_fuzz_field(form,field)
211
fieldstofuzz = field.downcase.strip.split(",")
212
@nrerrors = 0
213
while @fuzzsize <= @endsize+1
214
allfields = form[:fields]
215
datastr = ""
216
normaldata = ""
217
allfields.each do | thisfield |
218
dofuzzthis = false
219
if thisfield[:name]
220
fieldstofuzz.each do | fuzzthis |
221
if fuzzthis
222
if (thisfield[:name].downcase.strip == fuzzthis.downcase.strip)
223
dofuzzthis = true
224
end
225
end
226
end
227
if thisfield[:value]
228
normaldata = thisfield[:value].strip
229
else
230
normaldata = ""
231
end
232
if (dofuzzthis)
233
datastr << thisfield[:name].downcase.strip + "=" + @fuzzdata + "&"
234
else
235
datastr << thisfield[:name].downcase.strip + "=" + normaldata + "&"
236
end
237
end
238
end
239
datastr=datastr[0,datastr.length-1]
240
@send_data[:uri] = form[:action]
241
@send_data[:uri] = "/#{form[:action]}" if @send_data[:uri][0,1] != '/'
242
243
@send_data[:method] = form[:method].upcase
244
response = send_fuzz(@send_data,datastr)
245
if not process_response(response,field,"field")
246
return
247
end
248
if datastore['DELAY'] > 0
249
print_status(" (Sleeping for #{datastore['DELAY']} seconds...)")
250
select(nil,nil,nil,datastore['DELAY'])
251
end
252
end
253
end
254
255
def process_response(response,field,type)
256
if response == nil
257
print_error(" No response - #{@nrerrors+1} / #{datastore['STOPAFTER']} - fuzzdata length : #{@fuzzsize}")
258
if @nrerrors+1 >= datastore['STOPAFTER']
259
print_status(" *!* No response : #{type} #{field} | fuzzdata length : #{@fuzzsize}")
260
return false
261
else
262
@nrerrors = @nrerrors + 1
263
end
264
else
265
okcode = is_error_code(response.code)
266
if okcode
267
@nrerrors = 0
268
incr_fuzzsize()
269
end
270
if not okcode and @nrerrors+1 >= datastore['STOPAFTER']
271
print_status(" *!* Error response code #{response.code} | #{type} #{field} | fuzzdata length #{@fuzzsize}")
272
return false
273
else
274
@nrerrors = @nrerrors + 1
275
end
276
end
277
return true
278
end
279
280
def send_fuzz(postdata,data)
281
header = postdata[:headers]
282
response = send_request_raw({
283
'uri' => postdata[:uri],
284
'version' => postdata[:version],
285
'method' => postdata[:method],
286
'headers' => header,
287
'data' => data
288
}, datastore['TIMEOUT'])
289
return response
290
end
291
292
def get_field_val(input)
293
tmp = input.split(/\=/)
294
# get delimiter
295
tmp2 = tmp[1].strip
296
delim = tmp2[0,1]
297
if delim != "'" && delim != '"'
298
delim = ""
299
end
300
tmp3 = tmp[1].split(/>/)
301
tmp4 = tmp3[0].gsub(delim,"")
302
return tmp4
303
end
304
305
def get_form_data(body)
306
print_status("Enumerating form data")
307
body = body.gsub("\r","")
308
body = body.gsub("\n","")
309
bodydata = body.downcase.split(/<form/)
310
# we need part after <form
311
totalforms = bodydata.size - 1
312
print_status(" Number of forms : #{totalforms}")
313
formcnt = 0
314
formidx = 1
315
forms = []
316
while formcnt < totalforms
317
fdata = bodydata[formidx]
318
print_status(" - Enumerating form ##{formcnt+1}")
319
data = fdata.downcase.split(/<\/form>/)
320
# first, get action and name
321
formdata = data[0].downcase.split(/>/)
322
subdata = formdata[0].downcase.split(/ /)
323
namefound = false
324
actionfound = false
325
idfound = false
326
actionname = ""
327
formname = ""
328
formid = ""
329
formmethod = "post"
330
subdata.each do | thisfield |
331
if thisfield.match(/^name=/) and not namefound
332
formname = get_field_val(thisfield)
333
namefound = true
334
end
335
if thisfield.match(/^id=/) and not idfound
336
formid = get_field_val(thisfield)
337
idfound = true
338
end
339
if thisfield.match(/^method=/)
340
formmethod = get_field_val(thisfield)
341
end
342
if thisfield.match(/^action=/) and not actionfound
343
actionname = get_field_val(thisfield)
344
if (actionname.length < datastore['URL'].length) and (datastore['URL'].downcase.index(actionname.downcase).to_i() > -1)
345
actionname = datastore['URL']
346
end
347
actionfound = true
348
end
349
end
350
if datastore['ACTION'].length > 0
351
actionname = datastore['ACTION']
352
actionfound = true
353
end
354
355
if formname == "" and formid != ""
356
formname = formid
357
end
358
if formid == "" and formname != ""
359
formid = formname
360
end
361
if formid == "" and formname == ""
362
formid = "noname_" + (formcnt+1).to_s()
363
formname = formid
364
end
365
idfound = true
366
namefound = true
367
368
formfields = []
369
# input boxes
370
fieldtypemarks = [ '<input', '<select' ]
371
fieldtypemarks.each do | currfieldmark |
372
formfieldcnt=0
373
if (namefound or idfound) and actionfound
374
# get fields in current form - data[0]
375
subdata = data[0].downcase.split(currfieldmark)
376
skipflag=0
377
if subdata.size > 1
378
subdata.each do | thisinput |
379
if skipflag == 1
380
# first, find the delimiter
381
fielddata = thisinput.downcase.split(/>/)
382
fields = fielddata[0].split(/ /)
383
fieldname = ""
384
fieldtype = ""
385
fieldvalue = ""
386
fieldmethod = "post"
387
fieldid = ""
388
fields.each do | thisfield |
389
if thisfield.match(/^type=/)
390
fieldtype = get_field_val(thisfield)
391
end
392
if currfieldmark == "<select" and thisfield.match(/^class=/)
393
fieldtype = get_field_val(thisfield)
394
end
395
if thisfield.match(/^name=/)
396
fieldname = get_field_val(thisfield)
397
end
398
if thisfield.match(/^id=/)
399
fieldid = get_field_val(thisfield)
400
end
401
if thisfield.match(/^value=/)
402
# special case
403
location = fielddata[0].index(thisfield)
404
delta = fielddata[0].size - location
405
remaining = fielddata[0][location,delta]
406
tmp = remaining.strip.split(/\=/)
407
if tmp.size > 1
408
delim = tmp[1][0,1]
409
tmp2 = tmp[1].split(delim)
410
fieldvalue = tmp2[1]
411
end
412
end
413
end
414
if fieldname == "" and fieldid != ""
415
fieldname = fieldid
416
end
417
if fieldid == "" and fieldname != ""
418
fieldid = fieldname
419
end
420
print_status(" Field : #{fieldname}, type #{fieldtype}")
421
if fieldid != ""
422
formfields << {
423
:id => fieldid,
424
:name => fieldname,
425
:type => fieldtype,
426
:value => fieldvalue
427
}
428
formfieldcnt += 1
429
end
430
else
431
skipflag += 1
432
end
433
end
434
end
435
end
436
end
437
print_status(" Nr of fields in form '#{formname}' : #{formfields.size}")
438
# store in multidimensional array
439
forms << {
440
:name => formname,
441
:id => formid,
442
:action => actionname,
443
:method => formmethod,
444
:fields => formfields
445
}
446
formidx = formidx + 1
447
formcnt += 1
448
end
449
450
if forms.size > 0
451
print_status(" Forms : ")
452
end
453
454
forms.each do | thisform |
455
print_status(" - Name : #{thisform[:name]}, ID : #{thisform[:id]}, Action : #{thisform[:action]}, Method : #{thisform[:method]}")
456
end
457
458
return forms
459
end
460
461
def set_cookie(cookie)
462
@get_data_headers["Cookie"]=cookie
463
@send_data[:headers]["Cookie"]=cookie
464
end
465
466
def run
467
init_fuzzdata()
468
init_vars()
469
470
print_status("Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']}")
471
response = send_request_raw(
472
{
473
'uri' => normalize_uri(datastore['URL']),
474
'version' => '1.1',
475
'method' => 'GET',
476
'headers' => @get_data_headers
477
478
}, datastore['TIMEOUT'])
479
if response == nil
480
print_error("No response")
481
return
482
end
483
484
if datastore['HANDLECOOKIES']
485
cookie = response.get_cookies
486
set_cookie(cookie)
487
print_status("Set cookie: #{cookie}")
488
print_status("Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']} using cookies")
489
490
response = send_request_raw(
491
{
492
'uri' => normalize_uri(datastore['URL']),
493
'version' => '1.1',
494
'method' => 'GET',
495
'headers' => @get_data_headers
496
}, datastore['TIMEOUT'])
497
end
498
if response == nil
499
print_error("No response")
500
return
501
end
502
print_status("Code : #{response.code}")
503
okcode = is_error_code(response.code)
504
if not okcode
505
print_error("Server replied with error code. Check URL or set CODE to another value, and try again.")
506
return
507
end
508
if response.body
509
formfound = response.body.downcase.index("<form")
510
if formfound
511
formdata = get_form_data(response.body)
512
# fuzz !
513
# for each form that needs to be fuzzed
514
formdata.each do | thisform |
515
if thisform[:name].length > 0
516
if ((datastore['FORM'].strip == "") || (datastore['FORM'].upcase.strip == thisform[:name].upcase.strip)) && (thisform[:fields].size > 0)
517
print_status("Fuzzing fields in form #{thisform[:name].upcase.strip}")
518
# for each field in this form, fuzz one field at a time
519
formfields = thisform[:fields]
520
formfields.each do | thisfield |
521
if thisfield[:name]
522
if fuzz_this_field(thisfield[:name],thisfield[:type]) == 1
523
print_status(" - Fuzzing field #{thisfield[:name]}")
524
do_fuzz_field(thisform,thisfield[:name])
525
init_fuzzdata()
526
end
527
end
528
end
529
print_status("Done fuzzing fields in form #{thisform[:name].upcase.strip}")
530
end
531
# fuzz headers ?
532
if datastore['FUZZHEADERS']
533
print_status("Fuzzing header fields")
534
do_fuzz_headers(thisform,response.headers)
535
end
536
end
537
end
538
539
else
540
print_error("No form found in response body")
541
print_status(response.body)
542
return
543
end
544
else
545
print_error("No response data")
546
end
547
548
end
549
end
550
551