Lucene search

K
nmapPiotr Olma, Gioacchino MazzurcoNMAP:HTTP-FORM-FUZZER.NSE
HistoryJun 10, 2012 - 11:05 p.m.

http-form-fuzzer NSE Script

2012-06-1023:05:42
Piotr Olma, Gioacchino Mazzurco
nmap.org
236

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

0.973 High

EPSS

Percentile

99.8%

Performs a simple form fuzzing against forms found on websites. Tries strings and numbers of increasing length and attempts to determine if the fuzzing was successful.

Script Arguments

http-form-fuzzer.minlength

the minimum length of a string that will be used for fuzzing, defaults to 300000

http-form-fuzzer.maxlength

the maximum length of a string that will be used for fuzzing, defaults to 310000

http-form-fuzzer.targets

a table with the targets of fuzzing, for example {{path = /index.html, minlength = 40002}, {path = /foo.html, maxlength = 10000}}. The path parameter is required, if minlength or maxlength is not specified, then the values of http-form-fuzzer.minlength or http-form-fuzzer.maxlength will be used. Defaults to {{path=“/”}}

slaxml.debug

See the documentation for the slaxml library.

httpspider.doscraping, httpspider.maxdepth, httpspider.maxpagecount, httpspider.noblacklist, httpspider.url, httpspider.useheadfornonwebfiles, httpspider.withindomain, httpspider.withinhost

See the documentation for the httpspider library.

http.host, http.max-body-size, http.max-cache-size, http.max-pipeline, http.pipeline, http.truncated-ok, http.useragent

See the documentation for the http library.

smbdomain, smbhash, smbnoguest, smbpassword, smbtype, smbusername

See the documentation for the smbauth library.

Example Usage

nmap --script http-form-fuzzer --script-args 'http-form-fuzzer.targets={1={path=/},2={path=/register.html}}' -p 80 <host>

This script attempts to fuzz fields in forms it detects (it fuzzes one field at a time).
In each iteration it first tries to fuzz a field with a string, then with a number.
In the output, actions and paths for which errors were observed are listed, along with
names of fields that were being fuzzed during error occurrence. Length and type
(string/integer) of the input that caused the error are also provided.
We consider an error to be either: a response with status 500 or with an empty body,
a response that contains "server error" or "sql error" strings. ATM anything other than
that is considered not to be an 'error'.
TODO: develop more sophisticated techniques that will let us determine if the fuzzing was
successful (i.e. we got an 'error'). Ideally, an algorithm that will tell us a percentage
difference between responses should be implemented.

Script Output

PORT   STATE SERVICE REASON
80/tcp open  http    syn-ack
| http-form-fuzzer:
|   Path: /register.html Action: /validate.php
|     age
|       integer lengths that caused errors:
|         10000, 10001
|     name
|       string lengths that caused errors:
|         40000
|   Path: /form.html Action: /check_form.php
|     fieldfoo
|       integer lengths that caused errors:
|_        1, 2

Requires


description = [[
Performs a simple form fuzzing against forms found on websites.
Tries strings and numbers of increasing length and attempts to
determine if the fuzzing was successful.
]]

---
-- @usage
-- nmap --script http-form-fuzzer --script-args 'http-form-fuzzer.targets={1={path=/},2={path=/register.html}}' -p 80 <host>
--
-- This script attempts to fuzz fields in forms it detects (it fuzzes one field at a time).
-- In each iteration it first tries to fuzz a field with a string, then with a number.
-- In the output, actions and paths for which errors were observed are listed, along with
-- names of fields that were being fuzzed during error occurrence. Length and type
-- (string/integer) of the input that caused the error are also provided.
-- We consider an error to be either: a response with status 500 or with an empty body,
-- a response that contains "server error" or "sql error" strings. ATM anything other than
-- that is considered not to be an 'error'.
-- TODO: develop more sophisticated techniques that will let us determine if the fuzzing was
-- successful (i.e. we got an 'error'). Ideally, an algorithm that will tell us a percentage
-- difference between responses should be implemented.
--
-- @output
-- PORT   STATE SERVICE REASON
-- 80/tcp open  http    syn-ack
-- | http-form-fuzzer:
-- |   Path: /register.html Action: /validate.php
-- |     age
-- |       integer lengths that caused errors:
-- |         10000, 10001
-- |     name
-- |       string lengths that caused errors:
-- |         40000
-- |   Path: /form.html Action: /check_form.php
-- |     fieldfoo
-- |       integer lengths that caused errors:
-- |_        1, 2
--
-- @args http-form-fuzzer.targets a table with the targets of fuzzing, for example
-- {{path = /index.html, minlength = 40002}, {path = /foo.html, maxlength = 10000}}.
-- The path parameter is required, if minlength or maxlength is not specified,
-- then the values of http-form-fuzzer.minlength or http-form-fuzzer.maxlength will be used.
-- Defaults to {{path="/"}}
-- @args http-form-fuzzer.minlength the minimum length of a string that will be used for fuzzing,
-- defaults to 300000
-- @args http-form-fuzzer.maxlength the maximum length of a string that will be used for fuzzing,
-- defaults to 310000
--

author = {"Piotr Olma", "Gioacchino Mazzurco"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"fuzzer", "intrusive"}

local shortport = require 'shortport'
local http = require 'http'
local httpspider = require 'httpspider'
local stdnse = require 'stdnse'
local string = require 'string'
local table = require 'table'
local url = require 'url'
local rand = require 'rand'

-- check if the response we got indicates that fuzzing was successful
local function check_response(response)
  if not(response.body) or response.status==500 then
    return true
  end
  if response.body:find("[Ss][Ee][Rr][Vv][Ee][Rr]%s*[Ee][Rr][Rr][Oo][Rr]") or response.body:find("[Ss][Qq][Ll]%s*[Ee][Rr][Rr][Oo][Rr]") then
    return true
  end
  return false
end

-- check from response if request was too big
local function request_too_big(response)
  return response.status==413 or response.status==414
end

-- checks if a field is of type we want to fuzz
local function fuzzable(field_type)
  return field_type=="text" or field_type=="radio" or field_type=="checkbox" or field_type=="textarea"
end

-- generates postdata with value of "sampleString" for every field (that is fuzzable()) of a form
local function generate_safe_postdata(form)
  local postdata = {}
  for _,field in ipairs(form["fields"]) do
    if fuzzable(field["type"]) then
      postdata[field["name"]] = "sampleString"
    end
  end
  return postdata
end

-- generate a charset of characters with ascii codes from 33 to 126
-- you can use http://www.asciitable.com/ to see which characters those actually are
local charset = rand.charset(33,126)
local charset_number = rand.charset(49,57) -- ascii 49 -> 1; 57 -> 9

local function fuzz_form(form, minlen, maxlen, host, port, path)
  local affected_fields = {}
  local postdata = generate_safe_postdata(form)
  local action_absolute = httpspider.LinkExtractor.isAbsolute(form["action"])

  -- determine the path where the form needs to be submitted
  local form_submission_path
  if action_absolute then
    form_submission_path = form["action"]
  else
    local path_cropped = string.match(path, "(.*/).*")
    path_cropped = path_cropped and path_cropped or ""
    form_submission_path = path_cropped..form["action"]
  end

  -- determine should the form be sent by post or get
  local sending_function
  if form["method"]=="post" then
    sending_function = function(data) return http.post(host, port, form_submission_path, nil, nil, data) end
  else
    sending_function = function(data) return http.get(host, port, form_submission_path.."?"..url.build_query(data), {no_cache=true, bypass_cache=true}) end
  end

  local function fuzz_field(field)
    local affected_string = {}
    local affected_int = {}

    for i=minlen,maxlen do -- maybe a better idea would be to increment the string's length by more then 1 in each step
      local response_string
      local response_number

      --first try to fuzz with a string
      postdata[field["name"]] = rand.random_string(i, charset)
      response_string = sending_function(postdata)
      --then with a number
      postdata[field["name"]] = rand.random_string(i, charset_number)
      response_number = sending_function(postdata)

      if check_response(response_string) then
        affected_string[#affected_string+1]=i
      elseif request_too_big(response_string) then
        maxlen = i-1
        break
      end

      if check_response(response_number) then
        affected_int[#affected_int+1]=i
      elseif request_too_big(response_number) then
        maxlen = i-1
        break
      end
    end
    postdata[field["name"]] = "sampleString"
    return affected_string, affected_int
  end

  for _,field in ipairs(form["fields"]) do
    if fuzzable(field["type"]) then
      local affected_string, affected_int = fuzz_field(field, minlen, maxlen, postdata, sending_function)
      if #affected_string > 0 or #affected_int > 0 then
        local affected_next_index = #affected_fields+1
        affected_fields[affected_next_index] = {name = field["name"]}
        if #affected_string>0 then
          table.insert(affected_fields[affected_next_index], {name="string lengths that caused errors:", table.concat(affected_string, ", ")})
        end
        if #affected_int>0 then
          table.insert(affected_fields[affected_next_index], {name="integer lengths that caused errors:", table.concat(affected_int, ", ")})
        end
      end
    end
  end
  return affected_fields
end

portrule = shortport.http

function action(host, port)
  local targets = stdnse.get_script_args('http-form-fuzzer.targets') or {{path="/"}}
  local return_table = {}
  local minlen = stdnse.get_script_args("http-form-fuzzer.minlength") or 300000
  local maxlen = stdnse.get_script_args("http-form-fuzzer.maxlength") or 310000

  for _,target in pairs(targets) do
    stdnse.debug2("testing path: "..target["path"])
    local path = target["path"]
    if path then
      local response = http.get( host, port, path )
      local all_forms = http.grab_forms(response.body)
      minlen = target["minlength"] or minlen
      maxlen = target["maxlength"] or maxlen
      for _,form_plain in ipairs(all_forms) do
        local form = http.parse_form(form_plain)
        if form and form.action then
          local affected_fields = fuzz_form(form, minlen, maxlen, host, port, path)
          if #affected_fields > 0 then
            affected_fields["name"] = "Path: "..path.." Action: "..form["action"]
            table.insert(return_table, affected_fields)
          end
        end
      end
    end
  end
  return stdnse.format_output(true, return_table)
end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

0.973 High

EPSS

Percentile

99.8%

Related for NMAP:HTTP-FORM-FUZZER.NSE