diff options
Diffstat (limited to 'nselib/data/http-default-accounts-fingerprints.lua')
-rw-r--r-- | nselib/data/http-default-accounts-fingerprints.lua | 1738 |
1 files changed, 1738 insertions, 0 deletions
diff --git a/nselib/data/http-default-accounts-fingerprints.lua b/nselib/data/http-default-accounts-fingerprints.lua new file mode 100644 index 0000000..c5a5030 --- /dev/null +++ b/nselib/data/http-default-accounts-fingerprints.lua @@ -0,0 +1,1738 @@ +local base64 = require "base64" +local http = require "http" +local json = require "json" +local math = require "math" +local shortport = require "shortport" +local stdnse = require "stdnse" +local table = require "table" +local tableaux = require "tableaux" +local url = require "url" +local have_openssl, openssl = pcall(require, 'openssl') + +--- +-- http-default-accounts-fingerprints.lua +-- This file contains fingerprint data for http-default-accounts.nse +-- +-- STRUCTURE: +-- * <code>name</code> - Descriptive name +-- * <code>cpe</code> - Official CPE Dictionary entry (optional) +-- * <code>category</code> - Category +-- * <code>login_combos</code> - Table of default credential pairs +---- * <code>username</code> +---- * <code>password</code> +-- * <code>paths</code> - Table of likely locations (paths) of the target +-- * <code>target_check</code> - Validation function of the target (optional) +-- * <code>login_check</code> - Login function of the target +--- + +--- +-- 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 = tableaux.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 = tableaux.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. +-- @param host Host table +-- @param port Port table +-- @param path Path to request +-- @param user Username for Basic Auth +-- @param pass Password for Basic Auth +-- @param digest_auth Digest Authentication +-- @return True if login in was successful +--- +local function try_http_basic_login(host, port, path, user, pass, digest_auth) + local credentials = {username = user, password = pass, digest = digest_auth} + local resp = http_get_simple(host, port, path, {auth=credentials}) + return resp.status + and resp.status ~= 400 + and resp.status ~= 401 + and resp.status ~= 403 + and resp.status ~= 404 +end + +--- +-- Tries to login with a http post, if the FAIL string is not found +-- we assume login in was successful +-- @param host Host table +-- @param port Port table +-- @param target Target file +-- @param failstr String shown when login in fails +-- @param params Post parameters +-- @param follow_redirects True if you want redirects to be followed +-- @return True if login in was successful +--- +local function try_http_post_login(host, port, path, target, failstr, params, follow_redirects) + local resp = http_post_simple(host, port, url.absolute(path, target), nil, params) + if not resp.status then return false end + local status = tonumber(resp.status) or 0 + if follow_redirects and ( status > 300 and status < 400 ) then + resp = http_get_simple(host, port, url.absolute(path, resp.header.location)) + end + if resp.status and resp.status ~= 404 and not(http.response_contains(resp, failstr)) then + return true + end + return false +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%s*=%s*"([^"]*)') +end + +--- +-- Tests whether an HTTP response sets a named cookie with a given value +-- @param response a standard HTTP response object +-- @param name a case-insensitive cookie name that must be set +-- @param pattern to validate the cookie value +-- @return cookie value if such a cookie is found +--- +local function sets_cookie(response, name, pattern) + name = name:lower() + for _, ck in ipairs(response.cookies or {}) do + if ck.name:lower() == name then + return (not pattern or ck.value:find(pattern)) and ck.value + end + end + return false +end + +--- +-- Generates default scheme, host, and port components for a parsed URL. +-- +-- This filter function generates the scheme, host, and port components from +-- the standard <code>host</code> and <code>port</code> script objects. These +-- components can then be passed onto function <code>url.build</code>. +-- +-- As an example, the following code generates a URL for path "/test/" +-- on the current host and port: +-- <code> +-- local testurl = url.build(url_build_defaults(host, port, {path = "/test/"})) +-- </code> +-- or, alternatively, when not used as a filter: +-- <code> +-- local parsed = url_build_defaults(host, port) +-- parsed.path = "/test/" +-- local testurl = url.build(parsed) +-- </code> +-- +-- @param host The host the URL is intended for. +-- @param port The port the URL is intended for. +-- @param parsed Parsed URL, as typically returned by <code>url.parse</code>, +-- or nil. The table can be be missing the scheme, host, and port components. +-- @return A clone of the parsed URL, with any missing scheme, host, and port +-- components added. +-- @see url.parse +-- @see url.build +--- +local function url_build_defaults (host, port, parsed) + local parts = tableaux.tcopy(parsed or {}) + parts.host = parts.host or stdnse.get_hostname(host, port) + parts.scheme = parts.scheme or shortport.ssl(host, port) and "https" or "http" + if not parts.port and port.number ~= url.get_default_port(parts.scheme) then + parts.port = port.number + end + return parts +end + +--- +-- Encodes a string to make it safe for embedding into XML/HTML. +-- +-- @param s The string to be encoded. +-- @return A string with unsafe characters encoded +--- +local function xmlencode (s) + return s:gsub("%W", function (c) return ("&#x%x;"):format(c:byte()) end) +end + +fingerprints = {} + +--- +--WEB +--- +table.insert(fingerprints, { + -- Version 0.8.8a + name = "Cacti", + cpe = "cpe:/a:cacti:cacti", + category = "web", + paths = { + {path = "/"}, + {path = "/cacti/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (sets_cookie(response, "Cacti") or sets_cookie(response, "CactiEZ")) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "index.php", + "%sname%s*=%s*(['\"]?)login_password%1[%s>]", + {action="login", login_username=user, login_password=pass}) + end +}) + +table.insert(fingerprints, { + -- Version 2.0.6 + name = "Zabbix", + cpe = "cpe:/a:zabbix:zabbix", + category = "web", + paths = { + {path = "/zabbix/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 and sets_cookie(response, "zbx_sessionid") + end, + login_combos = { + {username = "admin", password = "zabbix"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "index.php"), nil, + {request="", name=user, password=pass, enter="Sign in"}) + return resp.status == 302 and resp.header["location"] == "dashboard.php" + end +}) + +table.insert(fingerprints, { + -- Version 0.7, 1.0.1 + name = "Xplico", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 and sets_cookie(response, "Xplico") + end, + login_combos = { + {username = "admin", password = "xplico"}, + {username = "xplico", password = "xplico"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "users/login") + -- harvest all hidden fields from the login form + local resp1 = http_get_simple(host, port, lurl) + if resp1.status ~= 200 then return false end + local html = (resp1.body or ""):match('<form%s+action%s*=%s*"[^"]*/users/login".->(.-)</form>') + if not html then return false end + local form = {} + for n, v in html:gmatch('<input%s+type%s*=%s*"hidden"%s+name%s*=%s*"(.-)"%s+value%s*=%s*"(.-)"') do + form[n] = v + end + -- add username and password to the form and submit it + form["data[User][username]"] = user + form["data[User][password]"] = pass + local resp2 = http_post_simple(host, port, lurl, {cookies=resp1.cookies}, form) + local loc = resp2.header["location"] or "" + return resp2.status == 302 + and (loc:find("/admins$") or loc:find("/pols/index$")) + end +}) + +table.insert(fingerprints, { + --Version 5.3.1.1944 on EH6000 + name = "ExtraHop Web UI", + category = "web", + paths = { + {path = "/extrahop/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("csrfmiddlewaretoken", 1, true) + and response.body:lower():find("<title>extrahop login", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + -- obtain cookies and a CSRF token + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local tname, tvalue = resp1.body:match("<input%s+type%s*=%s*'hidden'%s+name%s*=%s*'(csrfmiddlewaretoken)'%s+value%s*=%s*'(.-)'") + if not tname then return false end + local form = {[tname]=tvalue, + next=path, + username=user, + password=pass} + -- Referer header is mandatory + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=path}))} + local resp2 = http_post_simple(host, port, path, + {cookies=resp1.cookies, header=header}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/extrahop/$") + end +}) + +table.insert(fingerprints, { + -- Version 3.2.3, 3.4.4, 4.0.8 + name = "Nagios", + cpe = "cpe:/a:nagios:nagios", + category = "web", + paths = { + {path = "/"}, + {path = "/nagios/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Nagios Access" + end, + login_combos = { + {username = "nagiosadmin", password = "nagios"}, + {username = "nagiosadmin", password = "nagiosadmin"}, + -- IBM PurePower Integrated Manager + {username = "nagiosadmin", password = "PASSW0RD"}, + -- CactiEZ + {username = "nagiosadmin", password = "CactiEZ"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 3.1.1, 4.0.2, 4.1.2 + name = "Grafana", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 and sets_cookie(response, "grafana_sess") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local header = {["Accept"] = "application/json, text/plain, */*", + ["Content-Type"] = "application/json;charset=utf-8"} + local jin = {user=user, email="", password=pass} + json.make_object(jin) + local resp = http_post_simple(host, port, url.absolute(path, "login"), + {header=header}, json.generate(jin)) + return resp.status == 200 and sets_cookie(resp, "grafana_user") == user + end +}) + +table.insert(fingerprints, { + -- Version 8.1, 9.2, 10.3.4, 10.3.6, 12.1.2 + name = "WebLogic Server Console", + cpe = "cpe:/a:bea:weblogic_server", + category = "web", + paths = { + {path = "/console/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/console/login/LoginForm%.jsp%f[;\0]") + end, + login_combos = { + -- WebLogic 9.x + {username = "weblogic", password = "weblogic"}, + -- WebLogic 10.x, 12.x + {username = "weblogic", password = "weblogic1"}, + {username = "weblogic", password = "welcome1"}, + -- Adobe LiveCycle ES + {username = "weblogic", password = "password"}, + -- PeopleSoft + {username = "system", password = "Passw0rd"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "j_security_check"), nil, + {j_username=user,j_password=pass,j_character_encoding="UTF-8"}) + -- WebLogic 8.x, 9.x + if resp.status == 403 then return false end + -- WebLogic 10.x, 12.x + if resp.status == 302 + and (resp.header["location"] or ""):find("/console/login/LoginForm%.jsp$") then + return false + end + return true + end +}) + +table.insert(fingerprints, { + name = "Apache Tomcat", + cpe = "cpe:/a:apache:tomcat", + category = "web", + paths = { + {path = "/manager/html/"}, + {path = "/manager/status/"}, + {path = "/manager/text/"}, + {path = "/tomcat/manager/html/"}, + {path = "/tomcat/manager/status/"}, + {path = "/tomcat/manager/text/"}, + {path = "/cognos_express/manager/html/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Tomcat Manager Application" + end, + login_combos = { + {username = "tomcat", password = "tomcat"}, + {username = "admin", password = "admin"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2009-3548 + {username = "admin", password = ""}, + -- https://github.com/seshendra/vagrant-ubuntu-tomcat7/ + {username = "admin", password = "tomcat"}, + -- https://github.com/apache/tomcat/blob/2b8f9665dbfb89c78878784cd9b63d2b976ba623/webapps/manager/WEB-INF/jsp/403.jsp#L66 + {username = "tomcat", password = "s3cret"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2010-4094 + {username = "ADMIN", password = "ADMIN"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2009-4189 + {username = "ovwebusr", password = "OvW*busr1"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2009-4188 + {username = "j2deployer", password = "j2deployer"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2010-0557 + {username = "cxsdk", password = "kdsxc"}, + -- XAMPP https://www.apachefriends.org/index.html + {username = "xampp", password = "xampp"}, + -- QLogic QConvergeConsole http://www.qlogic.com/ + {username = "QCC", password = "QLogic66"}, + -- HAPI FHIR http://hapifhir.io/ + {username = "fhir", password = "FHIRDefaultPassword"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Apache Tomcat Host Manager", + cpe = "cpe:/a:apache:tomcat", + category = "web", + paths = { + {path = "/host-manager/html/"}, + {path = "/host-manager/text/"}, + {path = "/tomcat/host-manager/html/"}, + {path = "/tomcat/host-manager/text/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Tomcat Host Manager Application" + end, + login_combos = { + {username = "tomcat", password = "tomcat"}, + {username = "admin", password = "admin"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2009-3548 + {username = "admin", password = ""}, + -- https://github.com/seshendra/vagrant-ubuntu-tomcat7/ + {username = "admin", password = "tomcat"}, + -- https://github.com/apache/tomcat/blob/2b8f9665dbfb89c78878784cd9b63d2b976ba623/webapps/manager/WEB-INF/jsp/403.jsp#L66 + {username = "tomcat", password = "s3cret"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2010-4094 + {username = "ADMIN", password = "ADMIN"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2009-4189 + {username = "ovwebusr", password = "OvW*busr1"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2009-4188 + {username = "j2deployer", password = "j2deployer"}, + -- https://cve.mitre.org/cgi-bin/cvename.cgi?name=2010-0557 + {username = "cxsdk", password = "kdsxc"}, + -- XAMPP https://www.apachefriends.org/index.html + {username = "xampp", password = "xampp"}, + -- QLogic QConvergeConsole http://www.qlogic.com/ + {username = "QCC", password = "QLogic66"}, + -- HAPI FHIR http://hapifhir.io/ + {username = "fhir", password = "FHIRDefaultPassword"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Apache Felix OSGi Management Console", + category = "web", + paths = { + {path = "/system/console"}, + {path = "/lc/system/console"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "OSGi Management Console" + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "karaf", password = "karaf"}, + {username = "smx", password = "smx"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 1.4.1, 1.5.2, 1.5.3, 1.6.0, 1.6.1 + name = "Apache Axis2", + cpe = "cpe:/a:apache:axis2", + category = "web", + paths = { + {path = "/axis2/axis2-admin/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:lower():find("<title>login to axis2 :: administration page</title>", 1, true) + end, + login_combos = { + {username = "admin", password = "axis2"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "login"), nil, + {userName=user,password=pass,submit=" Login "}) + return resp.status == 200 + and (resp.body or ""):lower():find("<a%s+href%s*=%s*(['\"])axis2%-admin/logout%1") + end +}) + +table.insert(fingerprints, { + name = "Plumtree Portal", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/portal/server%.pt$") + end, + login_combos = { + {username = "Administrator", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {in_hi_space="Login", + in_hi_spaceID="0", + in_hi_control="Login", + in_hi_dologin="true", + in_tx_username=user, + in_pw_userpass=pass, + in_se_authsource=""} + local resp = http_post_simple(host, port, + url.absolute(path, "portal/server.pt"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/portal/server%.pt[;?]") + and sets_cookie(resp, "plloginoccured") == "true" + end +}) + +table.insert(fingerprints, { + -- Version 0.4.4.6.1 on SamuraiWTF 2.6, 0.4.7.0 on Kali 2016.2 + name = "BeEF", + category = "web", + paths = { + {path = "/ui/authentication/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("BeEF", 1, true) + and response.body:lower():find("<title>beef authentication</title>", 1, true) + end, + login_combos = { + {username = "beef", password = "beef"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "login"), nil, + {["username-cfrm"]=user, ["password-cfrm"]=pass}) + return resp.status == 200 + and (resp.body or ""):find("{%s*success%s*:%s*true%s*}") + end +}) + +table.insert(fingerprints, { + name = "Pure Storage", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body:find("<title>Pure Storage Login</title>", 1, true) + end, + login_combos = { + {username = "pureuser", password = "pureuser"} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "login", + "{%s*Welcome%s*:%s*Invalid%s*}", + {["username"]=user, ["password"]=pass}) + end +}) + +table.insert(fingerprints, { + name = "Active MQ", + category = "web", + paths = { + {path = "/admin"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "ActiveMQRealm" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) +--- +--ROUTERS +--- +table.insert(fingerprints, { + name = "Arris 2307", + category = "routers", + paths = { + {path = "/logo_t.gif"} + }, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "login.cgi", "Login Error !!", {action="submit", page="", logout="", pws=pass}) + end +}) + +table.insert(fingerprints, { + -- Version 12.2SE on Catalyst 3750, 3845, CBS3020, 12.3 on Aironet 1300 + name = "Cisco IOS", + cpe = "cpe:/o:cisco:ios", + category = "routers", + paths = { + {path = "/"}, + -- TODO: Remove these paths completely unless a bug gets filed (9/1/2016) + -- (The paths are likely redundant. "/" should be covering all the cases.) + -- {path = "/exec/show/log/CR"}, + -- {path = "/level/15/exec/-/configure/http"}, + -- {path = "/level/15/exec/-"}, + -- {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?%f[ ].* access$") + end, + login_combos = { + {username = "", password = ""}, + {username = "cisco", password = "cisco"}, + {username = "Cisco", password = "Cisco"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version (see below) + name = "Cisco Linksys", + cpe = "cpe:/h:linksys:*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + -- realm="Linksys WAP200", "Linksys WAP55AG", "Linksys E2000" + return (http_auth_realm(response) or ""):find("^Linksys %u[%u%d]+$") + end, + login_combos = { + -- WAP55AG, version 1.07.01 + -- E2000, version 1.0.03 (any username is valid) + {username = "", password = "admin"}, + -- WAP200, version 1.0.22 + {username = "admin", password = "admin"}, + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version ESIP-12-v302r125573-131230c_upc on EPC3925 + -- ES-16-E138-c3220r55103-150810 on EPC3928AD + name = "Cisco EPC39xx", + cpe = "cpe:/h:cisco:epc39*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Docsis", 1, true) + and response.body:find("window%.location%.href%s*=%s*(['\"])Docsis_system%.asp%1") + end, + login_combos = { + {username = "", password = ""}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {username_login=user, + password_login=pass, + LanguageSelect="en", + Language_Submit="0", + login="Log In"} + local resp = http_post_simple(host, port, + url.absolute(path, "goform/Docsis_system"), + nil, form) + local loc = resp.header["location"] or "" + return resp.status == 302 + and (loc:find("/Quick_setup%.asp$") or loc:find("/Administration%.asp$")) + end +}) + +table.insert(fingerprints, { + -- Version 1.0.1.3 on RT-N10U, RT-N66U + name = "ASUS RT", + cpe = "cpe:/h:asus:rt-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + -- realm="RT-N10U", "RT-N66U" + return (http_auth_realm(response) or ""):find("^RT%-%u[%u%d]+$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 5.00.12 on F5D7234-4 + name = "Belkin G Wireless Router", + cpe = "cpe:/h:belkin:f5d7234-4", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("setup_top.htm", 1, true) + and response.body:find("status.stm", 1, true) + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/login.exe"), nil, + -- this should be local time, not UTC + {totalMSec = stdnse.clock_ms()/1000, + pws = stdnse.tohex(openssl.md5(pass))}) + return resp.status == 302 + and (resp.header["location"] or ""):find("/index%.htm$") + end +}) + +table.insert(fingerprints, { + -- Version 1.00.12 on F9K1001 v1 + name = "Belkin N150", + cpe = "cpe:/h:belkin:n150_f9k1001", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Belkin", 1, true) + and response.body:find("isAPmode", 1, true) + and response.body:lower():find("showmenu.js", 1, true) + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {page="", + logout="", + action="submit", + pws=base64.enc(pass), + itsbutton1="Submit", + h_language="en", + is_parent_window="1"} + local resp = http_post_simple(host, port, url.absolute(path, "login.cgi"), + nil, form) + return resp.status == 200 + and resp.body + and resp.body:find("index.html", 1, true) + end +}) + +table.insert(fingerprints, { + -- Version H131-310CTU-C07_R01_4.5.5.27 + name = "Comtrend NexusLink-5631", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "DSL Router" + end, + login_combos = { + {username = "apuser", password = "apuser"}, + {username = "root", password = "12345"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 1.0.1-00 on model 5554 + name = "Zoom ADSL X5", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 301 + and (response.header["server"] or ""):find("^Nucleus/%d+%.") + and (response.header["location"] or ""):find("/hag/pages/home%.htm$") + end, + login_combos = { + {username = "admin", password = "zoomadsl"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, + url.absolute(path, "hag/pages/home.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 2.3, 2.4 on FVS318 + name = "Netgear ProSafe Firewall FVS318", + cpe = "cpe:/h:netgear:fvs318", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Netgear" + and response.body + and response.body:lower():find("<frame%s+src%s*=%s*(['\"]?)top.html%1%s") + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, url.absolute(path, "top.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 2.00.03, .05, .07, .08 on GS108Ev3, GS108PEv3 + name = "Netgear ProSafe Plus Switch", + cpe = "cpe:/h:netgear:gs108*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("loginTData", 1, true) + and response.body:lower():find("<title>netgear ", 1, true) + end, + login_combos = { + {username = "", password = "password"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "login.cgi"), + nil, {password=pass}) + return resp.status == 200 and sets_cookie(resp, "GS108SID", ".") + end +}) + +table.insert(fingerprints, { + -- AP6521, AP6522, AP7522, AP7532 + name = "Motorola AP", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^lighttpd/%d+%.") + and response.body + and response.body:lower():find("<title>motorola solutions</title>", 1, true) + end, + login_combos = { + {username = "admin", password = "motorola"} + }, + login_check = function (host, port, path, user, pass) + local form = {_dc = math.floor(stdnse.clock_ms()), + username = user, + password = pass} + local lurl = url.absolute(path, "rest.fcgi/services/rest/login?" .. url.build_query(form)) + local resp = http_get_simple(host, port, lurl) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.status + end +}) + +table.insert(fingerprints, { + -- Version 3.3.2, 4.3.1, 4.4.0, 4.4.1 on RFS6000 + name = "Motorola RF Switch", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^thttpd/%d+%.") + and response.body + and response.body:lower():find("<title>motorola wireless network management</title>", 1, true) + end, + login_combos = { + {username = "admin", password = "superuser"} + }, + login_check = function (host, port, path, user, 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 resp = http_get_simple(host, port, lurl) + return resp.status == 200 + and (resp.body or ""):find("^login 0 ") + end +}) + +table.insert(fingerprints, { + -- Version 3.4.5.1 on Aruba800 + name = "ArubaOS WebUI", + cpe = "cpe:/o:arubanetworks:arubaos", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 401 + and response.body + and response.body:find("/images/arubalogo.gif", 1, true) + and response.body:find("/screens/wms/wms.login", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "screens/wms/wms.login"), + nil, + {opcode="login", url="/", needxml="0", + uid=user, passwd=pass}) + return resp.status == 200 + and (resp.body or ""):find("/screens/wmsi/monitor.summary.html", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Aruba AirWave", + cpe = "cpe:/a:arubanetworks:airwave", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 401 + and response.body + and response.body:find("/noauth/theme/airwave/favicon.ico", 1, true) + and response.body:find("/api/user_prefs.json", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "LOGIN", + "403 Forbidden", + {credential_0=user, credential_1=pass, + destination=url.absolute(path, "index.html")}) + end +}) + +table.insert(fingerprints, { + -- Version 08.05.100 on NVR 1750D + name = "Nortel VPN Router", + cpe = "cpe:/h:nortel:vpn_router_*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "HTTP Server" + and response.body + and response.body:lower():find("<title>nortel vpn router</title>", 1, true) + end, + login_combos = { + {username = "admin", password = "setup"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, + url.absolute(path, "manage/bdy_sys.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 5.0.0r8 on NetScreen 5XT + name = "ScreenOS", + cpe = "cpe:/o:juniper:netscreen_screenos", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Virata%-EmWeb/R%d+_") + and response.body + and response.body:lower():find("admin_pw", 1, true) + end, + login_combos = { + {username = "netscreen", password = "netscreen"} + }, + login_check = function (host, port, path, user, pass) + local form = {admin_id="", + admin_pw="", + time=tostring(math.floor(stdnse.clock_ms())):sub(5), + un=base64.enc(user), + pw=base64.enc(pass)} + local resp = http_post_simple(host, port, url.absolute(path, "index.html"), + nil, form) + return resp.status == 303 + and (resp.header["location"] or ""):find("/nswebui.html?", 1, true) + end +}) + +table.insert(fingerprints, { + -- Version 11.4.1, 11.5.3 + name = "F5 TMOS", + cpe = "cpe:/o:f5:tmos", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("F5 Networks", 1, true) + and response.body:find("BIG-IP", 1, true) + and response.body:find("/tmui/tmui/system/settings/redirect.jsp", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "tmui/logmein.html", + "login%.jsp%?msgcode=1", + {username=user, passwd=pass}) + end +}) + +table.insert(fingerprints, { + -- Version 10.5 on MPX 8005 + name = "Citrix NetScaler", + cpe = "cpe:/a:citrix:netscaler", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NetScaler", 1, true) + and response.body:lower():find("<title>citrix login</title>", 1, true) + end, + login_combos = { + {username = "nsroot", password = "nsroot"} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "login/do_login", + "Invalid username or password", + {username=user, password=pass, url="", timezone_offset="0"}, + false) + end +}) + +--- +--Digital recorders +--- +table.insert(fingerprints, { + -- UI Version 03.2 (4.8), 03.2 (5.5) + name = "DM Digital Sprite 2", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Dedicated Micros", 1, true) + and response.body:find("webpages/index.shtml", 1, true) + and response.body:lower():find('<meta%s+name%s*=%s*"author"%s+content%s*=%s*"dedicated micros ') + end, + login_combos = { + {username = "dm", password = "web"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, + url.absolute(path, "frmpages/index.html"), + user, pass, true) + end +}) + +table.insert(fingerprints, { + -- Version SD32L30, ECS116/A + name = "DM NetVu", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Dedicated Micros", 1, true) + and response.body:find("/gui/gui_outer_frame.shtml", 1, true) + and response.body:lower():find('<meta%s+name%s*=%s*"author"%s+content%s*=%s*"dedicated micros ') + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "gui/frmpages/gui_system.shtml") + -- Check if authentication is required at all + local resp = http_get_simple(host, port, lurl) + if resp.status == 200 then + return (resp.body or ""):find('top.render_table("System Page"', 1, true) + end + return try_http_basic_login(host, port, lurl, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "AXIS Network Camera (M Series)", + category = "security", + paths = { + {path = "/view/viewer_index.shtml"}, + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + return realm:find("AXIS") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = ""}, + {username = "root", password = ""}, + {username = "root", password = "root"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Hikvision DS-XXX Network Camera", + category = "security", + paths = { + {path = "/PSIA/Custom/SelfExt/userCheck"}, + }, + target_check = function (host, port, path, response) + return response.header["server"] == "App-webs/" + + end, + login_combos = { + {username = "admin", password = "12345"}, + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "NUOO DVR", + category = "security", + paths = { + {path = "/"}, + }, + target_check = function (host, port, path, response) + return response.header['server'] and response.header["server"]:find("lighttpd") + and response.body and response.body:lower():find("<title>network video recorder login</title>") + end, + login_combos = { + {username = "admin", password = "admin"}, + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "login.php"), nil, + {language="en", user=user, pass=pass,submit="Login"}) + if resp.status == 302 and not(resp.body:find("loginfail")) then return true end + end +}) + +--- +--Industrial systems +--- +table.insert(fingerprints, { + -- Version 2.1.2, 2.2.0 on TSX ETY Port, 1.0.4, 2.2.0 on TSX ETY410 + name = "Schneider Modicon Web", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["server"] or ""):find("^Schneider%-WEB/V%d+%.") + and (response.header["location"] or ""):find("/index%.htm$") + end, + login_combos = { + {username = "USER", password = "USER"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, + url.absolute(path, "secure/system/globaldata.htm?Language=English"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 06.05.00/6.0.1 on QD2040 HW 675 + name = "TCS Basys Controls Communication Center", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Private" + and (response.header["server"] or ""):find("^lighttpd/%d+%.") + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 01.01 + name = "Riello UPS NetMan 204", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^mini_httpd/%d+%.") + and response.body + and response.body:lower():find("<title>netman 204 login</title>", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "fwupgrade", password = "fwupgrade"}, + {username = "user", password = "user"}, + {username = "eurek", password = "eurek"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/login.cgi"), + nil, {username=user, password=pass}) + return resp.status == 200 + and resp.body + and (resp.body:find(">window.location.replace(", 1, true) + or resp.body:find("Another user is logged in", 1, true)) + end +}) + +table.insert(fingerprints, { + -- Version 3.2.5 on AP9606, 2.5.0, 2.6.4 on AP9617, AP9619, 1.1.6 on AP7900 + name = "APC Management Card (basic auth)", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "APC Management Card" + end, + login_combos = { + {username = "apc", password = "apc"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +--- +--Printers +--- +table.insert(fingerprints, { + -- Version 61.17.5Z on ZTC GK420d + name = "Zebra Printer", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Zebra Technologies", 1, true) + and response.body:lower():find('<a href="config.html">view printer configuration</a>', 1, true) + end, + login_combos = { + {username = "", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "authorize", + "incorrect password", {["0"]=pass}) + end +}) + +table.insert(fingerprints, { + -- Version 61.17.5Z on ZTC GK420d, 1.01.4 + name = "Zebra Print Server", + category = "printer", + paths = { + {path = "/server/TCPIPGEN.htm"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Network Print Server" + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 1.04.9 on RICOH MP C4503, 1.05 on MP 5054, 1.12 on MP C5000 + name = "RICOH Web Image Monitor", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Web%-Server/%d+%.") + and response.body + and response.body:find("/websys/webArch/mainFrame.cgi", 1, true) + end, + login_combos = { + {username = "admin", password = ""}, + {username = "supervisor", password = ""} + }, + login_check = function (host, port, path, user, pass) + -- determine proper login path by locale + local resp0 = http.get(host, port, path) + if resp0.status ~= 200 then return false end + local lurl = (resp0.body or ""):match('location%.href="(/[^"]+/)mainFrame%.cgi"') + if not lurl then return false end + -- harvest the login form token + local resp1 = http_get_simple(host, port, url.absolute(lurl, "authForm.cgi"), + {cookies="cookieOnOffChecker=on"}) + if resp1.status ~= 200 then return false end + local token = (resp1.body or ""):match('<input%s+type%s*=%s*"hidden"%s+name%s*=%s*"wimToken"%s+value%s*=%s*"(.-)"') + if not token then return false end + -- build the login form and submit it + local form = {wimToken = token, + userid_work = "", + userid = base64.enc(user), + password_work = "", + password = base64.enc(pass), + open = ""} + local resp2 = http_post_simple(host, port, url.absolute(lurl, "login.cgi"), + {cookies=resp1.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/mainFrame%.cgi$") + and sets_cookie(resp2, "wimsesid", "^%d+$") + end +}) + +table.insert(fingerprints, { + -- Version 071.*, 072.* on WorkCentre 7835, 7845, ColorQube 8900X + name = "Xerox WorkCentre/ColorQube", + cpe = "cpe:/h:xerox:workcentre", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find('SuppliesType != "InkStick"', 1, true) + and response.body:find("XEROX WORKCENTRE", 1, true) + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + local form = {_fun_function="HTTP_Authenticate_fn", + NextPage=url.absolute(path, "properties/authentication/luidLogin.php"), + webUsername=user, + webPassword=pass, + frmaltDomain="default"} + return try_http_post_login(host, port, path, "userpost/xerox.set", + "/login%.php%?invalid=t", form) + end +}) + +table.insert(fingerprints, { + -- Version 1.1, 1.1 SP7 + name = "EFI Fiery Webtools", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["content-location"] or ""):find("^redirect%.html%.") + and response.body + and response.body:lower():find('content="0;url=wt2parser.cgi?home_', 1, true) + end, + login_combos = { + {username = "Administrator", password = ""}, + {username = "Administrator", password = "Fiery.1"} + }, + login_check = function (host, port, path, user, pass) + -- sessionId normally includes the client IP, not the target, + -- but this would be too revealing + local sessionid = host.ip + .. "_" + .. math.floor(stdnse.clock_ms()) + .. math.random(100000, 999999) + local encpass = xmlencode(pass) + local header = {["Content-Type"]="text/xml", ["SOAPAction"]='""'} + local soapmsg = [[ + <?xml version='1.0' encoding='UTF-8'?> + <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <SOAP-ENV:Body> + <ns1:doLogin xmlns:ns1="urn:FierySoapService" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <sessionId xsi:type="xsd:string">__SESS__</sessionId> + <in xsi:type="ns1:Login"> + <fieldsMask xsi:type="xsd:int">0</fieldsMask> + <password xsi:type="xsd:string">__PASS__</password> + <timeout xsi:type="xsd:int">30</timeout> + <userName xsi:type="xsd:string" xsi:nil="true"/> + </in> + </ns1:doLogin> + </SOAP-ENV:Body> + </SOAP-ENV:Envelope> + ]] + -- strip off indentation + soapmsg = soapmsg:gsub("%f[^\0\n]%s+", "") + -- username is not injected into the payload because it is implied + soapmsg = soapmsg:gsub("__%w+__", {__SESS__=sessionid, __PASS__=encpass}) + local resp = http_post_simple(host, port, url.absolute(path, "soap"), + {header=header}, soapmsg) + return resp.status == 200 + and (resp.body or ""):find('<result xsi:type="xsd:boolean">true</result>', 1, true) + end +}) + +table.insert(fingerprints, { + -- Version 3.6/4 + name = "Lantronix ThinWeb Manager", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and (response.header["server"] or ""):find("^Gordian Embedded") + and response.body + and response.body:lower():find("<title>lantronix thinweb manager", 1, true) + end, + login_combos = { + {username = "", password = "system"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "server_eps.html") + -- obtain login nonce + local resp1 = http_get_simple(host, port, lurl) + local nonce = resp1.status == 403 and sets_cookie(resp1, "SrvrNonce", ".") + if not nonce then return false end + -- credential is the MD5 hash of the nonce and the password (in upper case) + local creds = stdnse.tohex(openssl.md5(nonce .. ":" .. pass:upper())) + local cookies = ("SrvrNonce=%s; SrvrCreds=%s"):format(nonce, creds) + local resp2 = http_get_simple(host, port, lurl, {cookies=cookies}) + return resp2.status == 200 + end +}) + +--- +--Storage +--- +table.insert(fingerprints, { + -- Version TS200R021 on MSA 2000 G3 + name = "HP Storage Management Utility", + category = "storage", + paths = { + {path = "/api/id/"} + }, + -- TODO: Change the probe path to "/" and use the following target_check + -- once the http library adds support for gzip encoding. Don't forget + -- to change url.absolute() argument from "../" to "api/" in login_check. + --target_check = function (host, port, path, response) + -- return have_openssl + -- and response.status == 200 + -- and response.body + -- and response.body:find("brandStrings", 1, true) + -- and response.body:find("checkAuthentication", 1, true) + -- and response.body:find("hp stuff init", 1, true) + --end, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.header["command-status"] + and response.header["command-status"]:find("^0 %({%s*systemName:.*,%s*controller:.*}%)") + end, + login_combos = { + {username = "monitor", password = "!monitor"}, + {username = "manage", password = "!manage"}, + {username = "admin", password = "!admin"} + }, + login_check = function (host, port, path, user, pass) + local creds = stdnse.tohex(openssl.md5(user .. "_" .. pass)) + local header = {["Content-Type"] = "application/x-www-form-urlencoded", + ["datatype"] = "json"} + local resp = http_post_simple(host, port, url.absolute(path, "../"), + {header=header}, "/api/login/" .. creds) + return resp.status == 200 + and (resp.header["command-status"] or ""):find("^1 ") + end +}) + +table.insert(fingerprints, { + -- Version 7.5.0.3 on 2072-24C + name = "IBM Storwize V3700", + cpe = "cpe:/a:ibm:storwize_v3700_software", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("V3700", 1, true) + and response.body:lower():find("<title>[^<]-%sibm storwize v3700%s*</title>") + end, + login_combos = { + {username = "superuser", password = "passw0rd"} + }, + login_check = function (host, port, path, user, pass) + local form = {login=user, + password=pass, + newPassword="", + confirmPassword="", + tzoffset="0", -- present twice in the original form + nextURL="", -- present twice in the original form + licAccept=""} + local resp = http_post_simple(host, port, url.absolute(path, "login"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/gui$") + end +}) + +--- +--Virtualization systems +--- +table.insert(fingerprints, { + -- Version 5.0.0 + name = "VMware ESXi", + cpe = "cpe:/o:vmware:esxi", + category = "virtualization", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("ID_EESX_Welcome", 1, true) + and response.body:find("/folder?dcPath=ha-datacenter", 1, true) + end, + login_combos = { + {username = "root", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_basic_login(host, port, + url.absolute(path, "folder?dcPath=ha-datacenter"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + -- Version 4.0.0 + name = "PCoIP Zero Client", + cpe = "cpe:/a:teradici:pcoip_host_software", + category = "virtualization", + paths = { + {path = "/login.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("PCoIP® Zero Client", 1, true) + and response.body:find("password_value", 1, true) + end, + login_combos = { + {username = "", password = "Administrator"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "cgi-bin/login"), + nil, {password_value=pass, idle_timeout=60}) + -- successful login is a 302-redirect that sets a session cookie with hex + -- value; failed login is the same but the cookie contains an error message + return resp.status == 302 and sets_cookie(resp, "session_id", "^%x+$") + end +}) + +--- +--Remote consoles +--- +table.insert(fingerprints, { + -- Version 5.5, 6.1, 6.2, 7.2 on SLC16, SLC32, SLC48, SLC 8016 + name = "Lantronix SLC", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^mini_httpd/%d+%.") + and response.body + and response.body:find("lantronix", 1, true) + and response.body:find("slcpassword", 1, true) + end, + login_combos = { + {username = "sysadmin", password = "PASS"} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "./", + "%sname%s*=%s*(['\"]?)slcpassword%1[%s>]", + {slclogin=user, slcpassword=pass}) + end +}) + +table.insert(fingerprints, { + --Version 1.10.12, 1.80 + name = "Dell iDRAC6", + cpe = "cpe:/o:dell:idrac6_firmware", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 301 + and (response.header["server"] or ""):find("^Mbedthis%-Appweb/%d+%.") + and (response.header["location"] or ""):find("/start%.html$") + end, + login_combos = { + {username = "root", password = "calvin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_post_login(host, port, path, "data/login", + "<authResult>1</authResult>", + {user=user, password=pass}) + end +}) + +table.insert(fingerprints, { + name = "Dell iDRAC9", + cpe = "cpe:/o:dell:idrac9_firmware", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + -- analyze response for 1st request to "/" + if not (response.status == 302 and (response.header["location"] or ""):find("/restgui/start%.html$")) then return false end + + -- check with 2nd request to "/restgui/start.html" to be sure + local resp = http_get_simple(host, port, url.absolute(path, "restgui/start.html")) + return resp.status == 200 + and resp.body + and resp.body:find("idrac-start-screen", 1, true) + end, + login_combos = { + {username = "root", password = "calvin"} + }, + login_check = function (host, port, path, user, pass) + local headers = { + ["user"]='"'..user..'"', + ["password"]='"'..pass..'"' + } + local resp = http_post_simple(host, port, url.absolute(path, "sysmgmt/2015/bmc/session"), + {header=headers}) + local body = resp.body or "" + + return (resp.status == 201 and ( + body:find('"authResult":0') -- standard login success + or body:find('"authResult":7') -- login success with default credentials + or body:find('"authResult":9') -- login success with password reset required + ) + ) + end +}) + +table.insert(fingerprints, { + --Version 1.1 on Supermicro X7SB3 + name = "Supermicro WPCM450", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("ATEN International", 1, true) + and response.body:find("/cgi/login.cgi", 1, true) + end, + login_combos = { + {username = "ADMIN", password = "ADMIN"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "cgi/login.cgi"), + nil, {name=user, pwd=pass}) + return resp.status == 200 + and (resp.body or ""):find("../cgi/url_redirect.cgi?url_name=mainmenu", 1, true) + end +}) |