mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 13:11:28 +00:00
Added forms handling capability to http-sql-injection. Also, modified the output structure a bit and fixed some trailing whitespaces.
This commit is contained in:
@@ -8,38 +8,48 @@ local url = require "url"
|
||||
|
||||
description = [[
|
||||
Spiders an HTTP server looking for URLs containing queries vulnerable to an SQL
|
||||
injection attack.
|
||||
injection attack. It also extracts forms from found websites and tries to identify
|
||||
fields that are vulnerable.
|
||||
|
||||
The script spiders an HTTP server looking for URLs containing queries. It then
|
||||
proceeds to combine crafted SQL commands with susceptible URLs in order to
|
||||
obtain errors. The errors are analysed to see if the URL is vulnerable to
|
||||
attack. This uses the most basic form of SQL injection but anything more
|
||||
complicated is better suited to a standalone tool.
|
||||
complicated is better suited to a standalone tool.
|
||||
|
||||
We may not have access to the target web server's true hostname, which can prevent access to
|
||||
virtually hosted sites.
|
||||
]]
|
||||
|
||||
|
||||
author = "Eddie Bell"
|
||||
author = "Eddie Bell, Piotr Olma"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"intrusive", "vuln"}
|
||||
|
||||
---
|
||||
-- @args sql-injection.start The path at which to start spidering; default <code>/</code>.
|
||||
-- @args sql-injection.maxdepth The maximum depth to spider; default 10.
|
||||
-- @args sql-injection.maxpagecount the maximum amount of pages to visit.
|
||||
-- A negative value disables the limit (default: 20)
|
||||
-- @args sql-injection.url the url to start spidering. This is a URL
|
||||
-- relative to the scanned host eg. /default.html (default: /)
|
||||
-- @args sql-injection.withinhost only spider URLs within the same host.
|
||||
-- (default: true)
|
||||
-- @args sql-injection.withindomain only spider URLs within the same
|
||||
-- domain. This widens the scope from <code>withinhost</code> and can
|
||||
-- not be used in combination. (default: false)
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 80/tcp open http
|
||||
-- | sql-injection: Host might be vulnerable
|
||||
-- | /a_index.php?id_str=1'%20OR%20sqlspider
|
||||
-- | /a_index.php?id_str=1'%20OR%20sqlspider
|
||||
-- | /a_index.php?id_str=2'%20OR%20sqlspider
|
||||
-- 80/tcp open http syn-ack
|
||||
-- | http-sql-injection:
|
||||
-- | Possible sqli for queries:
|
||||
-- | http://foo.pl/forms/page.php?param=13'%20OR%20sqlspider
|
||||
-- | Possible sqli for forms:
|
||||
-- | Form at path: /forms/f1.html, form's action: a1/check1.php. Fields that might be vulnerable:
|
||||
-- | f1text
|
||||
-- | Form at path: /forms/a1/../f2.html, form's action: a1/check2.php. Fields that might be vulnerable:
|
||||
-- |_ f2text
|
||||
--
|
||||
|
||||
-- default settings
|
||||
local maxdepth = 10
|
||||
local start = '/'
|
||||
|
||||
portrule = shortport.port_or_service({80, 443}, {"http","https"})
|
||||
|
||||
@@ -69,26 +79,26 @@ local function build_injection_vector(urls)
|
||||
local utab, k, v, urlstr, response
|
||||
local qtab, old_qtab, results
|
||||
local all = {}
|
||||
|
||||
|
||||
for _, injectable in ipairs(urls) do
|
||||
if type(injectable) == "string" then
|
||||
utab = url.parse(injectable)
|
||||
qtab = url.parse_query(utab.query)
|
||||
|
||||
|
||||
for k, v in pairs(qtab) do
|
||||
old_qtab = qtab[k];
|
||||
qtab[k] = qtab[k] .. "'%20OR%20sqlspider"
|
||||
|
||||
|
||||
utab.query = url.build_query(qtab)
|
||||
urlstr = url.build(utab)
|
||||
table.insert(all, urlstr)
|
||||
|
||||
qtab[k] = old_qtab
|
||||
utab.query = url.build_query(qtab)
|
||||
|
||||
qtab[k] = old_qtab
|
||||
utab.query = url.build_query(qtab)
|
||||
end
|
||||
end
|
||||
end
|
||||
return all
|
||||
return all
|
||||
end
|
||||
|
||||
--[[
|
||||
@@ -103,7 +113,7 @@ local function inject(host, port, injectable)
|
||||
end
|
||||
|
||||
--[[
|
||||
Checks is received responses matches with usual sql error messages,
|
||||
Checks if received responses matches with usual sql error messages,
|
||||
what potentially means that the host is vulnerable to sql injection.
|
||||
]]--
|
||||
local function check_responses(queries, responses)
|
||||
@@ -116,20 +126,78 @@ local function check_responses(queries, responses)
|
||||
return results
|
||||
end
|
||||
|
||||
action = function(host, port)
|
||||
-- check for script arguments
|
||||
if stdnse.get_script_args('sql-injection.start') then
|
||||
start = stdnse.get_script_args('sql-injection.start')
|
||||
end
|
||||
-- checks if a field is of type we want to check for sqli
|
||||
local function sqli_field(field_type)
|
||||
return field_type=="text" or field_type=="radio" or field_type=="checkbox" or field_type=="textarea"
|
||||
end
|
||||
|
||||
if stdnse.get_script_args('sql-injection.maxdepth') then
|
||||
maxdepth = tonumber(stdnse.get_script_args('sql-injection.maxdepth'))
|
||||
stdnse.print_debug("maxdepth set to: " .. maxdepth)
|
||||
-- generates postdata with value of "sampleString" for every field (that satisfies sqli_field()) of a form
|
||||
local function generate_safe_postdata(form)
|
||||
local postdata = {}
|
||||
for _,field in ipairs(form["fields"]) do
|
||||
if sqli_field(field["type"]) then
|
||||
postdata[field["name"]] = "sampleString"
|
||||
end
|
||||
end
|
||||
return postdata
|
||||
end
|
||||
|
||||
local function generate_get_string(data)
|
||||
local get_str = {"?"}
|
||||
for name,value in pairs(data) do
|
||||
get_str[#get_str+1]=url.escape(name).."="..url.escape(value).."&"
|
||||
end
|
||||
return table.concat(get_str)
|
||||
end
|
||||
|
||||
-- checks each field of a form to see if it's vulnerable to sqli
|
||||
local function check_form(form, host, port, path)
|
||||
local vulnerable_fields = {}
|
||||
local postdata = generate_safe_postdata(form)
|
||||
local sending_function, response
|
||||
|
||||
local action_absolute = string.find(form["action"], "^https?://")
|
||||
-- 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..generate_get_string(data), nil) end
|
||||
end
|
||||
|
||||
for _,field in ipairs(form["fields"]) do
|
||||
if sqli_field(field["type"]) then
|
||||
stdnse.print_debug(2, "%s: checking field %s", SCRIPT_NAME, field["name"])
|
||||
postdata[field["name"]] = "' OR sqlspider"
|
||||
response = sending_function(postdata)
|
||||
if response and response.body and response.status==200 then
|
||||
if check_injection_response(response) then
|
||||
vulnerable_fields[#vulnerable_fields+1] = field["name"]
|
||||
end
|
||||
end
|
||||
postdata[field["name"]] = "sampleString"
|
||||
end
|
||||
end
|
||||
return vulnerable_fields
|
||||
end
|
||||
|
||||
|
||||
action = function(host, port)
|
||||
|
||||
-- crawl to find injectable urls
|
||||
local crawler = httpspider.Crawler:new(host, port, start, {scriptname = SCRIPT_NAME, maxpagecount = maxdepth})
|
||||
local crawler = httpspider.Crawler:new(host, port, nil, {scriptname = SCRIPT_NAME})
|
||||
local injectable = {}
|
||||
local results_forms = {name="Possible sqli for forms:"}
|
||||
|
||||
while(true) do
|
||||
local status, r = crawler:crawl()
|
||||
@@ -141,6 +209,22 @@ action = function(host, port)
|
||||
end
|
||||
end
|
||||
|
||||
-- first we try sqli on forms
|
||||
if r.response and r.response.body and r.response.status==200 then
|
||||
local all_forms = http.grab_forms(r.response.body)
|
||||
for _,form_plain in ipairs(all_forms) do
|
||||
local form = http.parse_form(form_plain)
|
||||
local path = r.url.path
|
||||
if form then
|
||||
local vulnerable_fields = check_form(form, host, port, path)
|
||||
if #vulnerable_fields > 0 then
|
||||
vulnerable_fields["name"] = "Form at path: "..path..", form's action: "..form["action"]..". Fields that might be vulnerable:"
|
||||
table.insert(results_forms, vulnerable_fields)
|
||||
end
|
||||
end
|
||||
end --for
|
||||
end --if
|
||||
|
||||
local links = httpspider.LinkExtractor:new(r.url, r.response.body, crawler.options):getLinks()
|
||||
for _,u in ipairs(links) do
|
||||
if url.parse(u).query then
|
||||
@@ -150,17 +234,16 @@ action = function(host, port)
|
||||
end
|
||||
|
||||
-- try to inject
|
||||
local results = {}
|
||||
local results_queries = {}
|
||||
if #injectable > 0 then
|
||||
stdnse.print_debug(1, "%s: Testing %d suspicious URLs", SCRIPT_NAME, #injectable)
|
||||
local injectableQs = build_injection_vector(injectable)
|
||||
local responses = inject(host, port, injectableQs)
|
||||
results = check_responses(injectableQs, responses)
|
||||
results_queries = check_responses(injectableQs, responses)
|
||||
end
|
||||
|
||||
if #results > 0 then
|
||||
return "Host might be vulnerable\n" .. table.concat(results, '\n')
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
results_queries["name"] = "Possible sqli for queries:"
|
||||
local res = {results_queries, results_forms}
|
||||
return stdnse.format_output(true, res)
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user