add http form fuzzer from corelanc0d3r
git-svn-id: file:///home/svn/framework3/trunk@11013 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
375a655317
commit
cf6da2c594
|
@ -0,0 +1,540 @@
|
|||
##
|
||||
# $Id$
|
||||
##
|
||||
|
||||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
##
|
||||
#
|
||||
# Tip : run "show advanced" for more options
|
||||
#
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'HTTP Form field fuzzer',
|
||||
'Description' => %q{
|
||||
This module will grab all fields from a form,
|
||||
and launch a series of POST actions, fuzzing the contents
|
||||
of the form fields. You can optionally fuzz headers too
|
||||
(option is enabled by default)
|
||||
},
|
||||
'Author' => [ 'corelanc0d3r' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Version' => '$Revision$',
|
||||
'References' =>
|
||||
[
|
||||
['URL','http://www.corelan.be:8800/index.php/2010/11/12/metasploit-module-http-form-field-fuzzer'],
|
||||
]
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new("URL", [ false, "The URL that contains the form", "/"]),
|
||||
OptString.new("FORM", [ false, "The name of the form to use. Leave empty to fuzz all forms","" ] ),
|
||||
OptString.new("FIELDS", [ false, "Name of the fields to fuzz. Leave empty to fuzz all fields","" ] ),
|
||||
OptString.new("ACTION", [ false, "Form action full URI. Leave empty to autodetect","" ] ),
|
||||
OptInt.new('STARTSIZE', [ true, "Fuzzing string startsize.",1000]),
|
||||
OptInt.new('ENDSIZE', [ true, "Max Fuzzing string size.",40000]),
|
||||
OptInt.new('STEPSIZE', [ true, "Increment fuzzing string each attempt.",1000]),
|
||||
OptInt.new('TIMEOUT', [ true, "Number of seconds to wait for response on GET or POST",15]),
|
||||
OptInt.new('DELAY', [ true, "Number of seconds to wait between 2 actions",0]),
|
||||
OptInt.new('STOPAFTER', [ false, "Stop after x number of consecutive errors",2]),
|
||||
OptBool.new('CYCLIC', [ true, "Use Cyclic pattern instead of A's (fuzzing payload).",true]),
|
||||
OptBool.new('FUZZHEADERS', [ true, "Fuzz headers",true]),
|
||||
OptString.new("HEADERFIELDS", [ false, "Name of the headerfields to fuzz. Leave empty to fuzz all fields","" ] ),
|
||||
OptString.new('TYPES', [ true, "Field types to fuzz","text,password,inputtextbox"]),
|
||||
OptString.new("CODE", [ true, "Response code(s) indicating OK", "200,301,302,303" ] )
|
||||
], self.class )
|
||||
end
|
||||
|
||||
def init_vars
|
||||
proto = "http://"
|
||||
if datastore['SSL']
|
||||
proto = "https://"
|
||||
end
|
||||
useragent="Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.15) Gecko/2009102814 Ubuntu/8.10 (intrepid) Firefox/3.0.15"
|
||||
if datastore['UserAgent'] != nil
|
||||
if datastore['UserAgent'].length > 0
|
||||
useragent = datastore['UserAgent']
|
||||
end
|
||||
end
|
||||
host = datastore['RHOST']
|
||||
if datastore['VHOST']
|
||||
if datastore['VHOST'].length > 0
|
||||
host = datastore['VHOST']
|
||||
end
|
||||
end
|
||||
@send_data= {
|
||||
:uri => '',
|
||||
:version => '1.1',
|
||||
:method => 'POST',
|
||||
:headers => {
|
||||
'Content-Length' => 100,
|
||||
'Host' => host,
|
||||
'User-Agent' => useragent,
|
||||
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language' => 'en-us,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip,deflate',
|
||||
'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
||||
'Keep-Alive' => '300',
|
||||
'Connection' => 'keep-alive',
|
||||
'Referer' => proto + datastore['RHOST'] + ":" + datastore['RPORT'],
|
||||
'Content-Type' => 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def init_fuzzdata
|
||||
@fuzzsize = datastore['STARTSIZE']
|
||||
@endsize = datastore['ENDSIZE']
|
||||
set_fuzz_payload()
|
||||
@nrerrors = 0
|
||||
end
|
||||
|
||||
def incr_fuzzsize
|
||||
@stepsize = datastore['STEPSIZE'].to_i
|
||||
@fuzzsize = @fuzzsize + @stepsize
|
||||
end
|
||||
|
||||
def set_fuzz_payload
|
||||
if datastore['CYCLIC']
|
||||
@fuzzdata = Rex::Text.pattern_create(@fuzzsize)
|
||||
else
|
||||
@fuzzdata = "A" * @fuzzsize
|
||||
end
|
||||
end
|
||||
|
||||
def is_error_code(code)
|
||||
okcode = false
|
||||
checkcodes = datastore['CODE'].split(",")
|
||||
checkcodes.each do | testcode |
|
||||
testcode = testcode.upcase.gsub(" ","")
|
||||
if testcode == code.to_s().upcase.gsub(" ","")
|
||||
okcode = true
|
||||
end
|
||||
end
|
||||
return okcode
|
||||
end
|
||||
|
||||
def fuzz_this_field(fieldname,fieldtype)
|
||||
fuzzcommands = datastore['FIELDS'].split(",")
|
||||
fuzzme = 0
|
||||
if fuzzcommands.size > 0
|
||||
fuzzcommands.each do |thiscmd|
|
||||
thiscmd = thiscmd.strip
|
||||
if ((fieldname.upcase == thiscmd.upcase) || (thiscmd == "")) && (fuzzme == 0)
|
||||
fuzzme = 1
|
||||
end
|
||||
end
|
||||
else
|
||||
fuzztypes = datastore['TYPES'].split(",")
|
||||
fuzztypes.each do | thistype |
|
||||
if (fieldtype.upcase.strip == thistype.upcase.strip)
|
||||
fuzzme = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if fuzzme == 1
|
||||
set_fuzz_payload()
|
||||
end
|
||||
return fuzzme
|
||||
end
|
||||
|
||||
def fuzz_this_headerfield(fieldname)
|
||||
fuzzheaderfields = datastore['HEADERFIELDS'].split(",")
|
||||
fuzzme = 0
|
||||
if fuzzheaderfields.size > 0
|
||||
fuzzheaderfields.each do |thisfield|
|
||||
thisfield = thisfield.strip
|
||||
if ((fieldname.upcase == thisfield.upcase) || (thisfield == "")) && (fuzzme == 0)
|
||||
fuzzme = 1
|
||||
end
|
||||
end
|
||||
else
|
||||
fuzzme = 1
|
||||
end
|
||||
if fuzzme == 1
|
||||
set_fuzz_payload()
|
||||
end
|
||||
return fuzzme
|
||||
end
|
||||
|
||||
def do_fuzz_headers(form,headers)
|
||||
headercnt = 0
|
||||
datastr = ""
|
||||
form[:fields].each do | thisfield |
|
||||
normaldata = "blah&"
|
||||
if thisfield[:value]
|
||||
if thisfield[:value] != ""
|
||||
normaldata = thisfield[:value].strip + "&"
|
||||
end
|
||||
end
|
||||
datastr << thisfield[:name].downcase.strip + "=" + normaldata
|
||||
end
|
||||
if datastr.length > 0
|
||||
datastr=datastr[0,datastr.length-1] + "\r\n"
|
||||
else
|
||||
datastr = "\r\n"
|
||||
end
|
||||
#first, check the original header fields and add some others - just for fun
|
||||
myheaders = @send_data[:headers]
|
||||
mysendheaders = @send_data[:headers].dup
|
||||
#get or post ?
|
||||
mysendheaders[:method] = form[:method].upcase
|
||||
myheaders.each do | thisheader |
|
||||
if not headers[thisheader[0]]
|
||||
#add header if needed
|
||||
mysendheaders[thisheader[0]]= thisheader[1]
|
||||
end
|
||||
end
|
||||
nrheaderstofuzz = mysendheaders.size
|
||||
mysendheaders.each do | thisheader|
|
||||
@fuzzheader = mysendheaders.dup
|
||||
@nrerrors = 0
|
||||
fuzzpacket = @send_data.dup
|
||||
fuzzpacket[:method] = mysendheaders[:method]
|
||||
headername = thisheader[0]
|
||||
if fuzz_this_headerfield(headername.to_s().upcase) == 1
|
||||
print_status(" - Fuzzing header '#{headername}' (#{headercnt+1}/#{nrheaderstofuzz})")
|
||||
init_fuzzdata()
|
||||
while @fuzzsize <= @endsize+1
|
||||
@fuzzheader[headername] = @fuzzdata
|
||||
fuzzpacket[:headers] = @fuzzheader
|
||||
response = send_fuzz(fuzzpacket,datastr)
|
||||
if not process_response(response,headername,"header")
|
||||
@fuzzsize = @endsize+2
|
||||
end
|
||||
if datastore['DELAY'] > 0
|
||||
print_status(" (Sleeping for #{datastore['DELAY']} seconds...)")
|
||||
select(nil,nil,nil,datastore['DELAY'])
|
||||
end
|
||||
incr_fuzzsize()
|
||||
end
|
||||
else
|
||||
print_status(" - Skipping header '#{headername}' (#{headercnt+1}/#{nrheaderstofuzz})")
|
||||
end
|
||||
headercnt += 1
|
||||
end
|
||||
end
|
||||
|
||||
def do_fuzz_field(form,field)
|
||||
fieldstofuzz = field.downcase.strip.split(",")
|
||||
@nrerrors = 0
|
||||
while @fuzzsize <= @endsize+1
|
||||
allfields = form[:fields]
|
||||
datastr = ""
|
||||
normaldata = ""
|
||||
allfields.each do | thisfield |
|
||||
dofuzzthis = false
|
||||
if thisfield[:name]
|
||||
fieldstofuzz.each do | fuzzthis |
|
||||
if fuzzthis
|
||||
if (thisfield[:name].downcase.strip == fuzzthis.downcase.strip)
|
||||
dofuzzthis = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if thisfield[:value]
|
||||
normaldata = thisfield[:value].strip
|
||||
else
|
||||
normaldata = ""
|
||||
end
|
||||
if (dofuzzthis)
|
||||
datastr << thisfield[:name].downcase.strip + "=" + @fuzzdata + "&"
|
||||
else
|
||||
datastr << thisfield[:name].downcase.strip + "=" + normaldata + "&"
|
||||
end
|
||||
end
|
||||
end
|
||||
datastr=datastr[0,datastr.length-1]
|
||||
@send_data[:uri] = form[:action]
|
||||
@send_data[:method] = form[:method].upcase
|
||||
response = send_fuzz(@send_data,datastr)
|
||||
if not process_response(response,field,"field")
|
||||
return
|
||||
end
|
||||
if datastore['DELAY'] > 0
|
||||
print_status(" (Sleeping for #{datastore['DELAY']} seconds...)")
|
||||
select(nil,nil,nil,datastore['DELAY'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_response(response,field,type)
|
||||
if response == nil
|
||||
print_error(" [-] No response - #{@nrerrors+1} / #{datastore['STOPAFTER']} - fuzzdata length : #{@fuzzsize}")
|
||||
if @nrerrors+1 >= datastore['STOPAFTER']
|
||||
print_status(" *!* No response : #{type} #{field} | fuzzdata length : #{@fuzzsize}")
|
||||
return false
|
||||
else
|
||||
@nrerrors = @nrerrors + 1
|
||||
end
|
||||
else
|
||||
okcode = is_error_code(response.code)
|
||||
if okcode
|
||||
@nrerrors = 0
|
||||
incr_fuzzsize()
|
||||
end
|
||||
if not okcode and @nrerrors+1 >= datastore['STOPAFTER']
|
||||
print_status(" *!* Error response code #{response.code} | #{type} #{field} | fuzzdata length #{@fuzzsize}")
|
||||
return false
|
||||
else
|
||||
@nrerrors = @nrerrors + 1
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def send_fuzz(postdata,data)
|
||||
header = postdata[:headers]
|
||||
response = send_request_raw({
|
||||
'uri' => postdata[:uri],
|
||||
'version' => postdata[:version],
|
||||
'method' => postdata[:method],
|
||||
'headers' => header,
|
||||
'data' => data
|
||||
}, datastore['TIMEOUT'])
|
||||
return response
|
||||
end
|
||||
|
||||
def get_field_val(input)
|
||||
tmp = input.split(/=/)
|
||||
#get delimeter
|
||||
tmp2 = tmp[1].strip
|
||||
delim = tmp2[0,1]
|
||||
if delim != "'" && delim != '"'
|
||||
delim = ""
|
||||
end
|
||||
tmp3 = tmp[1].split(/>/)
|
||||
tmp4 = tmp3[0].gsub(delim,"")
|
||||
return tmp4
|
||||
end
|
||||
|
||||
def get_form_data(body)
|
||||
print_status("[+] Enumerating form data")
|
||||
body = body.gsub("\r","")
|
||||
body = body.gsub("\n","")
|
||||
bodydata = body.downcase.split(/<form/)
|
||||
#we need part after <form
|
||||
totalforms = bodydata.size - 1
|
||||
print_status(" Number of forms : #{totalforms}")
|
||||
formcnt = 0
|
||||
formidx = 1
|
||||
forms = []
|
||||
while formcnt < totalforms
|
||||
fdata = bodydata[formidx]
|
||||
print_status(" - Enumerating form ##{formcnt+1}")
|
||||
data = fdata.downcase.split(/<\/form>/)
|
||||
#first, get action and name
|
||||
formdata = data[0].downcase.split(/>/)
|
||||
subdata = formdata[0].downcase.split(/ /)
|
||||
namefound = false
|
||||
actionfound = false
|
||||
idfound = false
|
||||
actionname = ""
|
||||
formname = ""
|
||||
formid = ""
|
||||
formmethod = "post"
|
||||
subdata.each do | thisfield |
|
||||
if thisfield.match(/^name=/) and not namefound
|
||||
formname = get_field_val(thisfield)
|
||||
namefound = true
|
||||
end
|
||||
if thisfield.match(/^id=/) and not idfound
|
||||
formid = get_field_val(thisfield)
|
||||
idfound = true
|
||||
end
|
||||
if thisfield.match(/^method=/)
|
||||
formmethod = get_field_val(thisfield)
|
||||
end
|
||||
if thisfield.match(/^action=/) and not actionfound
|
||||
actionname = get_field_val(thisfield)
|
||||
if (actionname.length < datastore['URL'].length) and (datastore['URL'].downcase.index(actionname.downcase).to_i() > -1)
|
||||
actionname = datastore['URL']
|
||||
end
|
||||
actionfound = true
|
||||
end
|
||||
end
|
||||
if datastore['ACTION'].length > 0
|
||||
actionname = datastore['ACTION']
|
||||
actionfound = true
|
||||
end
|
||||
|
||||
if formname == "" and formid != ""
|
||||
formname = formid
|
||||
end
|
||||
if formid == "" and formname != ""
|
||||
formid = formname
|
||||
end
|
||||
if formid == "" and formname == ""
|
||||
formid = "noname_" + (formcnt+1).to_s()
|
||||
formname = formid
|
||||
end
|
||||
idfound = true
|
||||
namefound = true
|
||||
|
||||
formfields = []
|
||||
#input boxes
|
||||
fieldtypemarks = [ '<input', '<select' ]
|
||||
fieldtypemarks.each do | currfieldmark |
|
||||
formfieldcnt=0
|
||||
if (namefound or idfound) and actionfound
|
||||
# get fields in current form - data[0]
|
||||
subdata = data[0].downcase.split(currfieldmark)
|
||||
skipflag=0
|
||||
if subdata.size > 1
|
||||
subdata.each do | thisinput |
|
||||
if skipflag == 1
|
||||
#first, find the delimeter
|
||||
fielddata = thisinput.downcase.split(/>/)
|
||||
fields = fielddata[0].split(/ /)
|
||||
fieldname = ""
|
||||
fieldtype = ""
|
||||
fieldvalue = ""
|
||||
fieldmethod = "post"
|
||||
fieldid = ""
|
||||
fields.each do | thisfield |
|
||||
if thisfield.match(/^type=/)
|
||||
fieldtype = get_field_val(thisfield)
|
||||
end
|
||||
if currfieldmark == "<select" and thisfield.match(/^class=/)
|
||||
fieldtype = get_field_val(thisfield)
|
||||
end
|
||||
if thisfield.match(/^name=/)
|
||||
fieldname = get_field_val(thisfield)
|
||||
end
|
||||
if thisfield.match(/^id=/)
|
||||
fieldid = get_field_val(thisfield)
|
||||
end
|
||||
if thisfield.match(/^value=/)
|
||||
#special case
|
||||
location = fielddata[0].index(thisfield)
|
||||
delta = fielddata[0].size - location
|
||||
remaining = fielddata[0][location,delta]
|
||||
tmp = remaining.strip.split(/=/)
|
||||
if tmp.size > 1
|
||||
delim = tmp[1][0,1]
|
||||
tmp2 = tmp[1].split(delim)
|
||||
fieldvalue = tmp2[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
if fieldname == "" and fieldid != ""
|
||||
fieldname = fieldid
|
||||
end
|
||||
if fieldid == "" and fieldname != ""
|
||||
fieldid = fieldname
|
||||
end
|
||||
print_status(" Field : #{fieldname}, type #{fieldtype}")
|
||||
if fieldid != ""
|
||||
formfields << {
|
||||
:id => fieldid,
|
||||
:name => fieldname,
|
||||
:type => fieldtype,
|
||||
:value => fieldvalue
|
||||
}
|
||||
formfieldcnt += 1
|
||||
end
|
||||
else
|
||||
skipflag += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
print_status(" Nr of fields in form '#{formname}' : #{formfields.size}")
|
||||
# store in multidimensional array
|
||||
forms << {
|
||||
:name => formname,
|
||||
:id => formid,
|
||||
:action => actionname,
|
||||
:method => formmethod,
|
||||
:fields => formfields
|
||||
}
|
||||
formidx = formidx + 1
|
||||
formcnt += 1
|
||||
end
|
||||
if forms.size > 0
|
||||
print_status(" Forms : ")
|
||||
end
|
||||
forms.each do | thisform |
|
||||
print_status(" - Name : #{thisform[:name]}, ID : #{thisform[:id]}, Action : #{thisform[:action]}, Method : #{thisform[:method]}")
|
||||
end
|
||||
return forms
|
||||
end
|
||||
|
||||
def run
|
||||
init_fuzzdata()
|
||||
init_vars()
|
||||
|
||||
print_status("[+] Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']}")
|
||||
response = send_request_raw(
|
||||
{
|
||||
'uri' => datastore['URL'],
|
||||
'version' => '1.1',
|
||||
'method' => 'GET'
|
||||
}, datastore['TIMEOUT'])
|
||||
if response == nil
|
||||
print_error("[-] No response")
|
||||
return
|
||||
end
|
||||
print_status("[+] Code : #{response.code}")
|
||||
okcode = is_error_code(response.code)
|
||||
if not okcode
|
||||
print_error("[-] Server replied with error code. Check URL or set CODE to another value, and try again.")
|
||||
return
|
||||
end
|
||||
if response.body
|
||||
formfound = response.body.downcase.index("<form")
|
||||
if formfound
|
||||
formdata = get_form_data(response.body)
|
||||
#fuzz !
|
||||
#for each form that needs to be fuzzed
|
||||
formdata.each do | thisform |
|
||||
if thisform[:name].length > 0
|
||||
if ((datastore['FORM'].strip == "") || (datastore['FORM'].upcase.strip == thisform[:name].upcase.strip)) && (thisform[:fields].size > 0)
|
||||
print_status("[+] Fuzzing fields in form #{thisform[:name].upcase.strip}")
|
||||
#for each field in this form, fuzz one field at a time
|
||||
formfields = thisform[:fields]
|
||||
formfields.each do | thisfield |
|
||||
if thisfield[:name]
|
||||
if fuzz_this_field(thisfield[:name],thisfield[:type]) == 1
|
||||
print_status(" - Fuzzing field #{thisfield[:name]}")
|
||||
do_fuzz_field(thisform,thisfield[:name])
|
||||
init_fuzzdata()
|
||||
end
|
||||
end
|
||||
end
|
||||
print_status("[+] Done fuzzing fields in form #{thisform[:name].upcase.strip}")
|
||||
end
|
||||
#fuzz headers ?
|
||||
if datastore['FUZZHEADERS'] == true
|
||||
print_status("[+] Fuzzing header fields")
|
||||
do_fuzz_headers(thisform,response.headers)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
print_error("[-] No form found in response body")
|
||||
print_status(response.body)
|
||||
return
|
||||
end
|
||||
else
|
||||
print_error("[-] No response data")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue