1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-20 14:39:02 +00:00

Patch by nnposter that improves performance of http-default-accounts

http://seclists.org/nmap-dev/2013/q3/346

For any given fingerprint from http-default-accounts-fingerprints
script http-default-accounts currently tests corresponding default
credentials if at least one  of the probe URLs succeeded, namely
returned with status other than 404.

Some web servers, such as Linksys devices, respond with HTTP/401 even
for non-existent URLs. This causes the script to assume that these URLs
do exist and to test the credentials, while ideally they should be
tested only on those servers where they make sense.

The purpose of the attached patches is to reduce unnecessary credential
guessing by implementing a new optional fingerprint element, function
target_check(), which takes some already collected target information,
including a probe URL response, and returns true or false, indicating
whether the credential guessing should be attempted or not.

All of the current fingerprints have been retrofitted with simple
target validations as follows:

* If the fingerprint uses native HTTP authentication, validate that the
target's realm matches the server type.

* If the fingerprint uses form-based authentication, validate that the
probe URL returned with HTTP/200 (as opposed to perhaps HTTP/401).

When testing against the above-mentioned Linksys the difference was
notable: 14 login attempts before the patch versus 1 attempt after the
patch.

This functionality provides opportunity for further improvement by
being able to match page content to differentiate between real HTTP/200
and a custom error page. (As of now the script completely skips targets
that return HTTP/200 for non-existent pages.)
This commit is contained in:
sophron
2013-08-18 01:42:48 +00:00
parent 9f0db3819e
commit 9561155701
2 changed files with 77 additions and 2 deletions

View File

