1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-21 06:59:01 +00:00

Implements consistent local cache control and redirect handling for script http-default-accounts. Closes #551

This commit is contained in:
nnposter
2016-10-30 18:58:25 +00:00
parent 2be31d5f49
commit de2ed2eec6
2 changed files with 68 additions and 27 deletions

View File

@@ -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

View File

@@ -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