mirror of
https://github.com/nmap/nmap.git
synced 2025-12-20 22:49:01 +00:00
Implements consistent local cache control and redirect handling for script http-default-accounts. Closes #551
This commit is contained in:
@@ -23,6 +23,54 @@ local have_openssl, openssl = pcall(require, 'openssl')
|
|||||||
-- between valid HTTP/200 and a custom error page.
|
-- between valid HTTP/200 and a custom error page.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
-- Recursively copy a table.
|
||||||
|
-- Only recurs when a value is a table, other values are copied by assignment.
|
||||||
|
local function tcopy (t)
|
||||||
|
local tc = {};
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
tc[k] = tcopy(v);
|
||||||
|
else
|
||||||
|
tc[k] = v;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return tc;
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Requests given path using http.get() but disabling cache and redirects.
|
||||||
|
-- @param host The host to connect to
|
||||||
|
-- @param port The port to connect to
|
||||||
|
-- @param path The path to retrieve
|
||||||
|
-- @param options [optional] A table of HTTP request options
|
||||||
|
-- @return A response table (see library http.lua for description)
|
||||||
|
---
|
||||||
|
local function http_get_simple (host, port, path, options)
|
||||||
|
local opts = tcopy(options or {})
|
||||||
|
opts.bypass_cache = true
|
||||||
|
opts.no_cache = true
|
||||||
|
opts.redirect_ok = false
|
||||||
|
return http.get(host, port, path, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Requests given path using http.post() but disabling cache and redirects.
|
||||||
|
-- (The current implementation of http.post() does not use either; this is
|
||||||
|
-- a defensive wrapper to guard against future problems.)
|
||||||
|
-- @param host The host to connect to
|
||||||
|
-- @param port The port to connect to
|
||||||
|
-- @param path The path to retrieve
|
||||||
|
-- @param options [optional] A table of HTTP request options
|
||||||
|
-- @param postdata A string or a table of data to be posted
|
||||||
|
-- @return A response table (see library http.lua for description)
|
||||||
|
---
|
||||||
|
local function http_post_simple (host, port, path, options, postdata)
|
||||||
|
local opts = tcopy(options or {})
|
||||||
|
opts.no_cache = true
|
||||||
|
opts.redirect_ok = false
|
||||||
|
return http.post(host, port, path, opts, nil, postdata)
|
||||||
|
end
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Requests given path using basic authentication.
|
-- Requests given path using basic authentication.
|
||||||
-- @param host Host table
|
-- @param host Host table
|
||||||
@@ -35,7 +83,7 @@ local have_openssl, openssl = pcall(require, 'openssl')
|
|||||||
---
|
---
|
||||||
local function try_http_basic_login(host, port, path, user, pass, digest_auth)
|
local function try_http_basic_login(host, port, path, user, pass, digest_auth)
|
||||||
local credentials = {username = user, password = pass, digest = digest_auth}
|
local credentials = {username = user, password = pass, digest = digest_auth}
|
||||||
local req = http.get(host, port, path, {no_cache=true, auth=credentials, redirect_ok = false})
|
local req = http_get_simple(host, port, path, {auth=credentials})
|
||||||
return req.status
|
return req.status
|
||||||
and req.status ~= 401
|
and req.status ~= 401
|
||||||
and req.status ~= 403
|
and req.status ~= 403
|
||||||
@@ -54,12 +102,11 @@ end
|
|||||||
-- @return True if login in was successful
|
-- @return True if login in was successful
|
||||||
---
|
---
|
||||||
local function try_http_post_login(host, port, path, target, failstr, params, follow_redirects)
|
local function try_http_post_login(host, port, path, target, failstr, params, follow_redirects)
|
||||||
local req = http.post(host, port, url.absolute(path, target), {no_cache=true}, nil, params)
|
local req = http_post_simple(host, port, url.absolute(path, target), nil, params)
|
||||||
|
|
||||||
if not req.status then return false end
|
if not req.status then return false end
|
||||||
local status = tonumber(req.status) or 0
|
local status = tonumber(req.status) or 0
|
||||||
if follow_redirects and ( status > 300 and status < 400 ) then
|
if follow_redirects and ( status > 300 and status < 400 ) then
|
||||||
req = http.get(host, port, url.absolute(path, req.header.location), { no_cache = true, redirect_ok = false })
|
req = http_get_simple(host, port, url.absolute(path, req.header.location))
|
||||||
end
|
end
|
||||||
if req.status and req.status ~= 404 and not(http.response_contains(req, failstr)) then
|
if req.status and req.status ~= 404 and not(http.response_contains(req, failstr)) then
|
||||||
return true
|
return true
|
||||||
@@ -129,11 +176,8 @@ table.insert(fingerprints, {
|
|||||||
{username = "admin", password = "zabbix"}
|
{username = "admin", password = "zabbix"}
|
||||||
},
|
},
|
||||||
login_check = function (host, port, path, user, pass)
|
login_check = function (host, port, path, user, pass)
|
||||||
local req = http.post(host, port, url.absolute(path, "index.php"),
|
local req = http_post_simple(host, port, url.absolute(path, "index.php"), nil,
|
||||||
{no_cache=true, redirect_ok=false},
|
{request="", name=user, password=pass, enter="Sign in"})
|
||||||
nil,
|
|
||||||
{request="", name=user, password=pass, enter="Sign in"},
|
|
||||||
false)
|
|
||||||
return req.status == 302 and req.header["location"] == "dashboard.php"
|
return req.status == 302 and req.header["location"] == "dashboard.php"
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
@@ -159,7 +203,7 @@ table.insert(fingerprints, {
|
|||||||
},
|
},
|
||||||
login_check = function (host, port, path, user, pass)
|
login_check = function (host, port, path, user, pass)
|
||||||
-- harvest all hidden fields from the login form
|
-- harvest all hidden fields from the login form
|
||||||
local req1 = http.get(host, port, path, {no_cache=true, redirect_ok = false})
|
local req1 = http_get_simple(host, port, path)
|
||||||
if req1.status ~= 200 then return false end
|
if req1.status ~= 200 then return false end
|
||||||
local html = req1.body and req1.body:match('<form%s+action%s*=%s*"[^"]*/users/login".->(.-)</form>')
|
local html = req1.body and req1.body:match('<form%s+action%s*=%s*"[^"]*/users/login".->(.-)</form>')
|
||||||
if not html then return false end
|
if not html then return false end
|
||||||
@@ -170,7 +214,7 @@ table.insert(fingerprints, {
|
|||||||
-- add username and password to the form and submit it
|
-- add username and password to the form and submit it
|
||||||
form["data[User][username]"] = user
|
form["data[User][username]"] = user
|
||||||
form["data[User][password]"] = pass
|
form["data[User][password]"] = pass
|
||||||
local req2 = http.post(host, port, path, {no_cache=true, cookies=req1.cookies}, nil, form)
|
local req2 = http_post_simple(host, port, path, {cookies=req1.cookies}, form)
|
||||||
if req2.status ~= 302 then return false end
|
if req2.status ~= 302 then return false end
|
||||||
local loc = req2.header["location"]
|
local loc = req2.header["location"]
|
||||||
return loc and (loc:match("/admins$") or loc:match("/pols/index$"))
|
return loc and (loc:match("/admins$") or loc:match("/pols/index$"))
|
||||||
@@ -389,7 +433,7 @@ table.insert(fingerprints, {
|
|||||||
username = user,
|
username = user,
|
||||||
password = pass}
|
password = pass}
|
||||||
local lurl = url.absolute(path, "rest.fcgi/services/rest/login?" .. url.build_query(form))
|
local lurl = url.absolute(path, "rest.fcgi/services/rest/login?" .. url.build_query(form))
|
||||||
local req = http.get(host, port, lurl, {no_cache=true, redirect_ok=false})
|
local req = http_get_simple(host, port, lurl)
|
||||||
return req.status == 200
|
return req.status == 200
|
||||||
and req.body
|
and req.body
|
||||||
and req.body:find('[{,]%s*"status"%s*:%s*true%s*[,}]')
|
and req.body:find('[{,]%s*"status"%s*:%s*true%s*[,}]')
|
||||||
@@ -416,7 +460,7 @@ table.insert(fingerprints, {
|
|||||||
login_check = function (host, port, path, user, pass)
|
login_check = function (host, port, path, user, pass)
|
||||||
local login = ("J20K34NMMT89XPIJ34S login %s %s"):format(stdnse.tohex(user), stdnse.tohex(pass))
|
local login = ("J20K34NMMT89XPIJ34S login %s %s"):format(stdnse.tohex(user), stdnse.tohex(pass))
|
||||||
local lurl = url.absolute(path, "usmCgi.cgi/?" .. url.escape(login))
|
local lurl = url.absolute(path, "usmCgi.cgi/?" .. url.escape(login))
|
||||||
local req = http.get(host, port, lurl, {no_cache=true, redirect_ok=false})
|
local req = http_get_simple(host, port, lurl)
|
||||||
return req.status == 200
|
return req.status == 200
|
||||||
and req.body
|
and req.body
|
||||||
and req.body:find("^login 0 ")
|
and req.body:find("^login 0 ")
|
||||||
@@ -613,8 +657,8 @@ table.insert(fingerprints, {
|
|||||||
local lpath = req0.body and req0.body:match('location%.href="(/[^"]+/)mainFrame%.cgi"')
|
local lpath = req0.body and req0.body:match('location%.href="(/[^"]+/)mainFrame%.cgi"')
|
||||||
if not lpath then return false end
|
if not lpath then return false end
|
||||||
-- harvest the login form token
|
-- harvest the login form token
|
||||||
local req1 = http.get(host, port, url.absolute(lpath, "authForm.cgi"),
|
local req1 = http_get_simple(host, port, url.absolute(lpath, "authForm.cgi"),
|
||||||
{cookies="cookieOnOffChecker=on", no_cache=true, redirect_ok=false})
|
{cookies="cookieOnOffChecker=on"})
|
||||||
if req1.status ~= 200 then return false end
|
if req1.status ~= 200 then return false end
|
||||||
local token = req1.body and req1.body:match('<input%s+type%s*=%s*"hidden"%s+name%s*=%s*"wimToken"%s+value%s*=%s*"(.-)"')
|
local token = req1.body and req1.body:match('<input%s+type%s*=%s*"hidden"%s+name%s*=%s*"wimToken"%s+value%s*=%s*"(.-)"')
|
||||||
if not token then return false end
|
if not token then return false end
|
||||||
@@ -625,9 +669,8 @@ table.insert(fingerprints, {
|
|||||||
password_work = "",
|
password_work = "",
|
||||||
password = base64.enc(pass),
|
password = base64.enc(pass),
|
||||||
open = ""}
|
open = ""}
|
||||||
local req2 = http.post(host, port, url.absolute(lpath, "login.cgi"),
|
local req2 = http_post_simple(host, port, url.absolute(lpath, "login.cgi"),
|
||||||
{cookies=req1.cookies, no_cache=true, redirect_ok=false},
|
{cookies=req1.cookies}, form)
|
||||||
nil, form)
|
|
||||||
local loc = req2.header["location"] or ""
|
local loc = req2.header["location"] or ""
|
||||||
-- successful login is a 302-redirect that sets a session cookie with numerical value
|
-- successful login is a 302-redirect that sets a session cookie with numerical value
|
||||||
if not (req2.status == 302 and loc:find("/mainFrame%.cgi$")) then return false end
|
if not (req2.status == 302 and loc:find("/mainFrame%.cgi$")) then return false end
|
||||||
@@ -686,7 +729,7 @@ table.insert(fingerprints, {
|
|||||||
login_check = function (host, port, path, user, pass)
|
login_check = function (host, port, path, user, pass)
|
||||||
local lurl = url.absolute(path, "server_eps.html")
|
local lurl = url.absolute(path, "server_eps.html")
|
||||||
-- obtain login nonce
|
-- obtain login nonce
|
||||||
local req1 = http.get(host, port, lurl, {no_cache=true, redirect_ok=false})
|
local req1 = http_get_simple(host, port, lurl)
|
||||||
if req1.status ~= 403 then return false end
|
if req1.status ~= 403 then return false end
|
||||||
local nonce = nil
|
local nonce = nil
|
||||||
for _, ck in ipairs(req1.cookies or {}) do
|
for _, ck in ipairs(req1.cookies or {}) do
|
||||||
@@ -699,8 +742,7 @@ table.insert(fingerprints, {
|
|||||||
-- credential is the MD5 hash of the nonce and the password (in upper case)
|
-- credential is the MD5 hash of the nonce and the password (in upper case)
|
||||||
local creds = stdnse.tohex(openssl.md5(nonce .. ":" .. pass:upper()))
|
local creds = stdnse.tohex(openssl.md5(nonce .. ":" .. pass:upper()))
|
||||||
local cookies = ("SrvrNonce=%s; SrvrCreds=%s"):format(nonce, creds)
|
local cookies = ("SrvrNonce=%s; SrvrCreds=%s"):format(nonce, creds)
|
||||||
local req2 = http.get(host, port, lurl,
|
local req2 = http_get_simple(host, port, lurl, {cookies=cookies})
|
||||||
{cookies=cookies, no_cache=true, redirect_ok=false})
|
|
||||||
return req2.status == 200
|
return req2.status == 200
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
@@ -739,13 +781,10 @@ table.insert(fingerprints, {
|
|||||||
},
|
},
|
||||||
login_check = function (host, port, path, user, pass)
|
login_check = function (host, port, path, user, pass)
|
||||||
local creds = stdnse.tohex(openssl.md5(user .. "_" .. pass))
|
local creds = stdnse.tohex(openssl.md5(user .. "_" .. pass))
|
||||||
local content = "/api/login/" .. creds
|
|
||||||
local header = {["Content-Type"] = "application/x-www-form-urlencoded",
|
local header = {["Content-Type"] = "application/x-www-form-urlencoded",
|
||||||
["datatype"] = "json"}
|
["datatype"] = "json"}
|
||||||
local req = http.generic_request(host, port, "POST",
|
local req = http_post_simple(host, port, url.absolute(path, "../"),
|
||||||
url.absolute(path, "../"),
|
{header=header}, "/api/login/" .. creds)
|
||||||
{header=header, content=content,
|
|
||||||
no_cache=true, redirect_ok=false})
|
|
||||||
return req.status == 200
|
return req.status == 200
|
||||||
and (req.header["command-status"] or ""):find("^1 ")
|
and (req.header["command-status"] or ""):find("^1 ")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -259,7 +259,9 @@ action = function(host, port)
|
|||||||
-- within the pipeline.
|
-- within the pipeline.
|
||||||
local path = probe.path
|
local path = probe.path
|
||||||
if not pathmap[path] then
|
if not pathmap[path] then
|
||||||
requests = http.pipeline_add(basepath .. path, nil, requests, 'GET')
|
requests = http.pipeline_add(basepath .. path,
|
||||||
|
{bypass_cache=true, redirect_ok=false},
|
||||||
|
requests, 'GET')
|
||||||
pathmap[path] = #requests
|
pathmap[path] = #requests
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user