Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/fuzzers/http/http_form_field.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::HttpClient78def initialize(info = {})9super(update_info(info,10'Name' => 'HTTP Form Field Fuzzer',11'Description' => %q{12This module will grab all fields from a form,13and launch a series of POST actions, fuzzing the contents14of the form fields. You can optionally fuzz headers too15(option is enabled by default)16},17'Author' => [18'corelanc0d3r',19'Paulino Calderon <calderon[at]websec.mx>' #Added cookie handling20],21'License' => MSF_LICENSE,22'References' =>23[24['URL','http://www.corelan.be:8800/index.php/2010/11/12/metasploit-module-http-form-field-fuzzer'],25]26))2728register_options(29[30OptString.new('URL', [ false, "The URL that contains the form", "/"]),31OptString.new('FORM', [ false, "The name of the form to use. Leave empty to fuzz all forms","" ] ),32OptString.new('FIELDS', [ false, "Name of the fields to fuzz. Leave empty to fuzz all fields","" ] ),33OptString.new('ACTION', [ false, "Form action full URI. Leave empty to autodetect","" ] ),34OptInt.new('STARTSIZE', [ true, "Fuzzing string startsize.",1000]),35OptInt.new('ENDSIZE', [ true, "Max Fuzzing string size.",40000]),36OptInt.new('STEPSIZE', [ true, "Increment fuzzing string each attempt.",1000]),37OptInt.new('TIMEOUT', [ true, "Number of seconds to wait for response on GET or POST",15]),38OptInt.new('DELAY', [ true, "Number of seconds to wait between 2 actions",0]),39OptInt.new('STOPAFTER', [ false, "Stop after x number of consecutive errors",2]),40OptBool.new('CYCLIC', [ true, "Use Cyclic pattern instead of A's (fuzzing payload).",true]),41OptBool.new('FUZZHEADERS', [ true, "Fuzz headers",true]),42OptString.new('HEADERFIELDS', [ false, "Name of the headerfields to fuzz. Leave empty to fuzz all fields","" ] ),43OptString.new('TYPES', [ true, "Field types to fuzz","text,password,inputtextbox"]),44OptString.new('CODE', [ true, "Response code(s) indicating OK", "200,301,302,303" ] ),45OptBool.new('HANDLECOOKIES', [ true, "Appends cookies with every request.",false])46])47end4849def init_vars50proto = "http://"51if datastore['SSL']52proto = "https://"53end5455@send_data = {56:uri => '',57:version => '1.1',58:method => 'POST',59:headers => {60'Content-Length' => 100,61'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',62'Accept-Language' => 'en-us,en;q=0.5',63'Accept-Encoding' => 'gzip,deflate',64'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',65'Keep-Alive' => '300',66'Connection' => 'keep-alive',67'Referer' => proto + datastore['RHOST'] + ":" + datastore['RPORT'].to_s,68'Content-Type' => 'application/x-www-form-urlencoded'69}70}71@get_data_headers = {72'Referer' => proto + datastore['RHOST'] + ":" + datastore['RPORT'].to_s,73}74end7576def init_fuzzdata77@fuzzsize = datastore['STARTSIZE']78@endsize = datastore['ENDSIZE']79set_fuzz_payload()80@nrerrors = 081end8283def incr_fuzzsize84@stepsize = datastore['STEPSIZE'].to_i85@fuzzsize = @fuzzsize + @stepsize86end8788def set_fuzz_payload89if datastore['CYCLIC']90@fuzzdata = Rex::Text.pattern_create(@fuzzsize)91else92@fuzzdata = "A" * @fuzzsize93end94end9596def is_error_code(code)97okcode = false98checkcodes = datastore['CODE'].split(",")99checkcodes.each do | testcode |100testcode = testcode.upcase.gsub(" ","")101if testcode == code.to_s().upcase.gsub(" ","")102okcode = true103end104end105return okcode106end107108def fuzz_this_field(fieldname,fieldtype)109fuzzcommands = datastore['FIELDS'].split(",")110fuzzme = 0111if fuzzcommands.size > 0112fuzzcommands.each do |thiscmd|113thiscmd = thiscmd.strip114if ((fieldname.upcase == thiscmd.upcase) || (thiscmd == "")) && (fuzzme == 0)115fuzzme = 1116end117end118else119fuzztypes = datastore['TYPES'].split(",")120fuzztypes.each do | thistype |121if (fieldtype.upcase.strip == thistype.upcase.strip)122fuzzme = 1123end124end125end126if fuzzme == 1127set_fuzz_payload()128end129return fuzzme130end131132def fuzz_this_headerfield(fieldname)133fuzzheaderfields = datastore['HEADERFIELDS'].split(",")134fuzzme = 0135if fuzzheaderfields.size > 0136fuzzheaderfields.each do |thisfield|137thisfield = thisfield.strip138if ((fieldname.upcase == thisfield.upcase) || (thisfield == "")) && (fuzzme == 0)139fuzzme = 1140end141end142else143fuzzme = 1144end145if fuzzme == 1146set_fuzz_payload()147end148return fuzzme149end150151def do_fuzz_headers(form,headers)152headercnt = 0153datastr = ""154form[:fields].each do | thisfield |155normaldata = "blah&"156if thisfield[:value]157if thisfield[:value] != ""158normaldata = thisfield[:value].strip + "&"159end160end161datastr << thisfield[:name].downcase.strip + "=" + normaldata162end163if datastr.length > 0164datastr=datastr[0,datastr.length-1] + "\r\n"165else166datastr = "\r\n"167end168# first, check the original header fields and add some others - just for fun169myheaders = @send_data[:headers]170mysendheaders = @send_data[:headers].dup171# get or post ?172mysendheaders[:method] = form[:method].upcase173myheaders.each do | thisheader |174if not headers[thisheader[0]]175# add header if needed176mysendheaders[thisheader[0]]= thisheader[1]177end178end179nrheaderstofuzz = mysendheaders.size180mysendheaders.each do | thisheader|181@fuzzheader = mysendheaders.dup182@nrerrors = 0183fuzzpacket = @send_data.dup184fuzzpacket[:method] = mysendheaders[:method]185headername = thisheader[0]186if fuzz_this_headerfield(headername.to_s().upcase) == 1187print_status(" - Fuzzing header '#{headername}' (#{headercnt+1}/#{nrheaderstofuzz})")188init_fuzzdata()189while @fuzzsize <= @endsize+1190@fuzzheader[headername] = @fuzzdata191fuzzpacket[:headers] = @fuzzheader192response = send_fuzz(fuzzpacket,datastr)193if not process_response(response,headername,"header")194@fuzzsize = @endsize+2195end196if datastore['DELAY'] > 0197print_status(" (Sleeping for #{datastore['DELAY']} seconds...)")198select(nil,nil,nil,datastore['DELAY'])199end200incr_fuzzsize()201end202else203print_status(" - Skipping header '#{headername}' (#{headercnt+1}/#{nrheaderstofuzz})")204end205headercnt += 1206end207end208209def do_fuzz_field(form,field)210fieldstofuzz = field.downcase.strip.split(",")211@nrerrors = 0212while @fuzzsize <= @endsize+1213allfields = form[:fields]214datastr = ""215normaldata = ""216allfields.each do | thisfield |217dofuzzthis = false218if thisfield[:name]219fieldstofuzz.each do | fuzzthis |220if fuzzthis221if (thisfield[:name].downcase.strip == fuzzthis.downcase.strip)222dofuzzthis = true223end224end225end226if thisfield[:value]227normaldata = thisfield[:value].strip228else229normaldata = ""230end231if (dofuzzthis)232datastr << thisfield[:name].downcase.strip + "=" + @fuzzdata + "&"233else234datastr << thisfield[:name].downcase.strip + "=" + normaldata + "&"235end236end237end238datastr=datastr[0,datastr.length-1]239@send_data[:uri] = form[:action]240@send_data[:uri] = "/#{form[:action]}" if @send_data[:uri][0,1] != '/'241242@send_data[:method] = form[:method].upcase243response = send_fuzz(@send_data,datastr)244if not process_response(response,field,"field")245return246end247if datastore['DELAY'] > 0248print_status(" (Sleeping for #{datastore['DELAY']} seconds...)")249select(nil,nil,nil,datastore['DELAY'])250end251end252end253254def process_response(response,field,type)255if response == nil256print_error(" No response - #{@nrerrors+1} / #{datastore['STOPAFTER']} - fuzzdata length : #{@fuzzsize}")257if @nrerrors+1 >= datastore['STOPAFTER']258print_status(" *!* No response : #{type} #{field} | fuzzdata length : #{@fuzzsize}")259return false260else261@nrerrors = @nrerrors + 1262end263else264okcode = is_error_code(response.code)265if okcode266@nrerrors = 0267incr_fuzzsize()268end269if not okcode and @nrerrors+1 >= datastore['STOPAFTER']270print_status(" *!* Error response code #{response.code} | #{type} #{field} | fuzzdata length #{@fuzzsize}")271return false272else273@nrerrors = @nrerrors + 1274end275end276return true277end278279def send_fuzz(postdata,data)280header = postdata[:headers]281response = send_request_raw({282'uri' => postdata[:uri],283'version' => postdata[:version],284'method' => postdata[:method],285'headers' => header,286'data' => data287}, datastore['TIMEOUT'])288return response289end290291def get_field_val(input)292tmp = input.split(/\=/)293# get delimiter294tmp2 = tmp[1].strip295delim = tmp2[0,1]296if delim != "'" && delim != '"'297delim = ""298end299tmp3 = tmp[1].split(/>/)300tmp4 = tmp3[0].gsub(delim,"")301return tmp4302end303304def get_form_data(body)305print_status("Enumerating form data")306body = body.gsub("\r","")307body = body.gsub("\n","")308bodydata = body.downcase.split(/<form/)309# we need part after <form310totalforms = bodydata.size - 1311print_status(" Number of forms : #{totalforms}")312formcnt = 0313formidx = 1314forms = []315while formcnt < totalforms316fdata = bodydata[formidx]317print_status(" - Enumerating form ##{formcnt+1}")318data = fdata.downcase.split(/<\/form>/)319# first, get action and name320formdata = data[0].downcase.split(/>/)321subdata = formdata[0].downcase.split(/ /)322namefound = false323actionfound = false324idfound = false325actionname = ""326formname = ""327formid = ""328formmethod = "post"329subdata.each do | thisfield |330if thisfield.match(/^name=/) and not namefound331formname = get_field_val(thisfield)332namefound = true333end334if thisfield.match(/^id=/) and not idfound335formid = get_field_val(thisfield)336idfound = true337end338if thisfield.match(/^method=/)339formmethod = get_field_val(thisfield)340end341if thisfield.match(/^action=/) and not actionfound342actionname = get_field_val(thisfield)343if (actionname.length < datastore['URL'].length) and (datastore['URL'].downcase.index(actionname.downcase).to_i() > -1)344actionname = datastore['URL']345end346actionfound = true347end348end349if datastore['ACTION'].length > 0350actionname = datastore['ACTION']351actionfound = true352end353354if formname == "" and formid != ""355formname = formid356end357if formid == "" and formname != ""358formid = formname359end360if formid == "" and formname == ""361formid = "noname_" + (formcnt+1).to_s()362formname = formid363end364idfound = true365namefound = true366367formfields = []368# input boxes369fieldtypemarks = [ '<input', '<select' ]370fieldtypemarks.each do | currfieldmark |371formfieldcnt=0372if (namefound or idfound) and actionfound373# get fields in current form - data[0]374subdata = data[0].downcase.split(currfieldmark)375skipflag=0376if subdata.size > 1377subdata.each do | thisinput |378if skipflag == 1379# first, find the delimiter380fielddata = thisinput.downcase.split(/>/)381fields = fielddata[0].split(/ /)382fieldname = ""383fieldtype = ""384fieldvalue = ""385fieldmethod = "post"386fieldid = ""387fields.each do | thisfield |388if thisfield.match(/^type=/)389fieldtype = get_field_val(thisfield)390end391if currfieldmark == "<select" and thisfield.match(/^class=/)392fieldtype = get_field_val(thisfield)393end394if thisfield.match(/^name=/)395fieldname = get_field_val(thisfield)396end397if thisfield.match(/^id=/)398fieldid = get_field_val(thisfield)399end400if thisfield.match(/^value=/)401# special case402location = fielddata[0].index(thisfield)403delta = fielddata[0].size - location404remaining = fielddata[0][location,delta]405tmp = remaining.strip.split(/\=/)406if tmp.size > 1407delim = tmp[1][0,1]408tmp2 = tmp[1].split(delim)409fieldvalue = tmp2[1]410end411end412end413if fieldname == "" and fieldid != ""414fieldname = fieldid415end416if fieldid == "" and fieldname != ""417fieldid = fieldname418end419print_status(" Field : #{fieldname}, type #{fieldtype}")420if fieldid != ""421formfields << {422:id => fieldid,423:name => fieldname,424:type => fieldtype,425:value => fieldvalue426}427formfieldcnt += 1428end429else430skipflag += 1431end432end433end434end435end436print_status(" Nr of fields in form '#{formname}' : #{formfields.size}")437# store in multidimensional array438forms << {439:name => formname,440:id => formid,441:action => actionname,442:method => formmethod,443:fields => formfields444}445formidx = formidx + 1446formcnt += 1447end448449if forms.size > 0450print_status(" Forms : ")451end452453forms.each do | thisform |454print_status(" - Name : #{thisform[:name]}, ID : #{thisform[:id]}, Action : #{thisform[:action]}, Method : #{thisform[:method]}")455end456457return forms458end459460def set_cookie(cookie)461@get_data_headers["Cookie"]=cookie462@send_data[:headers]["Cookie"]=cookie463end464465def run466init_fuzzdata()467init_vars()468469print_status("Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']}")470response = send_request_raw(471{472'uri' => normalize_uri(datastore['URL']),473'version' => '1.1',474'method' => 'GET',475'headers' => @get_data_headers476477}, datastore['TIMEOUT'])478if response == nil479print_error("No response")480return481end482483if datastore['HANDLECOOKIES']484cookie = response.get_cookies485set_cookie(cookie)486print_status("Set cookie: #{cookie}")487print_status("Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']} using cookies")488489response = send_request_raw(490{491'uri' => normalize_uri(datastore['URL']),492'version' => '1.1',493'method' => 'GET',494'headers' => @get_data_headers495}, datastore['TIMEOUT'])496end497if response == nil498print_error("No response")499return500end501print_status("Code : #{response.code}")502okcode = is_error_code(response.code)503if not okcode504print_error("Server replied with error code. Check URL or set CODE to another value, and try again.")505return506end507if response.body508formfound = response.body.downcase.index("<form")509if formfound510formdata = get_form_data(response.body)511# fuzz !512# for each form that needs to be fuzzed513formdata.each do | thisform |514if thisform[:name].length > 0515if ((datastore['FORM'].strip == "") || (datastore['FORM'].upcase.strip == thisform[:name].upcase.strip)) && (thisform[:fields].size > 0)516print_status("Fuzzing fields in form #{thisform[:name].upcase.strip}")517# for each field in this form, fuzz one field at a time518formfields = thisform[:fields]519formfields.each do | thisfield |520if thisfield[:name]521if fuzz_this_field(thisfield[:name],thisfield[:type]) == 1522print_status(" - Fuzzing field #{thisfield[:name]}")523do_fuzz_field(thisform,thisfield[:name])524init_fuzzdata()525end526end527end528print_status("Done fuzzing fields in form #{thisform[:name].upcase.strip}")529end530# fuzz headers ?531if datastore['FUZZHEADERS']532print_status("Fuzzing header fields")533do_fuzz_headers(thisform,response.headers)534end535end536end537538else539print_error("No form found in response body")540print_status(response.body)541return542end543else544print_error("No response data")545end546547end548end549550551