Lucene search
K

HTTP Form Field Fuzzer

🗓️ 12 Nov 2010 20:59:36Reported by corelanc0d3r, Paulino Calderon <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 33 Views

This module performs fuzzing on form fields and supports fuzzing headers too. It provides options to specify the URL, form name, fields to fuzz, form action, fuzzing string start size, max fuzzing string size, fuzzing string increment, response timeout, delay between actions, consecutive error limit, and more

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < 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',
        'Paulino Calderon <calderon[at]websec.mx>' #Added cookie handling
        ],
      'License'       => MSF_LICENSE,
      '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" ] ),
        OptBool.new('HANDLECOOKIES', [ true, "Appends cookies with every request.",false])
      ])
  end

  def init_vars
    proto = "http://"
    if datastore['SSL']
      proto = "https://"
    end

    @send_data = {
        :uri => '',
        :version => '1.1',
        :method => 'POST',
        :headers => {
          'Content-Length' => 100,
          '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'].to_s,
          'Content-Type' => 'application/x-www-form-urlencoded'
        }
      }
    @get_data_headers = {
        'Referer' => proto + datastore['RHOST'] + ":" + datastore['RPORT'].to_s,
      }
  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[:uri] = "/#{form[:action]}" if @send_data[:uri][0,1] != '/'

      @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 delimiter
    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 delimiter
                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 set_cookie(cookie)
    @get_data_headers["Cookie"]=cookie
    @send_data[:headers]["Cookie"]=cookie
  end

  def run
    init_fuzzdata()
    init_vars()

    print_status("Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']}")
    response = send_request_raw(
    {
      'uri' => normalize_uri(datastore['URL']),
      'version' => '1.1',
      'method' => 'GET',
      'headers' => @get_data_headers

    }, datastore['TIMEOUT'])
    if response == nil
      print_error("No response")
      return
    end

    if datastore['HANDLECOOKIES']
      cookie = response.get_cookies
      set_cookie(cookie)
      print_status("Set cookie: #{cookie}")
      print_status("Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']} using cookies")

      response = send_request_raw(
      {
        'uri' => normalize_uri(datastore['URL']),
        'version' => '1.1',
        'method' => 'GET',
        'headers' => @get_data_headers
      }, datastore['TIMEOUT'])
    end
    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']
              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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

07 Jan 2024 20:02Current
7.2High risk
Vulners AI Score7.2
33