@@ -13,7 +13,11 @@ local url = require "url"
---- * <code>username</code> - Default username ---- * <code>username</code> - Default username
---- * <code>password</code> - Default password ---- * <code>password</code> - Default password
-- * <code>paths</code> - Paths table containing the possible location of the target -- * <code>paths</code> - Paths table containing the possible location of the target
-- * <code>target_check</code> - Validation function of the target (optional)
-- * <code>login_check</code> - Login function of the target -- * <code>login_check</code> - Login function of the target
--
-- TODO: Update the functionality of <code>target_check</code> to differentiate
-- between valid HTTP/200 and a custom error page.
--- ---
--- ---
@@ -59,6 +63,18 @@ local function try_http_post_login(host, port, path, target, failstr, params, fo
end end
return false return false
end end
---
-- Returns authentication realm advertised in an HTTP response
-- @param response HTTP response object, such as a result from http.get()
-- @return realm found in response header WWW-Authenticate
-- (or nil if not present)
---
local function http_auth_realm(response)
local auth = response.header["www-authenticate"] or ""
return auth:match('%srealm="([^"]*)')
end
fingerprints = {} fingerprints = {}
--- ---
@@ -70,6 +86,9 @@ table.insert(fingerprints, {
paths = { paths = {
{path = "/cacti/"} {path = "/cacti/"}
}, },
target_check = function (host, port, path, response)
return response.status == 200
end,
login_combos = { login_combos = {
{username = "admin", password = "admin"} {username = "admin", password = "admin"}
}, },
@@ -85,6 +104,9 @@ table.insert(fingerprints, {
{path = "/manager/html/"}, {path = "/manager/html/"},
{path = "/tomcat/manager/html/"} {path = "/tomcat/manager/html/"}
}, },
target_check = function (host, port, path, response)
return http_auth_realm(response) == "Tomcat Manager Application"
end,
login_combos = { login_combos = {
{username = "tomcat", password = "tomcat"}, {username = "tomcat", password = "tomcat"},
{username = "admin", password = "admin"}, {username = "admin", password = "admin"},
@@ -104,6 +126,9 @@ table.insert(fingerprints, {
paths = { paths = {
{path = "/axis2/axis2-admin/"} {path = "/axis2/axis2-admin/"}
}, },
target_check = function (host, port, path, response)
return response.status == 200
end,
login_combos = { login_combos = {
{username = "admin", password = "axis2"} {username = "admin", password = "axis2"}
}, },
@@ -120,6 +145,9 @@ table.insert(fingerprints, {
paths = { paths = {
{path = "/logo_t.gif"} {path = "/logo_t.gif"}
}, },
target_check = function (host, port, path, response)
return response.status == 200
end,
login_combos = { login_combos = {
{username = "", password = ""} {username = "", password = ""}
}, },
@@ -129,7 +157,7 @@ table.insert(fingerprints, {
}) })
table.insert(fingerprints, { table.insert(fingerprints, {
name = "Cisco 2811", name = "Cisco IOS",
category = "routers", category = "routers",
paths = { paths = {
{path = "/exec/show/log/CR"}, {path = "/exec/show/log/CR"},
@@ -137,6 +165,11 @@ table.insert(fingerprints, {
{path = "/level/15/exec/-"}, {path = "/level/15/exec/-"},
{path = "/level/15/"} {path = "/level/15/"}
}, },
target_check = function (host, port, path, response)
local realm = http_auth_realm(response) or ""
-- Exact PCRE: "^level 15?( or view)? access$"
return realm:gsub("_"," "):find("^level 15? .*access$")
end,
login_combos = { login_combos = {
{username = "", password = ""}, {username = "", password = ""},
{username = "cisco", password = "cisco"} {username = "cisco", password = "cisco"}
@@ -152,6 +185,9 @@ table.insert(fingerprints, {
paths = { paths = {
{path = "/StatusLan.htm"} {path = "/StatusLan.htm"}
}, },
target_check = function (host, port, path, response)
return http_auth_realm(response) == "Linksys WAP200"
end,
login_combos = { login_combos = {
{username = "admin", password = "admin"} {username = "admin", password = "admin"}
}, },
@@ -166,6 +202,9 @@ table.insert(fingerprints, {
paths = { paths = {
{path = "/WPA_Preshared.asp"} {path = "/WPA_Preshared.asp"}
}, },
target_check = function (host, port, path, response)
return http_auth_realm(response) == "Linksys WAP55AG"
end,
login_combos = { login_combos = {
{username = "", password = "admin"} {username = "", password = "admin"}
}, },
@@ -174,6 +213,23 @@ table.insert(fingerprints, {
end end
}) })
table.insert(fingerprints, {
name = "Nortel VPN Router",
category = "routers",
paths = {
{path = "/manage/bdy_sys.htm"}
},
target_check = function (host, port, path, response)
return http_auth_realm(response) == "Management(1)"
end,
login_combos = {
{username = "admin", password = "setup"}
},
login_check = function (host, port, path, user, pass)
return try_http_basic_login(host, port, path, user, pass, false)
end
})
--- ---
--Digital recorders --Digital recorders
--- ---
@@ -183,6 +239,9 @@ table.insert(fingerprints, {
paths = { paths = {
{path = "/frmpages/index.html"} {path = "/frmpages/index.html"}
}, },
target_check = function (host, port, path, response)
return http_auth_realm(response) == "WebPage Configuration"
end,
login_combos = { login_combos = {
{username = "dm", password = "web"} {username = "dm", password = "web"}
}, },

View File

@@ -28,6 +28,11 @@ Remember each fingerprint must have:
* <code>paths</code> - Paths table containing the possible location of the target * <code>paths</code> - Paths table containing the possible location of the target
* <code>login_check</code> - Login function of the target * <code>login_check</code> - Login function of the target
In addition, a fingerprint may have:
* <code>target_check</code> - Target validation function. If defined, it will be
called to validate the target before attempting
any logins.
Default fingerprint file: /nselib/data/http-default-accounts-fingerprints.lua Default fingerprint file: /nselib/data/http-default-accounts-fingerprints.lua
This script was based on http-enum. This script was based on http-enum.
]] ]]
@@ -48,6 +53,10 @@ This script was based on http-enum.
-- Other useful arguments relevant to this script: -- Other useful arguments relevant to this script:
-- http.pipeline Sets max number of petitions in the same request. -- http.pipeline Sets max number of petitions in the same request.
-- http.useragent User agent for HTTP requests -- http.useragent User agent for HTTP requests
--
-- Revision History
-- 2013-08-13 nnposter
-- * added support for target_check()
--- ---
author = "Paulino Calderon" author = "Paulino Calderon"
@@ -109,6 +118,10 @@ local function validate_fingerprints(fingerprints)
-- Make sure they include the login function -- Make sure they include the login function
if(type(fingerprint.login_check) ~= "function") then if(type(fingerprint.login_check) ~= "function") then
return "Missing or invalid login_check function in entry #"..i return "Missing or invalid login_check function in entry #"..i
end
-- Make sure that the target validation is a function
if(fingerprint.target_check and type(fingerprint.target_check) ~= "function") then
return "Invalid target_check function in entry #"..i
end end
-- Are they missing any fields? -- Are they missing any fields?
if(fingerprint.category and type(fingerprint.category) ~= "string") then if(fingerprint.category and type(fingerprint.category) ~= "string") then
@@ -269,7 +282,10 @@ action = function(host, port)
if (results[j] and not(credentials_found)) then if (results[j] and not(credentials_found)) then
local path = basepath .. probe['path'] local path = basepath .. probe['path']
if( http.page_exists(results[j], result_404, known_404, path, true) ) then if http.page_exists(results[j], result_404, known_404, path, true)
and (not fingerprint.target_check
or fingerprint.target_check(host, port, path, results[j]))
then
for _, login_combo in ipairs(fingerprint.login_combos) do for _, login_combo in ipairs(fingerprint.login_combos) do
stdnse.print_debug(2, "%s: Trying login combo -> %s:%s", SCRIPT_NAME, login_combo["username"], login_combo["password"]) stdnse.print_debug(2, "%s: Trying login combo -> %s:%s", SCRIPT_NAME, login_combo["username"], login_combo["password"])
--Check default credentials --Check default credentials