From e356eb536c2b72b8416403b6cfc8199ef4a5e12f Mon Sep 17 00:00:00 2001 From: "@xer0dayz" <1n3@hushmail.com> Date: Wed, 1 Jul 2020 17:44:34 -0700 Subject: [PATCH] Merged several changes for v8.5 --- ...fault-accounts-fingerprints-nndefaccts.lua | 11145 ++++++++++++++++ templates/passive/Default_Credentials_1.sh | 8 + templates/passive/Default_Credentials_2.sh | 8 + 3 files changed, 11161 insertions(+) create mode 100644 bin/http-default-accounts-fingerprints-nndefaccts.lua create mode 100644 templates/passive/Default_Credentials_1.sh create mode 100644 templates/passive/Default_Credentials_2.sh diff --git a/bin/http-default-accounts-fingerprints-nndefaccts.lua b/bin/http-default-accounts-fingerprints-nndefaccts.lua new file mode 100644 index 0000000..aca4a43 --- /dev/null +++ b/bin/http-default-accounts-fingerprints-nndefaccts.lua @@ -0,0 +1,11145 @@ +--[[ +This file is part of NNdefaccts, an alternate fingerprint dataset for +Nmap script http-default-accounts. + +NNdefaccts is Copyright (c) 2012-2019 by nnposter +(nnposter /at/ users.sourceforge.net, ) + +NNdefaccts is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) +any later version. + +NNdefaccts is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . + +Note that NNdefaccts is licensed separately from Nmap. By obtaining +a custom license for Nmap you are not automatically entitled to modify or +distribute the NNdefaccts dataset to the same extent as Nmap itself and, +conversely, licensing NNdefaccts does not cover Nmap. For details, see +. + +You can obtain the latest version of the dataset from its public repository +at . + +To report bugs and other problems, contribute patches, request a feature, +provide generic feedback, etc., please see instructions posted at +. +]] + + +local base64 = require "base64" +local http = require "http" +local json = require "json" +local math = require "math" +local os = require "os" +local shortport = require "shortport" +local stdnse = require "stdnse" +local table = require "table" +local url = require "url" +local have_openssl, openssl = pcall(require, "openssl") +local have_rand, rand = pcall(require, "rand") +local have_stringaux, stringaux = pcall(require, "stringaux") +local have_tableaux, tableaux = pcall(require, "tableaux") + +--- +-- http-default-accounts-fingerprints-nndefaccts.lua +-- This file contains fingerprint data for http-default-accounts.nse +-- +-- STRUCTURE: +-- * name - Descriptive name +-- * cpe - Official CPE Dictionary entry (optional) +-- * category - Category +-- * login_combos - Table of default credential pairs +---- * username +---- * password +-- * paths - Table of likely locations (paths) of the target +-- * target_check - Validation function of the target +-- (optional but highly recommended) +-- * login_check - Login function of the target +--- + +--- +-- Backwards compatibility provisions for library rand +--- +if not have_rand then + rand = {} +end +if not rand.random_string then + rand.random_string = stdnse.generate_random_string +end + +--- +-- Generates a random alphanumeric string. +-- +-- @param len Length of the output string. +-- @return A random string consisting of letters and digits +--- +local function random_alnum (len) + return rand.random_string(len, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") +end + +--- +-- Generates a random hexadecimal string. +-- +-- @param len Length of the output string. +-- @return A random string consisting of hexadecimal digits +--- +local function random_hex (len) + return rand.random_string(len, "0123456789abcdef") +end + +--- +-- Backwards compatibility provisions for library stringaux +--- +if not have_stringaux then + stringaux = {} +end +if not stringaux.ipattern then + stringaux.ipattern = stdnse.generate_case_insensitive_pattern +end + +--- +-- Backwards compatibility provisions for library tableaux +--- +if not have_tableaux then + tableaux = {} +end +if not tableaux.tcopy then + tableaux.tcopy = + function (tbl) + local clone = {} + for k,v in pairs(tbl) do + clone[k] = type(v) == "table" and tableaux.tcopy(v) or v + end + return clone + end +end +if not tableaux.contains then + tableaux.contains = stdnse.contains +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 = 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 http_post_simple() with the body formatted as +-- Content-Type multipart/form-data. +-- @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 table of data to be posted +-- @return A response table (see library http.lua for description) +--- +local function http_post_multipart (host, port, path, options, postdata) + local boundary = ("-"):rep(20) + .. math.random(1000000, 9999999) + .. math.random(1000000, 9999999) + local opts = tableaux.tcopy(options or {}) + opts.header = opts.header or {} + opts.header["Content-Type"] = "multipart/form-data; boundary=" .. boundary + if type(postdata) ~= "table" then + return {status = nil, + ["status-line"] = "POST data must be a table", + header = {}, + rawheader = {}} + end + boundary = "--" .. boundary + local body = {} + for k, v in pairs(postdata) do + table.insert(body, boundary) + table.insert(body, ('Content-Disposition: form-data; name="%s"'):format(k)) + table.insert(body, "") + table.insert(body, v) + end + table.insert(body, boundary .. "--") + table.insert(body, "") + return http_post_simple (host, port, path, opts, table.concat(body, "\r\n")) +end + +--- +-- Requests given path using native HTTP authentication. +-- @param host Host table +-- @param port Port table +-- @param path Path to request +-- @param user HTTP authentication username +-- @param pass HTTP authentication password +-- @param digest true: digest auth, false: basic auth, "any": try to detect +-- @return True if login in was successful +--- +local function try_http_auth (host, port, path, user, pass, digest) + if digest == "any" then + local resp = http_get_simple(host, port, path) + local auth = (resp.header["www-authenticate"] or ""):lower():match("^%w+") + if not auth then return end + digest = auth == "digest" + end + local creds = {username = user, password = pass, digest = digest} + local resp = http_get_simple(host, port, path, {auth=creds}) + return resp.status and not (resp.status >= 400 and resp.status <= 405) +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 "" + -- NB: "OEM Netcam" devices lack the closing double quote + 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 get_cookie (response, name, pattern) + name = name:lower() + for _, ck in ipairs(response.cookies or {}) do + if ck.name:lower() == name and (not pattern or ck.value:find(pattern)) then + return ck.value + end + end + return false +end + +--- +-- Parses an HTML tag and returns parsed attributes +-- @param html a string representing HTML tag. It is expected that the first +-- and last characters are angle brackets. +-- @return table of attributes with their names converted to lowercase +--- +local function parse_tag (html) + local attrs = {} + local _, pos = html:find("^<%f[%w][%w-]+[^%w-]") + while true do + local attr, equal + _, pos, attr, equal = html:find("%f[%w]([%w-]+)%s*(=?)%s*", pos) + if not pos then break end + local oldpos = pos + 1 + if equal == "=" then + local c = html:sub(oldpos, oldpos) + if c == "\"" or c == "'" then + oldpos = oldpos + 1 + pos = html:find(c, oldpos, true) + else + pos = html:find("[%s>]", oldpos) + end + if not pos then break end + else + pos = oldpos + end + attrs[attr:lower()] = html:sub(oldpos, pos - 1) + end + return attrs +end + +--- +-- Searches given HTML string for an element tag that meets given attribute +-- critera and returns its position and all its attributes +-- @param html a string representing HTML test +-- @param elem an element to search for (for example "img" or "div") +-- @param criteria a table of attribute names and corresponding patterns, +-- for example {id="^secret$"}. The patterns are treated as case-insensitive. +-- (optional) +-- @param init a string position from which to start searching (optional) +-- @return position of the opening angle bracket of the found tag or nil +-- @return position of the closing angle bracket of the found tag or nil +-- @return table of tag attributes with their names converted to lowercase +--- +local function find_tag (html, elem, criteria, init) + local icrit = {} + for cnam, cptn in pairs(criteria or {}) do + icrit[cnam:lower()] = stringaux.ipattern(cptn) + end + local tptn = stringaux.ipattern("<" .. elem:gsub("%-", "%%-") .. "%f[%s/>].->") + local start + local stop = init + while true do + start, stop = html:find(tptn, stop) + if not start then break end + local attrs = parse_tag(html:sub(start, stop)) + local found = true + for cnam, cptn in pairs(icrit) do + local cval = attrs[cnam] + if not (cval and cval:find(cptn)) then + found = false + break + end + end + if found then return start, stop, attrs end + end + return +end + +--- +-- Searches given HTML string for an element tag that meets given attribute +-- critera and returns all its attributes +-- @param html a string representing HTML test +-- @param elem an element to search for (for example "img" or "div") +-- @param criteria a table of attribute names and corresponding patterns, +-- for example {id="^secret$"}. The patterns are treated as case-insensitive. +-- (optional) +-- @param init a string position from which to start searching (optional) +-- @return table of tag attributes with their names converted to lowercase +--- +local function get_tag (html, elem, criteria, init) + local start, stop, attrs = find_tag(html, elem, criteria, init) + return attrs +end + +--- +-- Builds an iterator function that searches given HTML string for element tags +-- that meets given attribute critera +-- @param html a string representing HTML test +-- @param elem an element to search for (for example "img" or "div") +-- @param criteria a table of attribute names and corresponding patterns, +-- for example {id="^secret$"}. The patterns are treated as case-insensitive. +-- (optional) +-- @param init a string position from which to start searching (optional) +-- @return iterator +--- +local function get_tags (html, elem, criteria) + local init = 0 + return function () + local _, attrs + _, init, attrs = find_tag(html, elem, criteria, (init or #html) + 1) + return attrs + end +end + +--- +-- Searches given HTML string for an element tag that meets given attribute +-- critera and returns inner HTML of the corresponding element +-- (Nested elements of the same type are not supported.) +-- @param html a string representing HTML test +-- @param elem an element to search for (for example "div" or "title") +-- @param criteria a table of attribute names and corresponding patterns, +-- for example {id="^secret$"}. The patterns are treated as case-insensitive. +-- (optional) +-- @param init a string position from which to start searching (optional) +-- @return inner HTML +--- +local function get_tag_html (html, elem, criteria, init) + local _, start, attrs = find_tag(html, elem, criteria, init) + if not start then return end + start = start + 1 + local stop = html:find(stringaux.ipattern("]"), start) + return stop and html:sub(start, stop - 1) or nil +end + +--- +-- Searches given HTML string for a meta refresh tag and returns the target URL +-- @param html a string representing HTML test +-- @param criteria a pattern to validate the extracted target URL +-- for example {id="^secret$"}. The patterns are treated as case-insensitive. +-- (optional) +-- @param init a string position from which to start searching (optional) +-- @return table of tag attributes with their names converted to lowercase +--- +local function get_refresh_url (html, criteria) + local refresh = get_tag(html, "meta", {["http-equiv"]="^refresh$", content="^0;%s*url="}) + if not refresh then return end + local url = refresh.content:match("=(.*)") + return url:find(stringaux.ipattern(criteria)) and url or nil +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 host and port script objects. These +-- components can then be passed onto function url.build. +-- +-- As an example, the following code generates a URL for path "/test/" +-- on the current host and port: +-- +-- local testurl = url.build(url_build_defaults(host, port, {path = "/test/"})) +-- +-- or, alternatively, when not used as a filter: +-- +-- local parsed = url_build_defaults(host, port) +-- parsed.path = "/test/" +-- local testurl = url.build(parsed) +-- +-- +-- @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 url.parse, +-- 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 + +--- +-- Decodes an XML-encoded string. +-- +-- @param s The string to be decoded. +-- @return A string with XML encoding stripped off +--- +local function xmldecode (s) + local refmap = {amp = "&", quot = "\"", apos = "'", lt ="<", gt = ">"} + return s:gsub("&.-;", + function (e) + local r = e:sub(2,-2) + if r:find("^#x%x%x$") then + return stdnse.fromhex(r:sub(3)) + end + return refmap[r] + end) +end + +--- +-- Performs URL encoding of all characters in a string. +-- +-- @param s The string to be encoded. +-- @return A URL-encoded string +--- +local function urlencode_all (s) + return s:gsub(".", function (c) return ("%%%02x"):format(c:byte()) end) +end + +--- +-- Decodes a base64-encoded string safely, catching any decoding errors. +-- +-- @param s The string to be decoded. +-- @return A decoded string or nil if the input is invalid +--- +local function b64decode (s) + local status, out = pcall(base64.dec, s) + return status and out or nil +end + + +fingerprints = {} + +--- +--WEB +--- +table.insert(fingerprints, { + name = "Ansible AWX", + cpe = "cpe:/a:ansible:tower", + category = "web", + paths = { + {path = "/api/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and get_cookie(response, "csrftoken", "^%w+$") + and response.body + and response.body:find("AWX REST API", 1, true)) then + return false + end + local jstatus, jout = json.parse(response.body) + return jstatus and jout.description == "AWX REST API" + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if resp1.status ~= 200 then return false end + local token = get_cookie(resp1, "csrftoken") + if not token then return false end + local form = {username=user, + password=pass, + next=path} + local header = {["X-CSRFToken"]=token} + local resp2 = http_post_simple(host, port, url.absolute(path, "login/"), + {cookies=resp1.cookies, header=header}, form) + return resp2.status == 302 + and resp2.header["location"] == path + and get_cookie(resp2, "userLoggedIn") == "true" + end +}) + +table.insert(fingerprints, { + 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 (get_cookie(response, "Cacti") or get_cookie(response, "CactiEZ")) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {action="login", + login_username=user, + login_password=pass} + local resp = http_post_simple(host, port, url.absolute(path, "index.php"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Zabbix", + cpe = "cpe:/a:zabbix:zabbix", + category = "web", + paths = { + {path = "/zabbix/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 and get_cookie(response, "zbx_sessionid") + end, + login_combos = { + {username = "admin", password = "zabbix"} + }, + login_check = function (host, port, path, user, pass) + local form = {request="", + name=user, + password=pass, + enter="Sign in"} + local resp = http_post_simple(host, port, url.absolute(path, "index.php"), + nil, form) + return resp.status == 302 and resp.header["location"] == "dashboard.php" + end +}) + +table.insert(fingerprints, { + name = "Xplico", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 and get_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") + local resp1 = http_get_simple(host, port, lurl) + if not (resp1.status == 200 and resp1.body) then return false end + local html = get_tag_html(resp1.body, "form", {action="/users/login$"}) + if not html then return false end + local form = {} + for input in get_tags(html, "input", {type="^hidden$", name="", value=""}) do + form[input.name] = input.value + end + 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, { + 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("extrahop login", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local token = get_tag(resp1.body, "input", {type="^hidden$", name="^csrfmiddlewaretoken$", value=""}) + if not token then return false end + local form = {[token.name]=token.value, + next=path, + username=user, + password=pass} + 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 ""):sub(-#path) == path + end +}) + +table.insert(fingerprints, { + 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"}, + {username = "nagiosadmin", password = "PASSW0RD"}, + {username = "nagiosadmin", password = "CactiEZ"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "ManageEngine OpManager 10/11", + cpe = "cpe:/a:zohocorp:manageengine_opmanager", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.body + and response.body:find("%Wwindow%.location%.href%s*=%s*(['\"])[^'\"]-/LoginPage%.do%1")) then + return false + end + local resp = http_get_simple(host, port, url.absolute(path, "LoginPage.do")) + return resp.status == 200 + and resp.body + and resp.body:find("ManageEngine", 1, true) + and resp.body:lower():find("<title>%s*manageengine opmanager%s*") + and get_tag(resp.body, "form", {action="/jsp/login%.do$"}) + end, + login_combos = { + {username = "IntegrationUser", password = "plugin"}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, url.absolute(path, "LoginPage.do")) + if resp1.status ~= 200 then return false end + local form2 = {clienttype="html", + isCookieADAuth="", + domainName="NULL", + authType="localUserLogin", + webstart="", + ScreenWidth=1024, + ScreenHeight=768, + loginFromCookieData="", + userName=user, + password=pass, + uname=""} + local resp2 = http_post_simple(host, port, + url.absolute(path, "jsp/Login.do"), + {cookies=resp1.cookies}, form2) + return (resp2.status == 200 or resp2.status == 302) + and get_cookie(resp2, "OPUTILSTICKET", "^%x+$") + end +}) + +table.insert(fingerprints, { + name = "ManageEngine OpManager 12", + cpe = "cpe:/a:zohocorp:manageengine_opmanager", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("ManageEngine", 1, true) + and response.body:lower():find("%s*manageengine opmanager%s*") + and get_tag(response.body, "form", {action="^j_security_check%f[;\0]"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if resp1.status ~= 200 then return false end + local form2 = {AUTHRULE_NAME="Authenticator", + clienttype="html", + ScreenWidth=1024, + ScreenHeight=768, + loginFromCookieData="false", + ntlmv2="false", + j_username=user, + j_password=pass, + domainNameAD="Authenticator", + uname=""} + local resp2 = http_post_simple(host, port, + url.absolute(path, "j_security_check"), + {cookies=resp1.cookies}, form2) + return resp2.status == 303 + and (resp2.header["location"] or ""):sub(-#path) == path + end +}) + +table.insert(fingerprints, { + name = "ntopng", + cpe = "cpe:/a:ntop:ntopng", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local loc = response.header["location"] or "" + if not (response.status == 302 + and loc:find("/lua/login.lua?referer=", 1, true) + and get_cookie(response, "session") == "") then + return false + end + local resp = http_get_simple(host, port, loc) + return resp.status == 200 + and resp.body + and resp.body:find("ntopng", 1, true) + and resp.body:lower():find("welcome to ntopng", 1, true) + and get_tag(resp.body, "form", {action="/authorize%.html$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {user=user, + password=pass, + referer=host.name .. path} + local resp = http_post_simple(host, port, + url.absolute(path, "authorize.html"), + nil, form) + return resp.status == 302 + and resp.header["location"] == path + and get_cookie(resp, "user") == user + end +}) + +table.insert(fingerprints, { + name = "OpenNMS", + cpe = "cpe:/a:opennms:opennms", + category = "web", + paths = { + {path = "/login.jsp"}, + {path = "/opennms/login.jsp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("OpenNMS", 1, true) + and response.body:lower():find("%s*opennms web console%s*") + and get_tag(response.body, "input", {name="^j_username$"}) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "rtc", password = "rtc"} + }, + login_check = function (host, port, path, user, pass) + local form = {j_username=user, + j_password=pass, + j_usergroups="", + Login=""} + local resp = http_post_simple(host, port, + url.absolute(path, "j_spring_security_check"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/index%.jsp%f[?\0]") + end +}) + +table.insert(fingerprints, { + name = "SevOne NMS", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and get_cookie(response, "SEVONE") + and response.body + and response.body:lower():find("sevone nms - network manager", 1, true) + end, + login_combos = { + {username = "Admin", password = "SevOne"}, + {username = "SevOneStats", password = "n3v3rd13"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local token = resp1.body:match("GlobalData%.Utilities%.Xsrf%.setToken%(%s*['\"](%x+)") + if not token then return false end + local form = {login=user, + passwd=pass, + browser="mozilla", + version=52, + tzString=os.date("!%a %b %d %Y %H:%M:%S GMT+0000"), + check_tz=0} + local refpath = url.absolute(path, "doms/login/index.php") + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=refpath})), + ["X-CSRFToken"]=token} + local resp2 = http_post_simple(host, port, + url.absolute(refpath, "processLogin.php"), + {cookies=resp1.cookies, header=header}, form) + if not (resp2.status == 200 and resp2.body) then return false end + local jstatus, jout = json.parse(resp2.body) + return jstatus and (jout.status == 0 or jout.status == -3) + end +}) + +table.insert(fingerprints, { + name = "Device42 Appliance Manager", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 and get_cookie(response, "d42amid") + end, + login_combos = { + {username = "d42admin", password = "default"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "accounts/login/") + local resp1 = http_get_simple(host, port, lurl .. "?next=" .. path) + if not (resp1.status == 200 and resp1.body) then return false end + local form = {csrfmiddlewaretoken=get_cookie(resp1, "d42amid_csrftoken"), + username=user, + password=pass, + next=path} + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=lurl}))} + local resp2 = http_post_simple(host, port, lurl, + {cookies=resp1.cookies, header=header}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):sub(-#path) == path + end +}) + +table.insert(fingerprints, { + name = "Grafana", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 and get_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 get_cookie(resp, "grafana_user") == user + end +}) + +table.insert(fingerprints, { + name = "Apache Ambari", + cpe = "cpe:/a:apache:ambari", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">Ambari<", 1, true) + and response.body:lower():find("<title>ambari", 1, true) + and get_tag(response.body, "script", {src="^javascripts/app%.js$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "api/v1/users/admin"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Cloudera Manager", + cpe = "cpe:/a:cloudera:cloudera_manager", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and get_cookie(response, "CLOUDERA_MANAGER_SESSIONID") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {j_username=user, + j_password=pass, + returnUrl="", + submit=""} + local resp = http_post_simple(host, port, + url.absolute(path, "j_spring_security_check"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/cmf/postLogin%f[?\0]") + end +}) + +table.insert(fingerprints, { + name = "OpenDaylight", + cpe = "cpe:/a:opendaylight:opendaylight", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and get_cookie(response, "JSESSIONID", "^%x+$") + and response.body + and response.body:find("OpenDaylight", 1, true) + and response.body:lower():find("opendaylight ", 1, true) + and get_tag(response.body, "form", {action="^j_security_check%f[;\0]"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if resp1.status ~= 200 then return false end + local resp2 = http_post_simple(host, port, + url.absolute(path, "j_security_check"), + {cookies=resp1.cookies}, + {j_username=user, j_password=pass}) + return resp2.status == 302 + and (resp2.header["location"] or ""):find(path, -#path, true) + end +}) + +table.insert(fingerprints, { + name = "OrientDB Studio", + cpe = "cpe:/a:orientdb:orientdb", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("OrientDB", 1, true) + and get_tag(response.body, "meta", {content="^OrientDB Studio$"}) + and get_refresh_url(response.body, "/studio/index%.html$") + end, + login_combos = { + {username = "reader", password = "reader"}, + {username = "writer", password = "writer"}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, url.absolute(path, "listDatabases")) + if not (resp1.status == 200 and resp1.body) then return false end + local jstatus, jout = json.parse(resp1.body) + if not (jstatus and type(jout.databases) == "table") then return false end + for _, db in ipairs(jout.databases) do + if try_http_auth(host, port, + url.absolute(path, "connect/" .. url.escape(db)), + user, pass, false) then + return true + end + end + return false + end +}) + +table.insert(fingerprints, { + name = "RockMongo", + cpe = "cpe:/a:rockmongo:rockmongo", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local loc = response.header["location"] or "" + if not (response.status == 302 + and loc:find("/index.php?action=login.index", 1, true)) then + return false + end + local resp = http_get_simple(host, port, loc) + return resp.status == 200 + and resp.body + and resp.body:find("RockMongo", 1, true) + and resp.body:lower():find("<title>rockmongo") + and get_tag(resp.body, "select", {name="^host$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {more=0, + host=0, + username=user, + password=pass, + db="", + lang="en_us", + expire=3} + local resp = http_post_simple(host, port, + url.absolute(path, "index.php?action=login.index&host=0"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("?action=admin.index", 1, true) + and get_cookie(resp, "ROCK_LANG", "^[%a_]+$") + end +}) + +table.insert(fingerprints, { + name = "Sambar Server", + cpe = "cpe:/a:sambar:sambar_server", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^SAMBAR%f[%s\0]") + end, + login_combos = { + {username = "admin", password = ""}, + {username = "anonymous", password = ""}, + {username = "billy-bob", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "session/login"), + user, pass, true) + end +}) + +table.insert(fingerprints, { + 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 = { + {username = "weblogic", password = "weblogic"}, + {username = "weblogic", password = "weblogic1"}, + {username = "weblogic", password = "welcome1"}, + {username = "weblogic", password = "password"}, + {username = "system", password = "Passw0rd"}, + {username = "system", password = "password"}, + {username = "operator", password = "Passw0rd"}, + {username = "operator", password = "password"}, + {username = "monitor", password = "Passw0rd"}, + {username = "monitor", password = "password"}, + {username = "oraclesystemuser", password = "Passw0rd"}, + {username = "oraclesystemuser", password = "password"} + }, + login_check = function (host, port, path, user, pass) + local form = {j_username=user, + j_password=pass, + j_character_encoding="UTF-8"} + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=path}))} + local resp = http_post_simple(host, port, + url.absolute(path, "j_security_check"), + {header=header}, form) + if not (resp.status >= 200 and resp.status <= 399) then return false end + 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 = "WebSphere Community Edition Console", + cpe = "cpe:/a:ibm:websphere_application_server", + category = "web", + paths = { + {path = "/console/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/portal%f[/].-/Welcome%f[?\0]") + end, + login_combos = { + {username = "system", password = "manager"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + local resource = resp1.header["location"] + if not (resp1.status == 302 and resource) then return false end + local respath = resource:match("%f[/]/%f[^/].*"):gsub("/%.%f[/]", "") + local resp2 = http_get_simple(host, port, respath) + if resp2.status ~= 200 then return false end + local form3 = {j_username=user, + j_password=pass, + submit="Login"} + local resp3 = http_post_simple(host, port, + url.absolute(respath, "j_security_check"), + {cookies=resp2.cookies}, form3) + return resp3.status == 302 + and (resp3.header["location"] or ""):find(respath, 1, true) + end +}) + +table.insert(fingerprints, { + name = "JBoss EAP Admin Console", + cpe = "cpe:/a:redhat:jboss_enterprise_application_platform", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/admin-console/", 1, true) + and get_tag(response.body, "a", {href="/admin%-console/$"}) + and response.body:lower():find("welcome to jboss", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local curl = url.absolute(path, "admin-console/") + local resp1 = http_get_simple(host, port, + url.absolute(curl, "secure/summary.seam")) + local lurl = resp1.header["location"] + if not (resp1.status == 302 and lurl) then return false end + local lpath = lurl:match("%f[/]/%f[^/].*") + local resp2 = http_get_simple(host, port, lpath) + if resp2.status ~= 200 then return false end + local form3 = {login_form="login_form", + ["login_form:name"]=user, + ["login_form:password"]=pass, + ["login_form:submit"]="Login", + ["javax.faces.ViewState"]="j_id1"} + local resp3 = http_post_simple(host, port, lpath:gsub("[;?].*$", ""), + {cookies=resp1.cookies}, form3) + return resp3.status == 302 + and (resp3.header["location"] or ""):find("/admin-console/secure/summary.seam?conversationId=", 1, true) + end +}) + +table.insert(fingerprints, { + name = "JBoss JMX Console", + cpe = "cpe:/a:redhat:jboss_enterprise_application_platform", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/jmx-console/", 1, true) + and get_tag(response.body, "a", {href="/jmx%-console/$"}) + and response.body:lower():find("<title>welcome to jboss", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "jmx-console/"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "JBoss Web Console", + cpe = "cpe:/a:redhat:jboss_enterprise_web_platform", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/web-console/", 1, true) + and get_tag(response.body, "a", {href="/web%-console/$"}) + and response.body:lower():find("<title>welcome to jboss", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "web-console/"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Apache Tomcat Manager", + cpe = "cpe:/a:apache:tomcat", + category = "web", + paths = { + {path = "/manager/html/"}, + {path = "/manager/status/"}, + {path = "/tomcat/manager/html/"}, + {path = "/tomcat/manager/status/"}, + {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"}, + {username = "admin", password = ""}, + {username = "admin", password = "tomcat"}, + {username = "ADMIN", password = "ADMIN"}, + {username = "ovwebusr", password = "OvW*busr1"}, + {username = "j2deployer", password = "j2deployer"}, + {username = "cxsdk", password = "kdsxc"}, + {username = "xampp", password = "xampp"}, + {username = "QCC", password = "QLogic66"}, + {username = "fhir", password = "FHIRDefaultPassword"}, + {username = "username", password = "password"}, + {username = "username1", password = "password"}, + {username = "pippo", password = "paperino"}, + {username = "topolino", password = "minnie"}, + {username = "root", password = "vagrant"}, + {username = "tomcat", password = "s3cret"}, + {username = "root", password = "owaspbwa"}, + {username = "admin", password = "owaspbwa"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(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"}, + {username = "admin", password = ""}, + {username = "ADMIN", password = "ADMIN"}, + {username = "xampp", password = "xampp"}, + {username = "QCC", password = "QLogic66"}, + {username = "fhir", password = "FHIRDefaultPassword"}, + {username = "username", password = "password"}, + {username = "pippo", password = "paperino"}, + {username = "root", password = "vagrant"}, + {username = "tomcat", password = "s3cret"}, + {username = "root", password = "owaspbwa"}, + {username = "admin", password = "owaspbwa"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Apache ActiveMQ", + cpe = "cpe:/a:apache:activemq", + category = "web", + paths = { + {path = "/admin/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "ActiveMQRealm" + end, + login_combos = { + {username = "user", password = "user"}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Pivotal RabbitMQ", + cpe = "cpe:/a:pivotal_software:rabbitmq", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("RabbitMQ", 1, true) + and response.body:lower():find("<title>rabbitmq management", 1, true) + and get_tag(response.body, "div", {id="^outer$"}) + end, + login_combos = { + {username = "guest", password = "guest"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "api/whoami"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "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_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + 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:find("Axis2", 1, true) + and response.body:lower():find("login to axis2 :: administration page", 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 get_tag(resp.body or "", "a", {href="^axis2%-admin/logout$"}) + end +}) + +table.insert(fingerprints, { + name = "Apache Ofbiz", + cpe = "cpe:/a:apache:ofbiz", + category = "web", + paths = { + {path = "/webtools/"} + }, + target_check = function (host, port, path, response) + local loc = response.header["location"] or "" + if not (response.status == 302 + and loc:find(url.absolute(path, "control/main"), 1, true)) then + return false + end + local resp = http_get_simple(host, port, loc) + return resp.status == 200 + and resp.body + and resp.body:find(url.absolute(loc, "checkLogin"), 1, true) + and resp.body:lower():find("powered by%s+]-%shref%s*=%s*['\"]https?://ofbiz%.apache%.org%W") + end, + login_combos = { + {username = "admin", password = "ofbiz"} + }, + login_check = function (host, port, path, user, pass) + local form = {USERNAME=user, + PASSWORD=pass, + JavaScriptEnabled="Y"} + local resp = http_post_simple(host, port, + url.absolute(path, "control/login"), + nil, form) + return resp.status == 200 + and get_cookie(resp, path:match("/([^/]+)/$") .. ".autoUserLoginId") == user + end +}) + +table.insert(fingerprints, { + name = "Opencast Matterhorn", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local loc = response.header["location"] or "" + if not (response.status == 302 + and loc:find("/login%.html%f[;\0]") + and get_cookie(response, "JSESSIONID", "^%w+$")) then + return false + end + local resp = http_get_simple(host, port, loc) + return resp.status == 200 + and resp.body + and resp.body:find("Matterhorn", 1, true) + and resp.body:lower():find("opencast matterhorn ", 1, true) + and get_tag(resp.body, "form", {action="/j_spring_security_check$"}) + end, + login_combos = { + {username = "admin", password = "opencast"} + }, + login_check = function (host, port, path, user, pass) + local form = {j_username=user, + j_password=pass, + submit="Login"} + local resp = http_post_simple(host, port, + url.absolute(path, "j_spring_security_check"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/welcome%.html$") + and get_cookie(resp, "JSESSIONID", "^%w+$") + end +}) + +table.insert(fingerprints, { + name = "Opencast", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/admin%-ng/login%.html%f[;\0]") + and get_cookie(response, "JSESSIONID", "^%w+$") + end, + login_combos = { + {username = "admin", password = "opencast"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "admin-ng/j_spring_security_check"), + nil, {j_username=user, j_password=pass}) + return resp.status == 302 + and (resp.header["location"] or ""):find("/admin%-ng/index%.html$") + and get_cookie(resp, "JSESSIONID", "^%w+$") + 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 get_cookie(resp, "plloginoccured") == "true" + end +}) + +table.insert(fingerprints, { + name = "GLPI", + cpe = "cpe:/a:glpi-project:glpi", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("GLPI", 1, true) + and response.body:lower():find("<title>glpi ", 1, true) + and get_tag(response.body, "input", {name="^login_name$"}) + end, + login_combos = { + {username = "glpi", password = "glpi"}, + {username = "tech", password = "tech"}, + {username = "post-only", password = "postonly"}, + {username = "normal", password = "normal"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local token = get_tag(resp1.body, "input", {type="^hidden$", name="^_glpi_csrf_token$", value=""}) + if not token then return false end + local form2 = {login_name=user, + login_password=pass, + submit="Post", + [token.name]=token.value} + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=path}))} + local resp2 = http_post_simple(host, port, url.absolute(path, "login.php"), + {cookies=resp1.cookies, header=header}, form2) + return resp2.status == 200 + and (resp2.body or ""):find("%Wwindow%.location%s*=%s*(['\"])[^'\"]-/front/[%w.]+%.php%1") + end +}) + +table.insert(fingerprints, { + name = "OTRS", + cpe = "cpe:/a:otrs:otrs", + category = "web", + paths = { + {path = "/otrs/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("OTRS", 1, true) + and response.body:find(url.absolute(path, "index.pl"), 1, true) + and get_tag(response.body, "input", {name="^requestedurl$"}) + end, + login_combos = { + {username = "root@localhost", password = "root"}, + {username = "root@localhost", password = "changeme"} + }, + login_check = function (host, port, path, user, pass) + local form = {Action="Login", + RequestedURL="", + Lang="en", + TimeOffset=0, + User=user, + Password=pass} + local resp = http_post_simple(host, port, url.absolute(path, "index.pl"), + nil, form) + return resp.status == 302 + and get_cookie(resp, "OTRSAgentInterface", "^%w+$") + end +}) + +table.insert(fingerprints, { + name = "Ilias (var.1)", + cpe = "cpe:/a:ilias:ilias", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and get_cookie(response, "ilClientId") + and (response.header["location"] or ""):find("%f[^/\0]login%.php%?.*%f[^?&]client_id=") + end, + login_combos = { + {username = "root", password = "homer"} + }, + login_check = function (host, port, path, user, pass) + local resp0 = http_get_simple(host, port, path) + local furl = (resp0.header["location"] or ""):gsub("^https?://[^/]*", "") + if not (resp0.status == 302 and furl:find("%f[^/\0]login%.php%?")) then + return false + end + furl = url.absolute(path, furl) + local resp1 = http_get_simple(host, port, furl, {cookies=resp0.cookies}) + if not (resp1.status == 200 and resp1.body) then return false end + local frm = get_tag(resp1.body, "form", {name="^formlogin$", action="[?&;]client_id="}) + if not frm then return false end + local form = {username=user, + password=pass, + ["cmd[doStandardAuthentication]"]="Anmelden"} + local resp2 = http_post_simple(host, port, + url.absolute(furl, xmldecode(frm.action)), + {cookies=resp0.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/ilias%.php?%?") + end +}) + +table.insert(fingerprints, { + name = "Ilias (var.2)", + cpe = "cpe:/a:ilias:ilias", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and get_cookie(response, "ilClientId") + and (response.header["location"] or ""):find("%f[^/\0]ilias%.php%f[?\0]") + end, + login_combos = { + {username = "root", password = "homer"} + }, + login_check = function (host, port, path, user, pass) + local resp0 = http_get_simple(host, port, path) + if resp0.status ~= 302 then return false end + local form1 = {target="", + client_id=get_cookie(resp0, "ilClientId"), + cmd="force_login", + lang="en"} + local furl = url.absolute(path, "login.php?" .. url.build_query(form1)) + local resp1 = http_get_simple(host, port, furl, {cookies=resp0.cookies}) + if not (resp1.status == 200 and resp1.body) then return false end + local frm = get_tag(resp1.body, "form", {name="^formlogin$", action="[?&;]client_id="}) + if not frm then return false end + local form = {username=user, + password=pass, + ["cmd[doStandardAuthentication]"]="Anmelden"} + local resp2 = http_post_simple(host, port, + url.absolute(furl, xmldecode(frm.action)), + {cookies=resp0.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/ilias%.php?%?") + end +}) + +table.insert(fingerprints, { + name = "Jitamin", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("%?controller=Auth/AuthController&action=login$") + and get_cookie(response, "JM_SID") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin@admin.com", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local lurl = path .. "?controller=Auth/AuthController&action=" + local resp1 = http_get_simple(host, port, lurl .. "login") + if not (resp1.status == 200 and resp1.body) then return false end + local token = get_tag(resp1.body, "input", {type="^hidden$", name="^csrf_token$", value=""}) + if not token then return false end + local form = {[token.name]=token.value, + username=user, + password=pass} + local resp2 = http_post_simple(host, port, lurl .. "check", + {cookies=resp1.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("%?controller=Dashboard/DashboardController&action=index$") + end +}) + +table.insert(fingerprints, { + name = "Kanboard", + cpe = "cpe:/a:kanboard:kanboard", + category = "web", + paths = { + {path = "/"}, + {path = "/kanboard/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("%?controller=AuthController&action=login$") + and get_cookie(response, "KB_SID") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local lurl = path .. "?controller=AuthController&action=" + local resp1 = http_get_simple(host, port, lurl .. "login") + if not (resp1.status == 200 and resp1.body) then return false end + local token = get_tag(resp1.body, "input", {type="^hidden$", name="^csrf_token$", value=""}) + if not token then return false end + local form = {[token.name]=token.value, + username=user, + password=pass} + local resp2 = http_post_simple(host, port, lurl .. "check", + {cookies=resp1.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("%?controller=DashboardController&action=show$") + end +}) + +table.insert(fingerprints, { + name = "RainLoop Webmail", + category = "web", + paths = { + {path = "/"}, + {path = "/rainloop/"}, + {path = "/webmail/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("rainloop/v/", 1, true) + and get_tag(response.body, "link", {href="^rainloop/v/%d[%d.]+%d/static/css/app%.min%.css%f[?\0]"}) + end, + login_combos = { + {username = "admin", password = "12345"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path .. "?/AdminAppData") + if not (resp1.status == 200 and resp1.body) then return false end + local jstr = resp1.body:match('{[^{]*"Auth"%s*:.*"PluginsLink"%s*:[^}]*}') + local jstatus, jout = json.parse(jstr or "{}") + local token = jstatus and (jout.Token or jout.System and jout.System.token) + if not token then return false end + local form2 = {Login=user, + Password=pass, + Action="AdminLogin", + XToken=token} + local resp2 = http_post_simple(host, port, path .. "?/Ajax/&q[]=/0/", + {cookies = resp1.cookies}, form2) + if not (resp2.status == 200 and resp2.body) then return false end + jstatus, jout = json.parse(resp2.body) + return jstatus and jout.Action == "AdminLogin" and jout.Result + end +}) + +table.insert(fingerprints, { + name = "TeamPass", + cpe = "cpe:/a:teampass:teampass", + category = "web", + paths = { + {path = "/"}, + {path = "/teampass/"}, + {path = "/TeamPass/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and tableaux.contains(openssl.supported_ciphers(), "aes-256-ecb") + and tableaux.contains(openssl.supported_ciphers(), "aes-256-ctr") + and response.status == 200 + and response.body + and response.body:find("TeamPass", 1, true) + and response.body:find("(['\"])sources/main%.queries%.php%1") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local lurl = resp1.body:match("['\"]([^'\"]+)['\"]%s*,%s*{%s*type%s*:%s*['\"]identify_user['\"]") + local aespwd = resp1.body:match("%Wreturn%s+Aes%.Ctr%.encrypt%s*%(%s*%w+%s*,%s*['\"](.-)['\"]%s*,%s*256%s*%)") + or resp1.body:match("['\"]identify_user['\"]%s*,%s*data%s*:%s*prepareExchangedData%(%s*%w+%s*,%s*['\"]encode['\"]%s*,%s*['\"](.-)['\"]") + if not (lurl and aespwd) then return false end + aespwd = aespwd .. ("\0"):rep(32-#aespwd) + local aeskey = openssl.encrypt("aes-256-ecb", aespwd, nil, aespwd):sub(1, 16):rep(2) + local nonce = ("<I4"):pack(math.floor(stdnse.clock_ms() / 1000)) + .. string.char(math.random(0, 255)):rep(4) + local randstr = random_alnum(10) + local jin = {login=user, + pw=pass, + duree_session="60", + screenHeight=tostring(math.random(480, 1024)), + randomstring=randstr} + json.make_object(jin) + local ctext = base64.enc(nonce .. openssl.encrypt("aes-256-ctr", aeskey, nonce .. ("\0"):rep(8), json.generate(jin))) + local resp2 = http_post_simple(host, port, url.absolute(path, lurl), + {cookies = resp1.cookies}, + {type="identify_user",data=ctext}) + if not (resp2.status == 200 and resp2.body) then return false end + local jstatus, jout = json.parse(resp2.body) + return jstatus and jout[1] and jout[1].value == randstr + end +}) + +table.insert(fingerprints, { + name = "CapeSoft TimeClock", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("TimeClock", 1, true) + and response.body:lower():find("<title>capesoft time clock web ", 1, true) + and response.body:lower():find("%Whref%s*=%s*(['\"])employees%.php%1") + end, + login_combos = { + {username = "9970", password = "password"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "employees.php"), nil, + {login=user,password=pass,action="Login"}) + return resp.status == 200 + and (resp.body or ""):find("%sclass%s*=%s*(['\"]?)logout%1[%s>]") + end +}) + +table.insert(fingerprints, { + 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", 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 = "Greenbone Security Assistant", + cpe = "cpe:/a:greenbone:greenbone_security_assistant", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local loc = (response.header["location"] or ""):gsub("^https?://[^/]*", "") + if not (response.status == 303 + and loc:find("/login/login%.html$")) then + return false + end + local resp = http_get_simple(host, port, loc) + return resp.status == 200 + and resp.body + and resp.body:find("Greenbone", 1, true) + and resp.body:lower():find("greenbone security assistant", 1, true) + and get_tag(resp.body, "form", {action="/omp$"}) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "sadmin", password = "changeme"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "omp") + local form = {cmd="login", + text=lurl.."?r=1", + login=user, + password=pass} + local resp = http_post_simple(host, port, lurl, nil, form) + return resp.status == 303 + and (resp.header["location"] or ""):find("/omp%?.*%f[^?&]token=") + end +}) + +table.insert(fingerprints, { + name = "Sagitta Hashstack", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local lurl = (response.header["location"] or ""):gsub("^https?://[^/]*", "") + if not (response.status == 302 and lurl:find("/login$")) then + return false + end + local resp = http_get_simple(host, port, lurl) + return resp.status == 200 + and resp.body + and resp.body:find("hashstack", 1, true) + and resp.body:lower():find("hashstack - login", 1, true) + and get_tag(resp.body, "form", {class="^form%-signin$"}) + 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"} + local jin = {username=user, 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 get_cookie(resp, "sid", ".") + end +}) + +table.insert(fingerprints, { + name = "ZKSoftware WebServer", + category = "web", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "ZK Web Server" + and response.body + and response.body:find("%Wlocation%.href%s*=%s*(['\"])[^'\"]-/csl/login%1") + end, + login_combos = { + {username = "administrator", password = "123456"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200) then return false end + local resp2 = http_post_simple(host, port, url.absolute(path, "csl/check"), + {cookies=resp1.cookies}, + {username=user, userpwd=pass}) + return resp2.status == 200 + and get_tag(resp2.body or "", "frame", {src="/csl/menu$"}) + end +}) + +table.insert(fingerprints, { + name = "ComfortableMexicanSofa", + category = "web", + paths = { + {path = "/admin/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 302 and response.body) then return false end + local loc = response.header["location"] or "" + local _, pos = loc:find(url.absolute(path, "sites/"), 1, true) + if not pos then return false end + loc = loc:sub(pos) + if not (loc == "/new" or loc:find("^/%d+/")) then return false end + for _, ck in ipairs(response.cookies or {}) do + if ck.name:find("_session$") then return ck.value:find("%-%-%x+$") end + end + return false + end, + login_combos = { + {username = "username", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "sites/new"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Hippo CMS", + category = "web", + paths = { + {path = "/"}, + {path = "/cms/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("hippo-login", 1, true) + and get_tag(response.body, "input", {name="^id2_hf_0$"}) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "editor", password = "editor"}, + {username = "author", password = "author"} + }, + login_check = function (host, port, path, user, pass) + local lurl; + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local submit = get_tag(resp1.body, "input", {name="^:submit$", onclick=""}) + if submit then + local qry = submit.onclick:match("=%s*wicketSubmitFormById%(['\"]id%d+['\"],%s*['\"](.-)['\"]") + if not qry then return false end + lurl = xmldecode(qry) .. "&random=" .. math.random() + else + local frm = get_tag(resp1.body, "form", {name="^signInForm$", action=""}) + if not frm then return false end + lurl = frm.action + end + local form = {id2_hf_0="", + username=user, + password=pass, + locale="en", + [":submit"]="log in"} + local resp2 = http_post_simple(host, port, url.absolute(path, lurl), + {cookies=resp1.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):sub(-#path) == path + end +}) + +--- +--ROUTERS +--- +table.insert(fingerprints, { + name = "Cisco IOS", + cpe = "cpe:/o:cisco:ios", + category = "routers", + paths = { + {path = "/"}, + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + 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_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Cisco Small Business 200", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/nikola_login.html", 1, true) + and response.body:lower():find("switch", 1, true) + end, + login_combos = { + {username = "cisco", password = "cisco"} + }, + login_check = function (host, port, path, user, pass) + local form = {uname=user, + pwd2=base64.enc(pass), + language_selector="en-US", + err_flag=0, + err_msg="", + passpage="nikola_main2.html", + failpage="nikola_login.html", + submit_flag=0} + local resp = http_post_simple(host, port, + url.absolute(path, "nikola_login.html"), + nil, form) + return resp.status == 200 and get_cookie(resp, "SID", ".") + end +}) + +table.insert(fingerprints, { + name = "Cisco Linksys", + cpe = "cpe:/h:linksys:*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + return realm:find("^Linksys %u[%u%d]+%s*$") + or realm:find("^WRT54GC%w*$") + or realm == "NR041" + end, + login_combos = { + {username = "", password = "admin"}, + {username = "admin", password = "admin"}, + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Cisco DPC3848VM", + cpe = "cpe:/h:cisco:dpc3848vm", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and response.header["location"] == "Docsis_system.php" + end, + login_combos = { + {username = "user", password = ""}, + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {username_login=user, + password_login=pass, + LanguageSelect="en", + login="Log In"} + local resp = http_post_simple(host, port, url.absolute(path, "check.php"), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local lstatus = resp.body:match("%Wvar%s+login_status%s*=%s*(%-?%d+)") + return tonumber(lstatus or "99") <= 0 + end +}) + +table.insert(fingerprints, { + name = "Cisco EPC3925", + cpe = "cpe:/h:cisco:epc3925", + 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("%Wwindow%.location%.href%s*=%s*(['\"])Docsis_system%.asp%1") + end, + login_combos = { + {username = "", password = ""} + }, + 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) + return resp.status == 302 + and (resp.header["location"] or ""):find("/Quick_setup%.asp$") + end +}) + +table.insert(fingerprints, { + name = "Cisco Configuration Utility (var.1)", + 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("cisco", 1, true) + and response.body:find("%Wfunction%s+en_value%s*%(") + and get_tag(response.body, "input", {name="^keep_name$"}) + end, + login_combos = { + {username = "cisco", password = "cisco"} + }, + login_check = function (host, port, path, user, pass) + pass = ("%s%02d"):format(pass, #pass) + pass = pass:rep(math.ceil(64 / #pass)):sub(1, 64) + local form = {submit_button="login", + keep_name=0, + enc=1, + user=user, + pwd=stdnse.tohex(openssl.md5(pass))} + local resp = http_post_simple(host, port, url.absolute(path, "login.cgi"), + nil, form) + return resp.status == 200 + and (resp.body or ""):find("%Wvar%s+session_key%s*=%s*(['\"])%x*%1%s*;") + end +}) + +table.insert(fingerprints, { + name = "Cisco Configuration Utility (var.2)", + 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("cisco", 1, true) + and response.body:find("%Wfunction%s+en_value%s*%(") + and get_tag(response.body, "input", {name="^gui_action$"}) + end, + login_combos = { + {username = "cisco", password = "cisco"} + }, + login_check = function (host, port, path, user, pass) + pass = ("%s%02d"):format(pass, #pass) + pass = pass:rep(math.ceil(64 / #pass)):sub(1, 64) + local form = {submit_button="login", + submit_type="", + gui_action="", + wait_time=0, + change_action="", + enc=1, + user=user, + pwd=stdnse.tohex(openssl.md5(pass)), + sel_lang="EN"} + local resp = http_post_simple(host, port, url.absolute(path, "login.cgi"), + nil, form) + return resp.status == 200 + and get_tag(resp.body or "", "input", {name="^session_key$", value="^%x+$"}) + end +}) + +table.insert(fingerprints, { + name = "Cisco Router Access", + 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("%Wvar%s+nonce%s*=%s*(['\"])%x+%1") + and response.body:find("%Wfunction%s+en_value%s*%(") + and get_tag(response.body, "input", {name="^gui_action$"}) + end, + login_combos = { + {username = "", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local nonce = resp1.body:match("%Wvar%s+nonce%s*=%s*['\"](%x+)['\"]") + if not nonce then return false end + pass = ("%s%02d"):format(pass, #pass) + pass = pass:rep(math.ceil(64 / #pass)):sub(1, 64) + pass = stdnse.tohex(openssl.md5(pass)) + local wait_time = get_tag(resp1.body, "input", {name="^wait_time$"}) + local form = {submit_button="login", + change_action="", + gui_action="Apply", + wait_time=wait_time and wait_time.value or "", + submit_type="", + http_username=user, + http_passwd=stdnse.tohex(openssl.md5(pass .. nonce))} + local resp2 = http_post_simple(host, port, url.absolute(path, "login.cgi"), + nil, form) + return resp2.status == 200 + and (resp2.body or ""):find(";session_id=%x+%W") + end +}) + +table.insert(fingerprints, { + name = "Cisco IronPort", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 303 + and (response.header["server"] or ""):find("^glass/%d+%.") + and (response.header["location"] or ""):find("/login%f[?\0]") + and get_cookie(response, "sid", "^%w+$") + end, + login_combos = { + {username = "admin", password = "ironport"} + }, + login_check = function (host, port, path, user, pass) + local refpath = url.absolute(path, "default") + local form = {referrer=url.build(url_build_defaults(host, port, {path=refpath})), + screen="login", + username=user, + password=pass, + action="Login"} + local resp = http_post_simple(host, port, url.absolute(path, "login"), + nil, form) + return resp.status == 303 + and (get_cookie(resp, "euq_authenticated", "^%w+$") + or get_cookie(resp, "authenticated", "^%w+$")) + end +}) + +table.insert(fingerprints, { + name = "Allied Telesis AR", + cpe = "cpe:/h:alliedtelesyn:cable_dsl_router_at-ar*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + return realm:find("^Allied Telesis ") + or realm:find("^Allied Telesyn ") + or realm:find("^CentreCOM ") + end, + login_combos = { + {username = "manager", password = "friend"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "HP ProCurve Switch", + cpe = "cpe:/h:hp:procurve_switch", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):lower():find("^ehttp[/%s]") + and response.body + and response.body:find("ProCurve Switch", 1, true) + and (response.body:find("%Wdocument%.location%s*=%s*(['\"])home%.html%1") + or get_tag(response.body, "frame", {src="^nctabs%.html$"})) + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "security/web_access.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Huawei USG", + cpe = "cpe:/h:huawei:usg*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and get_cookie(response, "SESSIONID", "&Huawei") + end, + login_combos = { + {username = "admin", password = "Admin@123"}, + {username = "audit-admin", password = "Admin@123"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + local cookie + for _, ck in ipairs(resp1.cookies or {}) do + if ck.name == "SESSIONID" then + cookie = "SESSIONID=" .. ck.value + if not ck.httponly then + cookie = cookie:match("^(.-)&") + end + break + end + end + if not (resp1.status == 200 and cookie) then return false end + local form = {["spring-security-redirect"]="", + password=pass, + language="en", + lang="English", + username=user, + platcontent=""} + local lurl = url.absolute(path, "default.html?dc=" .. math.floor(stdnse.clock_ms())) + local resp2 = http_post_simple(host, port, lurl, {cookies=cookie}, form) + return resp2.status == 200 + and (resp2.body or ""):find("top.location.replace(localHref)", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Moxa AirWorks", + category = "routers", + paths = { + {path = "/Login.asp"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Moxa AWK", 1, true) + and response.body:find("/webNonce%W") + and get_tag(response.body, "form", {action="/home%.asp$"}) + end, + login_combos = { + {username = "admin", password = "root"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, url.absolute(path, "Login.asp")) + if not (resp1.status == 200 and resp1.body) then return false end + local pcookie = resp1.body:match("%Wfunction%s+SetCookie%W[^}]-theName%s*=%s*['\"](.-)[='\"]") + if not pcookie then return false end + local form2 = {user=user, time=math.floor(stdnse.clock_ms())} + local url2 = url.absolute(path, "webNonce?" .. url.build_query(form2)) + local resp2 = http_get_simple(host, port, url2, + {cookies={{name=pcookie, value=""}}}) + if not (resp2.status == 200 and resp2.body) then return false end + local cpass = stdnse.tohex(openssl.md5(pass .. resp2.body)) + local form3 = {Username=user, + Password="", + ["Submit.x"]=0, + ["Submit.y"]=0} + local resp3 = http_post_simple(host, port, url.absolute(path, "home.asp"), + {cookies={{name=pcookie, value=cpass}}}, + form3) + return resp3.status == 200 + and get_tag(resp3.body or "", "frame", {src="^main%.asp$"}) + end +}) + +table.insert(fingerprints, { + name = "Moxa EDR (var.1)", + cpe = "cpe:/o:moxa:edr_g903_firmware", + category = "routers", + paths = { + {path = "/Login.asp"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Moxa EDR", 1, true) + and response.body:find(">iGenSel2%((['\"])Username%1") + and response.body:find("%Wdocument%.getElementById%(%s*(['\"])Username%1%s*%)%.value%s*%+%s*(['\"]):%2") + end, + login_combos = { + {username = "admin", password = ""}, + {username = "user", password = ""} + }, + login_check = function (host, port, path, user, pass) + local cpass = stdnse.tohex(openssl.md5(#pass > 0 and pass or "NULL")) + local cookies = {{name="admin:EDR", value=(user=="admin" and cpass or "")}, + {name="user:EDR", value=(user=="user" and cpass or "")}} + local form1 = {Username=user, + Password=pass, + ["Submit.x"]=0, + ["Submit.y"]=0} + local resp1 = http_post_simple(host, port, url.absolute(path, "init.asp"), + {cookies=cookies}, form1) + if resp1.status~=200 then return false end + local resp2 = http_get_simple(host, port, url.absolute(path, "index.asp"), + {cookies=cookies}) + return resp2.status == 200 + and get_tag(resp2.body or "", "frame", {src="^name%.asp$"}) +end +}) + +table.insert(fingerprints, { + name = "Moxa EDR (var.2)", + cpe = "cpe:/o:moxa:edr_g903_firmware", + category = "routers", + paths = { + {path = "/Login.asp"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Moxa EDR", 1, true) + and response.body:find(">iGenSel2%((['\"])Username%1") + and response.body:find("%Wdocument%.getElementById%(%s*(['\"])Username%1%s*%)%.value%s*;") + end, + login_combos = { + {username = "admin", password = ""}, + {username = "user", password = ""} + }, + login_check = function (host, port, path, user, pass) + local cuser = #user > 0 and user or "unknown" + local cpass = #pass > 0 and pass or "NULL" + local cookies = {{name="NAME", value=url.escape(cuser)}, + {name="PASSWORD", value=stdnse.tohex(openssl.md5(cpass))}} + local form1 = {Username=user, + Password=pass, + ["Submit.x"]=0, + ["Submit.y"]=0} + local resp1 = http_post_simple(host, port, url.absolute(path, "init.asp"), + {cookies=cookies}, form1) + if resp1.status~=200 then return false end + local resp2 = http_get_simple(host, port, url.absolute(path, "home.asp"), + {cookies=cookies}) + return resp2.status == 200 + and get_tag(resp2.body or "", "frame", {src="^name%.asp$"}) +end +}) + +table.insert(fingerprints, { + name = "Moxa EDR (var.3)", + cpe = "cpe:/o:moxa:edr_g903_firmware", + category = "routers", + paths = { + {path = "/Login.asp"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Moxa EDR", 1, true) + and response.body:find("%Wdocument%.getElementById%(%s*(['\"])InputPassword%1%s*%)%.action%s*=%s*(['\"])[^'\"]-/init%.asp%2") + and not response.body:find("sysnotify_support", 1, true) + and response.body:find("%Wvar%s+rndN%s*=%s*%d+%s*;") + end, + login_combos = { + {username = "admin", password = "moxa"}, + {username = "user", password = "moxa"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, url.absolute(path, "Login.asp")) + if not (resp1.status == 200 and resp1.body) then return false end + local nonce = resp1.body:match("%Wvar%s+rndN%s*=%s*(%d+)%s*;") + if not nonce then return false end + local cuser = #user > 0 and user or "unknown" + local cpass = pass .. nonce + local cookies = {{name="NAME", value=url.escape(cuser)}, + {name="PASSWORD", value=stdnse.tohex(openssl.md5(cpass))}} + local form2 = {Username=user, + Password=pass, + ["Submit.x"]=0, + ["Submit.y"]=0} + local resp2 = http_post_simple(host, port, url.absolute(path, "init.asp"), + {cookies=cookies}, form2) + if resp2.status~=200 then return false end + local resp3 = http_get_simple(host, port, url.absolute(path, "home.asp"), + {cookies=cookies}) + return resp3.status == 200 + and get_tag(resp3.body or "", "frame", {src="^name%.asp$"}) + end +}) + +table.insert(fingerprints, { + name = "Moxa EDR (var.4)", + category = "routers", + paths = { + {path = "/Login.asp"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Moxa EDR", 1, true) + and response.body:find("%Wdocument%.getElementById%(%s*(['\"])InputPassword%1%s*%)%.action%s*=%s*(['\"])[^'\"]-/init%.asp%2") + and not response.body:find("sysnotify_support", 1, true) + and not response.body:find("%Wvar%s+rndN%s*=%s*%d+%s*;") + end, + login_combos = { + {username = "admin", password = "moxa"}, + {username = "user", password = "moxa"} + }, + login_check = function (host, port, path, user, pass) + local cuser = #user > 0 and user or "unknown" + local cpass = #pass > 0 and pass or "NULL" + local cookies = {{name="NAME", value=url.escape(cuser)}, + {name="PASSWORD", value=stdnse.tohex(openssl.md5(cpass))}} + local form1 = {Username=user, + Password=pass, + ["Submit.x"]=0, + ["Submit.y"]=0} + local resp1 = http_post_simple(host, port, url.absolute(path, "init.asp"), + {cookies=cookies}, form1) + if resp1.status~=200 then return false end + local resp2 = http_get_simple(host, port, url.absolute(path, "home.asp"), + {cookies=cookies}) + return resp2.status == 200 + and get_tag(resp2.body or "", "frame", {src="^name%.asp$"}) + end +}) + +table.insert(fingerprints, { + name = "Moxa EDR (var.5)", + cpe = "cpe:/o:moxa:edr_g903_firmware", + category = "routers", + paths = { + {path = "/Login.asp"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Moxa EDR", 1, true) + and response.body:find("%Wdocument%.getElementById%(%s*(['\"])InputPassword%1%s*%)%.action%s*=%s*(['\"])[^'\"]-/init%.asp%2") + and response.body:find("sysnotify_support", 1, true) + end, + login_combos = { + {username = "admin", password = "moxa"}, + {username = "user", password = "moxa"} + }, + login_check = function (host, port, path, user, pass) + local cuser = #user > 0 and user or "unknown" + local cpass = #pass > 0 and pass or "NULL" + local cookies = {{name="sysnotify_support", value="yes"}, + {name="sysnotify_loginStatus", value="initial"}, + {name="lasttime", value=tostring(math.floor(stdnse.clock_ms()))}, + {name="sessionID", value=tostring(math.random(1000000000, 4294967295))}, + {name="NAME", value=url.escape(cuser)}, + {name="PASSWORD", value=stdnse.tohex(openssl.md5(cpass))}, + {name="AUTHORITY", value=""}} + local form = {Username=user, + Password=pass, + ["Submit.x"]=0, + ["Submit.y"]=0} + local resp = http_post_simple(host, port, url.absolute(path, "init.asp"), + {cookies=cookies}, form) + return resp.status == 200 + and (resp.body or ""):find("%sonLoad%s*=%s*['\"]SetAuthorityCookie%(") + end +}) + +table.insert(fingerprints, { + name = "Ovislink AirLive (basic auth)", + cpe = "cpe:/h:ovislink:airlive_*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + return realm:find("^AirLive ") + or realm:find("%f[%w]admin/airlive$") + or realm:find("%f[%w]airlive/airlive$") + end, + login_combos = { + {username = "admin", password = "airlive"}, + {username = "airlive", password = "airlive"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Ovislink AirLive AP", + cpe = "cpe:/h:ovislink:airlive_*", + category = "routers", + paths = { + {path = "/index.asp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("AirLive", 1, true) + and response.body:lower():find("airlive [%w-]+") + and response.body:lower():find("%shref%s*=%s*(['\"]?)sts_%w+%.asp%1[%s>]") + end, + login_combos = { + {username = "", password = "airlive"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "goform/asp_login"), + nil, {psw=pass}) + return resp.status == 302 + and (resp.header["location"] or ""):find("/sts_%w+%.asp$") + end +}) + +table.insert(fingerprints, { + name = "Ovislink AirLive WIAS (var.1)", + cpe = "cpe:/h:ovislink:airlive_*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("WIAS", 1, true) + and response.body:lower():find("wias%-%d+%a") + and get_tag(response.body, "form", {action="^check%.shtml$"}) + and get_tag(response.body, "input", {name="^password$"}) + end, + login_combos = { + {username = "admin", password = "airlive"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "check.shtml"), + nil, {username=user,password=pass}) + return resp.status == 302 + and resp.header["location"] == "home.shtml" + end +}) + +table.insert(fingerprints, { + name = "Ovislink AirLive WIAS (var.2)", + cpe = "cpe:/h:ovislink:airlive_*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("AirLive", 1, true) + and response.body:lower():find("airlive wias%-%d+%a") + and get_tag(response.body, "form", {action="^check%.shtml$"}) + and get_tag(response.body, "input", {name="^adm_pwd$"}) + end, + login_combos = { + {username = "admin", password = "airlive"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "check.shtml"), + nil, {adm_name=user,adm_pwd=pass}) + return resp.status == 302 + and resp.header["location"] == "home.shtml" + end +}) + +table.insert(fingerprints, { + name = "AirTies router", + cpe = "cpe:/h:airties:air_*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and get_refresh_url(response.body, "/js/%.js_check%.html$") + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {redirect="", + self="", + user=user, + password=pass, + gonder="OK"} + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/login"), + nil, form) + return resp.status == 200 + and get_cookie(resp, "AIRTIESSESSION", "^%x+$") + and get_refresh_url(resp.body or "", "/main%.html$") + end +}) + +table.insert(fingerprints, { + name = "Arris Touchstone", + cpe = "cpe:/a:arris:touchstone_*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("sta_wifi", 1, true) + and get_tag(response.body, "form", {action="^check%.php$"}) + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "check.php"), + nil, {username=user,password=pass}) + return resp.status == 200 + and get_cookie(resp, "PHPSESSID", "^%w+$") + and (resp.body or ""):find("%Wlocation%.href%s*=%s*(['\"])admin_password_change%.php%1") + end +}) + +table.insert(fingerprints, { + name = "ASUS TM router", + cpe = "cpe:/h:asus:tm-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^TM%-%u[%u%d]+$") + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "ASUS router", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) + if not realm then return false end + local type = realm:match("^(%u+)%-%u[%u%d]+$") + for t in ("DSL,EA,RP,RT,TM"):gmatch("%u+") do + if t == type then return true end + end + return false + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "ASUS RX3041", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^ *RX3041%f[ \0]") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + 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, + {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, { + name = "Belkin/Arris 2307", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("isAPmode", 1, true) + and get_tag(response.body, "meta", {name="^description$", content="^%w+ 2307$"}) + 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 or ""):find("index.html", 1, true) + end +}) + +table.insert(fingerprints, { + name = "D-Link DIR router (var.1)", + cpe = "cpe:/h:d-link:dir-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find(" DIR%-%d+") + and response.body + and response.body:find("AUTH.Login(", 1, true) + and response.body:find('%WOBJ%("loginusr"%)%.value%s*=%s*""') + and response.body:lower():find("d%-link systems[^<]+ home") + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {REPORT_METHOD="xml", + ACTION="login_plaintext", + USER=user, + PASSWD=pass, + CAPTCHA=""} + local resp = http_post_simple(host, port, url.absolute(path, "session.cgi"), + {cookies="uid="..random_alnum(10)}, form) + return resp.status == 200 + and (resp.body or ""):find("SUCCESS", 1, true) + end +}) + +table.insert(fingerprints, { + name = "D-Link DIR router (var.2)", + cpe = "cpe:/h:d-link:dir-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find(" DIR%-%d+") + and response.body + and response.body:find("AUTH.Login(", 1, true) + and response.body:find('%WOBJ%("loginusr"%)%.value%s*=%s*username%W') + and response.body:lower():find("d%-link systems[^<]+ home") + end, + login_combos = { + {username = "Admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {REPORT_METHOD="xml", + ACTION="login_plaintext", + USER=user, + PASSWD=pass, + CAPTCHA=""} + local resp = http_post_simple(host, port, url.absolute(path, "session.cgi"), + {cookies="uid="..random_alnum(10)}, form) + return resp.status == 200 + and (resp.body or ""):find("SUCCESS", 1, true) + end +}) + +table.insert(fingerprints, { + name = "D-Link DIR router (var.3)", + cpe = "cpe:/h:d-link:dir-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and (response.header["server"] or ""):find(" DIR%-%d+") + and response.body + and response.body:find("AUTH.Login_Hash(", 1, true) + and response.body:lower():find("d%-link systems[^<]+ home") + end, + login_combos = { + {username = "Admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local url2 = url.absolute(path, "authentication.cgi") + local url1 = url2 .. "?captcha=&dummy=" .. math.floor(stdnse.clock_ms()) + local resp1 = http_get_simple(host, port, url1) + if not (resp1.status == 200 and resp1.body) then return false end + local jstatus, jout = json.parse(resp1.body) + if not (jstatus and jout.uid and jout.challenge) then return false end + local auth = stdnse.tohex(openssl.hmac("MD5", pass, user .. jout.challenge)) + local resp2 = http_post_simple(host, port, url2, + {cookies = "uid=" .. jout.uid}, + {id=user, password=auth:upper()}) + if not (resp2.status == 200 and resp2.body) then return false end + jstatus, jout = json.parse(resp2.body) + return jstatus and jout.status == "ok" + end +}) + +table.insert(fingerprints, { + name = "D-Link DIR-620", + cpe = "cpe:/h:d-link:dir-620", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("DIR-620", 1, true) + and response.body:lower():find("dir-620", 1, true) + and get_tag(response.body, "form", {action="^index%.cgi$"}) + end, + login_combos = { + {username = "admin", password = "anonymous"} + }, + login_check = function (host, port, path, user, pass) + local cookies = {{name="user_ip", value="127.0.0.1"}, + {name="cookie_lang", value="rus"}, + {name="client_login", value=user}, + {name="client_password", value=pass}} + local resp = http_post_simple(host, port, url.absolute(path, "index.cgi"), + {cookies=cookies}, + {v2="y",rs_type="html",auth="auth"}) + return resp.status == 200 + and (resp.body or ""):find("%sid%s*=%s*(['\"])v_firmware_value%1%s*>%d") + end +}) + +table.insert(fingerprints, { + name = "D-Link DIR router (basic auth)", + cpe = "cpe:/h:d-link:dir-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("%f[%w]DIR%-%d%d%d%f[%u\0]") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "D-Link DSL router", + cpe = "cpe:/h:d-link:dsl-*", + category = "routers", + 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("%Wwindow%.location%.href%s*=%s*(['\"])[^'\"]-/cgi%-bin/webproc%1") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = "password"}, + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "cgi-bin/webproc") + local resp1 = http_get_simple(host, port, lurl) + if not (resp1.status == 200) then return false end + local form = {getpage="html/index.html", + errorpage="html/main.html", + ["var:menu"]="setup", + ["var:page"]="wizard", + ["obj-action"]="auth", + [":username"]=user, + [":password"]=pass, + [":action"]="login", + [":sessionid"]=get_cookie(resp1, "sessionid")} + local resp2 = http_post_simple(host, port, lurl, + {cookies=resp1.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/cgi-bin/webproc?getpage=html/index.html&", 1, true) + end +}) + +table.insert(fingerprints, { + name = "D-Link DSL router (basic auth)", + cpe = "cpe:/h:d-link:dsl-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^DSL%-%d%d%d%d?[BRU]%f[_\0]") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "support", password = "support"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "D-Link DSL T router (basic auth)", + cpe = "cpe:/h:d-link:dsl-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("%f[^ \0]DSL%-%d%d%d%d?T$") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "user", password = "user"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "TP-Link (basic auth)", + cpe = "cpe:/o:tp-link:lm_firmware", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 401 + and (http_auth_realm(response) or ""):find("^TP%-LINK") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "TP-Link (MD5 cookie)", + cpe = "cpe:/o:tp-link:lm_firmware", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and (http_auth_realm(response) or ""):find("^TP%-LINK") + and response.body + and response.body:find("%spassword%s*=%s*hex_md5") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local auth = base64.enc(user .. ":" .. stdnse.tohex(openssl.md5(pass))) + local cookie = "Authorization=" .. url.escape("Basic " .. auth) + local resp = http_get_simple(host, port, + url.absolute(path, "userRpm/LoginRpm.htm?Save=Save"), + {cookies=cookie}) + return resp.status == 200 + and (resp.body or ""):find(">window%.parent%.location%.href%s*=%s*(['\"])[^'\"]-/userRpm/Index%.htm%1") + end +}) + +table.insert(fingerprints, { + name = "TP-Link (plain cookie)", + cpe = "cpe:/o:tp-link:lm_firmware", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (http_auth_realm(response) or ""):find("^TP%-LINK") + and response.body + and not response.body:find("%spassword%s*=%s*hex_md5") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local auth = base64.enc(user .. ":" .. pass) + local cookie = "Authorization=" .. url.escape("Basic " .. auth) + local resp = http_get_simple(host, port, path, {cookies=cookie}) + return resp.status == 200 + and (resp.body or ""):find("%shref%s*=%s*(['\"])[^'\"]-/userRpm/LogoutRpm%.htm%1") + end +}) + +table.insert(fingerprints, { + 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_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "iBall Baton", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^iBall Baton ") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "support", password = "support"}, + {username = "user", password = "user"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Link-Net LW/LWH router", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 302 + and (response.header["location"] or ""):find("/home%.asp$")) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "home.asp")) + return resp.status == 200 + and resp.body + and resp.body:find("LINK-NET", 1, true) + and resp.body:find("%svendor%s*=%s*(['\"])LINK%-NET%1") + and resp.body:lower():find("[%s>]wireless router") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "internet/wan.asp"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Planex Broad Lanner", + cpe = "cpe:/h:planex:brl-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Planex Communications", 1, true) + and get_tag(response.body, "meta", {content="^B%a%a%-04FM%a HTML"}) + and get_tag(response.body, "frame", {src="^top%.htm$"}) + end, + login_combos = { + {username = "", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "top.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "TrendChip ADSL Modem", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "ADSL Modem" + and (response.header["server"] or ""):find("^Boa/%d+%.") + and get_cookie(response, "SESSIONID", "^%x+$") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = "1234"}, + {username = ("qwertyuiop"):rep(13):sub(1, 128), + password = ("1234567890"):rep(13):sub(1, 128)}, + {username = "user3", + password = ("1234567890"):rep(13):sub(1, 128)}, + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not resp1.status then return false end + local auth = {username = user, password = pass} + local resp2 = http_get_simple(host, port, path, + {auth=auth, cookies=resp1.cookies}) + return resp2.status == 200 + end +}) + +table.insert(fingerprints, { + name = "Westell", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/htmlV/PasswordChange%.asp$") + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "htmlV/PasswordChange.asp"), + user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Yamaha RT 10.x", + cpe = "cpe:/o:yahama:rt*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local lurl = response.status == 200 + and get_refresh_url(response.body or "", "/user/index[_%a]*.html$") + if not lurl then return false end + local resp = http_get_simple(host, port, lurl) + return (http_auth_realm(resp) or ""):find("^YAMAHA%-RT ") + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp = http_get_simple(host, port, path) + local lurl = resp.status == 200 + and get_refresh_url(resp.body or "", "/user/index[_%a]*.html$") + if not lurl then return false end + return try_http_auth(host, port, lurl, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Yamaha RT 11.x", + cpe = "cpe:/o:yahama:rt*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^YAMAHA%-RT ") + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Yamaha SWX", + category = "routers", + paths = { + {path = "/login.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Yamaha Corporation", 1, true) + and get_tag(response.body, "form", {action="/goform/authenticate%.json$"}) + and get_tag(response.body, "input", {name="^URL$", value="/dashboard/index%.html$"}) + end, + login_combos = { + {username="", password=""} + }, + login_check = function (host, port, path, user, pass) + local form = {URL=url.absolute(path, "/dashboard/index.html"), + USER=user, + PASS=pass} + local resp = http_post_simple(host, port, + url.absolute(path, "goform/authenticate.json"), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.result == "SUCCESS" + end +}) + +table.insert(fingerprints, { + 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_auth(host, port, url.absolute(path, "hag/pages/home.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "ZTE F660", + cpe = "cpe:/h:zte:f660", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("ZTE", 1, true) + and response.body:lower():find("f660", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local ltoken = resp1.body:match("%WgetObj%(%s*['\"]Frm_Logintoken['\"]%s*%)%.value%s*=%s*['\"](%d+)['\"]%s*;") + if not ltoken then return false end + local form = {frashnum="", + action="login", + Frm_Logintoken=ltoken, + Username=user, + Password=pass} + local resp2 = http_post_simple(host, port, path, {cookies=resp1.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/start%.ghtml$") + end +}) + +table.insert(fingerprints, { + name = "ZTE ZXV10 I5xx", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("ZTE", 1, true) + and get_tag(response.body, "form", {name="^flogin$", action="^getpage%.gch%?pid=1001$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local stime = resp1.body:match("%Wdocument%.getElementById%(%s*['\"]submittime['\"]%s*%)%.value%s*=%s*['\"](%d+)['\"]%s*;") + if not stime then return false end + local form = {submenu=-1, + menuPos=-1, + nosubmenu=1, + nextpage="welcome.gch", + nextgch="", + nextjs="welcome.js", + title="Come In to Configuration", + path="Welcome", + submittime=stime, + tUsername=user, + tPassword=pass} + local resp2 = http_post_simple(host, port, + url.absolute(path, "getpage.gch?pid=1001"), + nil, form) + return resp2.status == 200 + and (resp2.body or ""):lower():find("[^<]-configuration") + end +}) + +table.insert(fingerprints, { + name = "ZTE ZXV10 W300", + cpe = "cpe:/o:zte:zxv10_w300_firmware", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^ZXV10 W300$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "3Com OfficeConnect VPN Firewall", + cpe = "cpe:/h:3com:3cr870-95", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("3Com", 1, true) + and response.body:find("%Wtop%.document%.location%s*=%s*(['\"])[^'\"]-/default%.htm%1") + and get_tag(response.body, "meta", {["http-equiv"]="^3cnumber$"}) + end, + login_combos = { + {username = "", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/admin?page=x"), + nil, {AdminPassword=pass,next=10,page="x"}) + return resp.status == 200 + and get_tag(resp.body or "", "input", {name="^tk$"}) + end +}) + +table.insert(fingerprints, { + name = "Corega", + cpe = "cpe:/o:corega:cg-*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + return realm:find("^CG%-%u*BAR") + or realm:find("^corega BAR ") + end, + login_combos = { + {username = "root", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + 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 get_tag(response.body, "frame", {src="^top%.html$"}) + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "top.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Netgear Router (legacy)", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^R[PT][13]1[14]$") + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Netgear Router", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + return realm:find("^NETGEAR %u+%d+[%w-]+%s*$") + or realm == "Netgear" + or realm == "FR114P" + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + 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 get_cookie(resp, "GS108SID", ".") + end +}) + +table.insert(fingerprints, { + name = "Netgear Smart Switch", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("document.forms[0].pwd.focus();", 1, true) + and response.body:lower():find("%saction%s*=%s*(['\"])[^'\"]-/base/%w+_login%.html%1") + and response.body:lower():find("<title>netgear ", 1, true) + end, + login_combos = { + {username = "", password = "password"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local lurl = resp1.body:match("['\"]([^'\"]-/base/%w+_login%.html)") + if not lurl then return false end + local button = lurl:find("main_login", 1, true) and "" or "_button" + local form = {pwd=pass, + ["login" .. button .. ".x"]=0, + ["login" .. button .. ".y"]=0, + err_flag=0, + err_msg=""} + local resp2 = http_post_simple(host, port, lurl, nil, form) + return resp2.status == 200 and get_cookie(resp2, "SID", ".") + end +}) + +table.insert(fingerprints, { + name = "Netgear Intelligent Edge", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("document.forms[0].uname.focus();", 1, true) + and response.body:lower():find("%saction%s*=%s*(['\"])[^'\"]-/base/%w+_login%.html%1") + and response.body:lower():find("<title>netgear ", 1, true) + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local lurl = resp1.body:match("['\"]([^'\"]-/base/%w+_login%.html)") + if not lurl then return false end + local form = {uname=user, + pwd=pass, + ["login_button.x"]=0, + ["login_button.y"]=0, + err_flag=0, + err_msg="", + submt=""} + local resp2 = http_post_simple(host, port, lurl, nil, form) + return resp2.status == 200 and get_cookie(resp2, "SID", ".") + end +}) + +table.insert(fingerprints, { + name = "Netgear Gigabit Enterprise Switch", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/base/web_main.html", 1, true) + and response.body:lower():find("<title>netgear system login", 1, true) + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "base/web_main.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "PLANET Smart Gigabit Switch", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">Welcome to PLANET ", 1, true) + and get_tag(response.body, "form", {action="/pass$"}) + end, + login_combos = { + {username = "", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {password=pass, + x=0, + y=0} + local resp = http_post_simple(host, port, url.absolute(path, "pass"), + nil, form) + if not (resp.status == 200 + and get_tag(resp.body or "", "frame", {src="/planet%.htm$"})) then + return false + end + http_get_simple(host, port, url.absolute(path, "logout?submit=Apply")) + return true + end +}) + +table.insert(fingerprints, { + name = "PLANET Managed Switch (var.1)", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local server = response.header["server"] or "" + return (http_auth_realm(response) or ""):find("^Loging?$") + and (server == "Vitesse Web Server" + or server == "WebServer") + and response.body + and response.body:find(">Authorization required to access this URL.<", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "PLANET Managed Switch (var.2)", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local loc = (response.header["location"] or ""):gsub("^https?://[^/]*", "") + if not (response.status == 302 + and loc:find("/default%.html$")) then + return false + end + local resp = http_get_simple(host, port, loc) + return resp.status == 200 + and resp.body + and resp.body:find("1366X768", 1, true) + and resp.body:lower():find("switch web management (1366x768 is recommended)", 1, true) + and get_tag(resp.body, "form", {action="/goform/WebSetting%.html$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {name=user, + pwd=pass, + app="login"} + local resp = http_post_simple(host, port, + url.absolute(path, "goform/WebSetting.html"), + nil, form) + return resp.status == 203 + and resp.body + and get_tag(resp.body, "frame", {src="/frontboard%.html$"}) + end +}) + +table.insert(fingerprints, { + name = "PLANET Managed Switch (var.3)", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/cgi-bin/get.cgi?cmd=portlink&lg=", 1, true) + and get_tag(response.body, "frame", {src="/cgi%-bin/get%.cgi%?cmd=portlink&lg=%w+$"}) + and response.body:lower():find("managed switch", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "cgi-bin/get.cgi?cmd=portlink&lg=en"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "PLANET Wireless Router", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("PLANET Technology", 1, true) + and response.body:find("(['\"])dataCenter%.js%1") + and response.body:find("%Wauth_action%s*:%s*(['\"])login%1") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {username=user, + password=base64.enc(pass:gsub("%s", "@")), + getPage="index.html", + action="Apply", + auth_action="login", + mode="AUTH", + _flg=0} + local resp = http_post_simple(host, port, + url.absolute(path, "postCenter.js"), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body:gsub("'", "\"")) + if not (jstatus and jout.result == "0") then return false end + http_get_simple(host, port, url.absolute(path, "login.html")) + return true + end +}) + +table.insert(fingerprints, { + name = "Rubytech chassis", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("fake_server.html", 1, true) + and get_tag(response.body, "form", {action="^fake_server%.html$"}) + and get_tag(response.body, "input", {name="^textpass$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = stdnse.output_table() + form.textuser=user + form.textpass=pass + form.Submit="Login" + form.randstr=math.random() + local resp = http_post_simple(host, port, + url.absolute(path, "fake_server.html"), + nil, form) + return resp.status == 200 + and (resp.body or ""):find("%Wlocation%.href%s*=%s*['\"][^'\"]-/main_frame%.html%?") + end +}) + +table.insert(fingerprints, { + name = "ZyXEL Prestige", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + return realm:find("^Prestige ") + or realm:find("^P[%u-]*645ME") + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "ZyXEL ZyWALL (var.1)", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and (response.header["server"] or ""):find("^RomPager/%d+%.") + and response.body + and response.body:find("rpAuth.html", 1, true) + and response.body:find("%WchangeURL%(%s*(['\"])[^'\"]-%f[%w]rpAuth%.html%1%s*%)") + end, + login_combos = { + {username = "", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + local form = {LoginPassword="ZyXEL ZyWALL Series", + hiddenPassword=stdnse.tohex(openssl.md5(pass)), + Prestige_Login="Login"} + local resp = http_post_simple(host, port, + url.absolute(path, "Forms/rpAuth_1"), + nil, form) + return resp.status == 303 + and (resp.header["location"] or ""):find("/passWarning%.html$") + end +}) + +table.insert(fingerprints, { + name = "ZyXEL ZyWALL (var.2)", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("ZyWALL", 1, true) + and response.body:lower():find("zywall %w") + and get_tag(response.body, "input", {name="^pwd_r$"}) + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + local form = {username=user, + pwd=pass, + pwd_r="", + password=pass} + local resp = http_post_simple(host, port, path, nil, form) + return resp.status == 302 + and resp.header["location"] == "ext-js/web-pages/login/chgpw.html" + and get_cookie(resp, "authtok", "^[%w+-]+$") + end +}) + +table.insert(fingerprints, { + name = "Adtran NetVanta", + cpe = "cpe:/h:adtran:netvanta_*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^NetVanta %d+%f[ \0]") + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Siemens Gigaset SX762/763", + cpe = "cpe:/h:siemens:gigaset_sx76*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 303 + and (response.header["server"] or ""):find("^SiemensGigaset%-Server/%d+%.") + and (response.header["location"] or ""):find("/UE/welcome_login%.html$") + end, + login_combos = { + {username = "", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {form_submission_type="login", + form_submission_parameter="", + current_page="welcome_login.html", + next_page="home_security.html", + i=1, + admin_role_name="administrator", + operator_role_name="operator", + subscriber_role_name="subscriber", + choose_role=0, + your_password=pass, + Login="OK"} + local resp = http_post_simple(host, port, + url.absolute(path, "UE/ProcessForm"), + nil, form) + return resp.status == 303 + and (resp.header["location"] or ""):find("/UE/home_security%.html$") + end +}) + +table.insert(fingerprints, { + name = "Siemens Scalance X-200", + cpe = "cpe:/o:siemens:scalance_x-200_series_firmware", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and get_cookie(response, "siemens_ad_session", "^%x+") + and response.body + and response.body:find(" SCALANCE X ", 1, true) + and get_tag(response.body, "input", {name="^nonceA$"}) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "user", password = "user"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local nonce = get_tag(resp1.body, "input", {name="^nonceA$", value="^%x+$"}) + if not nonce then return false end + local auth = stdnse.tohex(openssl.md5(table.concat({user, pass, nonce.value}, ":"))) + local resp2 = http_post_simple(host, port, path, {cookies=resp1.cookies}, + {encoded=user..":"..auth, nonceA=nonce.value}) + return resp2.status == 200 + and (resp2.body or ""):find("%Wlocation%.href%s*=%s*(['\"])index1%.html%1") + end +}) + +table.insert(fingerprints, { + name = "Siemens Scalance M873/M875", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^SCALANCE M%-?87%d%f[%D]") + end, + login_combos = { + {username = "admin", password = "scalance"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Siemens RUGGEDCOM WIN", + cpe = "cpe:/h:siemens:ruggedcom_win*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "" + and get_cookie(response, "sessionId", "^%d+$") + and (response.header["server"] or ""):find("^BS/%d+%.") + end, + login_combos = { + {username = "admin", password = "generic"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not get_cookie(resp1, "sessionId", "^%d+$") then return false end + local resp2 = http_get_simple(host, port, path, + {cookies=resp1.cookies, + auth={username=user,password=pass}}) + return resp2.status == 200 + and get_refresh_url(resp2.body, "/0/m%d+$") + end +}) + +table.insert(fingerprints, { + name = "Siemens RUGGEDCOM ROS (var.1)", + cpe = "cpe:/o:siemens:ruggedcom_rugged_operating_system", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local loc = (response.header["location"] or ""):gsub("^https?://[^/]*", "") + if not (response.status == 302 + and loc:find("/InitialPage%.asp$")) then + return false + end + local resp = http_get_simple(host, port, loc) + return resp.status == 200 + and resp.body + and resp.body:find("RuggedSwitch Operating System", 1, true) + and get_tag(resp.body, "a", {href="^Menu%.asp%?UID=%d+$"}) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "operator", password = "operator"}, + {username = "guest", password = "guest"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, + url.absolute(path, "InitialPage.asp")) + if not (resp1.status == 200 and resp1.body) then return false end + local llink = get_tag(resp1.body, "a", {href="^Menu%.asp%?UID=%d+$"}) + if not llink then return false end + local lurl = url.absolute(path, llink.href) + local resp2 = http_get_simple(host, port, lurl) + if resp2.status ~= 401 then return false end + return try_http_auth(host, port, lurl, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Siemens RUGGEDCOM ROS (var.2)", + cpe = "cpe:/o:siemens:ruggedcom_rugged_operating_system", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local loc = (response.header["location"] or ""):gsub("^https?://[^/]*", "") + if not (response.status == 302 + and loc:find("/InitialPage%.asp$")) then + return false + end + local resp = http_get_simple(host, port, loc) + return resp.status == 200 + and resp.body + and resp.body:find("goahead.gif", 1, true) + and resp.body:find("LogIn", 1, true) + and get_tag(resp.body, "form", {action="/goform/postLoginData%?UID=%d+$"}) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "operator", password = "operator"}, + {username = "guest", password = "guest"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, + url.absolute(path, "InitialPage.asp")) + if not (resp1.status == 200 and resp1.body) then return false end + local frm = get_tag(resp1.body, "form", {action="/goform/postLoginData%?UID=%d+$"}) + if not frm then return false end + local form = {User=user, + Password=pass, + choice="LogIn"} + local resp2 = http_post_simple(host, port, url.absolute(path, frm.action), + nil, form) + return (resp2.status == 203 or resp2.status == 200) + and get_tag(resp2.body or "", "a", {href="/logout%.asp%?uid=%d+$"}) + end +}) + +table.insert(fingerprints, { + name = "Siemens RUGGEDCOM ROX", + category = "routers", + paths = { + {path = "/login.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/skins/macified/styles/master.css", 1, true) + and response.body:find("confdLogin();", 1, true) + and get_tag(response.body, "a", {onclick="^confdlogin%(%);"}) + and get_tag(response.body, "body", {onload="^loadbannercontent%(%);"}) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "oper", password = "oper"}, + {username = "guest", password = "guest"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "confd/login"), + nil, {user=user,passwd=pass}) + return resp.status == 200 + and (resp.body or ""):find("^(['\"])sess%d+%1$") + end +}) + +table.insert(fingerprints, { + name = "VideoFlow DVP", + category = "routers", + paths = { + {path = "/login.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/skins/macified/styles/master.css", 1, true) + and response.body:find("confdLogin();", 1, true) + and get_tag(response.body, "a", {onclick="^confdlogin%(%);"}) + and get_tag(response.body, "body", {onload="^document%.form%.username%.focus%(%);"}) + end, + login_combos = { + {username = "root", password = "videoflow"}, + {username = "admin", password = "admin"}, + {username = "oper", password = "oper"}, + {username = "private", password = "private"}, + {username = "public", password = "public"}, + {username = "devel", password = "leved"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "confd/login"), + nil, {user=user,passwd=pass}) + return resp.status == 200 + and (resp.body or ""):find("^(['\"])sess%d+%1$") + end +}) + +table.insert(fingerprints, { + name = "Foxconn Femtocell", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("login.cgi", 1, true) + and get_tag(response.body, "form", {action="^cgi%-bin/login%.cgi$"}) + and response.body:lower():find("<title>femtocell management system", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local lurl = ("cgi-bin/login.cgi?username=%s&password=%s&Submit=Login"):format( + url.escape(user), url.escape(pass)) + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and get_cookie(resp, "sessionID", ".") + and (resp.body or ""):find("%Wwindow%.location%s*=%s*(['\"])mainFrame%.cgi%1") + end +}) + +table.insert(fingerprints, { + name = "Datum Systems SnIP", + cpe = "cpe:/o:datumsystems:snip", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^SnIP%d+$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Option GlobeSurfer II", + 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("GlobeSurfer II", 1, true) + and response.body:find("%Wf%.action%s*=%s*(['\"])[^'\"]-/cache/%d+/upgrade%.cgi%1") + and get_cookie(response, "session_id", "^%d+$") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local page2 = get_tag(resp1.body, "input", {name="^active_page$", value="^%d+$"}) + local url2 = resp1.body:match(".*%Wfunction%s+mimic_button%s*%([^}]-%Wcase%s+0%s*:[^}]-%Wf%.action%s*=%s*['\"]([^'\"]-/cache/%d+/index%.cgi)['\"]") + if not (page2 and url2) then return false end + local form2 = {active_page=page2.value, + prev_page=0, + page_title="Connection status", + nav_stack_0=page2.value, + mimic_button_field="sidebar: sidebar_logout..", + button_value="", + transaction_id=0} + local resp2 = http_post_simple(host, port, url2, + {cookies=resp1.cookies}, form2) + if not (resp2.status == 200 and resp2.body) then return false end + local authkey = get_tag(resp2.body, "input", {name="^auth_key$", value="^%d+$"}) + local transid = get_tag(resp2.body, "input", {name="^transaction_id$", value="^%d+$"}) + local page3 = get_tag(resp2.body, "input", {name="^active_page$", value="^%d+$"}) + local url3 = resp2.body:match(".*%Wfunction%s+mimic_button%s*%([^}]-%Wcase%s+0%s*:[^}]-%Wf%.action%s*=%s*['\"]([^'\"]-/cache/(%d+)/index%.cgi)['\"]") + if not (authkey and transid and page3 and url3) then return false end + local form3 = {active_page=page3.value, + prev_page=page2.value, + page_title="Login", + nav_stack_0=page3.value, + ["nav_" .. page3.value .. "_button_value"]="sidebar_logout", + mimic_button_field="submit_button_login_submit: ..", + button_value="sidebar_logout", + transaction_id=transid.value, + lang=0, + user_name=user, + ["password_" .. get_cookie(resp2, "session_id")]="", + md5_pass=stdnse.tohex(openssl.md5(pass .. authkey.value)), + auth_key=authkey.value} + local resp3 = http_post_simple(host, port, url3, + {cookies=resp2.cookies}, form3) + return resp3.status == 200 + and (resp3.body or ""):find("sidebar%5Fadvanced..", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Option GlobeSurfer III", + 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("md5_pass", 1, true) + and response.body:lower():find("[^<]-globesurfer%W") + and get_cookie(response, "rg_cookie_session_id", "^%d+$") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local authkey = get_tag(resp1.body, "input", {name="^auth_key$", value="^%d+$"}) + if not authkey then return false end + local form = {active_page="page_login", + prev_page="", + page_title="Login", + mimic_button_field="submit_button_login_submit: ..", + button_value="", + strip_page_top=0, + page_title_text="Login", + page_icon_number=30, + defval_lang=0, + defval_username="", + md5_pass=stdnse.tohex(openssl.md5(pass .. authkey.value)), + auth_key=authkey.value, + lang=0, + username=user, + ["password_" .. get_cookie(resp1, "rg_cookie_session_id")]=""} + local resp2 = http_post_simple(host, port, url.absolute(path, "index.cgi"), + {cookies=resp1.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("active%5fpage=page%5fhome", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Digi TransPort", + category = "routers", + paths = { + {path = "/login.asp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("TransPort WR", 1, true) + and response.body:lower():find("<title>transport wr", 1, true) + and get_cookie(response, "SID", "^%x+$") + end, + login_combos = { + {username = "username", password = "password"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.cookies) then return false end + local form = {username=user, + password=pass, + login="LOG IN"} + local resp2 = http_post_simple(host, port, path, + {cookies=resp1.cookies}, form) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/default%.asp$") + end +}) + +table.insert(fingerprints, { + name = "Sea Tel MXP", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Micro Digital Web Server" + and response.body + and response.body:find("MXP", 1, true) + and response.body:lower():find("%Wwindow%.location%.href%s*=%s*(['\"])login%.html%1") + end, + login_combos = { + {username = "Dealer", password = "seatel1"}, + {username = "SysAdmin", password = "seatel2"}, + {username = "User", password = "seatel3"} + }, + login_check = function (host, port, path, user, pass) + local form = {uId=user, + uPwd=pass, + uLoginMode="in", + callConter=0} + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/userValidate"), + nil, form) + return resp.status == 200 + and (resp.body or ""):find("^%s*%^true%s*$") + end +}) + +table.insert(fingerprints, { + name = "Thrane & Thrane Sailor 900 VSAT (var.1)", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and get_cookie(response, "tt_adm", "^%l+$") + and response.body + and get_tag(response.body, "form", {action="%?pageid=%w+$"}) + and get_tag(response.body, "input", {name="^pass_login$"}) + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local frm = get_tag(resp1.body, "form", {action="%?pageid=%w+$"}) + if not frm then return false end + local resp2 = http_post_simple(host, port, url.absolute(path, frm.action), + nil, {user_login=user,pass_login=pass}) + return resp2.status == 200 + and url.unescape(get_cookie(resp2, "tt_adm", "%%3[Aa]") or ""):find(":" .. user .. ":", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Thrane & Thrane Sailor 900 VSAT (var.2)", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and get_cookie(response, "tt_adm", "^%l+$") + and response.body + and response.body:find("900 VSAT", 1, true) + and get_tag(response.body, "a", {href="%?pageid=administration$"}) + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local llink = get_tag(resp1.body, "a", {href="%?pageid=administration$"}) + if not llink then return false end + local resp2 = http_post_simple(host, port, url.absolute(path, llink.href), + nil, {user_login=user,pass_login=pass}) + return resp2.status == 200 + and url.unescape(get_cookie(resp2, "tt_adm", "%%3[Aa]") or ""):find(":" .. user .. ":", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Ubiquiti AirOS", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 302 + and (response.header["location"] or ""):find("/cookiechecker?uri=/", 1, true)) then + return false + end + for _, ck in ipairs(response.cookies or {}) do + if ck.name == "AIROS_SESSIONID" or ck.name:find("^AIROS_%x+$") then + return ck.value:find("^%x+$") + end + end + return false + end, + login_combos = { + {username = "ubnt", password = "ubnt"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_multipart(host, port, + url.absolute(path, "login.cgi"), nil, + {uri=path, username=user, password=pass}) + return resp.status == 302 + and resp.header["location"] == path + end +}) + +table.insert(fingerprints, { + name = "Ubiquiti EdgeOS", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">EdgeOS<", 1, true) + and response.body:find("%WEDGE%.Config%s*=") + and response.body:lower():find("<title>edgeos") + end, + login_combos = { + {username = "ubnt", password = "ubnt"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, path, nil, + {username=user,password=pass}) + return (resp.status == 302 or resp.status == 303) + and (resp.header["location"] or ""):sub(-#path) == path + and get_cookie(resp, "PHPSESSID", "^%w+$") + end +}) + +table.insert(fingerprints, { + name = "Ubiquiti EdgeSwitch", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">Ubiquiti EdgeSwitch<", 1, true) + and response.body:lower():find("ubiquiti edgeswitch") + and get_tag(response.body, "script", {src="/static/scripts/bundle%-%x+%.js$"}) + end, + login_combos = { + {username = "ubnt", password = "ubnt"} + }, + login_check = function (host, port, path, user, pass) + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=path})), + ["Content-Type"]="application/json", + ["Accept"]="application/json, text/plain, */*"} + local jin = {username=user, password=pass} + json.make_object(jin) + local resp = http_post_simple(host, port, + url.absolute(path, "api/v1.0/user/login"), + {header=header}, json.generate(jin)) + return resp.status == 200 + and (resp.header["x-auth-token"] or ""):find("^%x+$") + end +}) + +table.insert(fingerprints, { + name = "NetComm ADSL router", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^NetComm ") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Netcomm NTC", + category = "routers", + paths = { + {path = "/index.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NetComm", 1, true) + and response.body:lower():find("/netcomm_gui_banner.jpg", 1, true) + and get_cookie(response, "_appwebSessionId_", "^%x+$") + end, + login_combos = { + {username = "root", password = "admin"}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, path, nil, + {username=user,password=pass}) + return resp.status == 302 + and (resp.header["location"] or ""):find("/st[as]tus%.html%f[?\0]") + end +}) + +table.insert(fingerprints, { + name = "Netcomm 3G17Wn", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">3G17Wn", 1, true) + and get_cookie(response, "_appwebSessionId_", "^%x+$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, path, nil, + {username=user,password=pass}) + return resp.status == 302 + and (resp.header["location"] or ""):find("/adm/status%.asp$") + end +}) + +table.insert(fingerprints, { + name = "NetComm 3G21WB", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("3G21WB", 1, true) + and response.body:lower():find("3g21wb", 1, true) + and get_tag(response.body, "frame", {src="^menu%.html$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "menu.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "NetComm 3G42WT", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("3G42WT", 1, true) + and response.body:lower():find("<title>3g42wt", 1, true) + and get_tag(response.body, "frame", {src="^login%.html$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "login.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "PacketFront DRG600", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "drg600.wifi" + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Airlink ACEmanager", + cpe = "cpe:/h:sierrawireless:airlink_mp_*", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Sierra Wireless AirLink", 1, true) + and response.body:lower():find("<title>:+%s+acemanager%s+:+") + end, + login_combos = { + {username = "user", password = "12345"} + }, + login_check = function (host, port, path, user, pass) + local encuser = xmlencode(user) + local header = {["Content-Type"]="text/xml"} + local msg = [=[ + + + __USER__ + + + + ]=] + msg = msg:gsub("%f[^\0\n]%s+", "") + msg = msg:gsub("__%w+__", {__USER__=encuser, __PASS__=pass}) + local resp = http_post_simple(host, port, + url.absolute(path, "xml/Connect.xml"), + {header=header}, msg) + return resp.status == 200 and get_cookie(resp, "token", "^%d+$") + end +}) + +table.insert(fingerprints, { + name = "Mimosa Relay", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Mimosa", 1, true) + and response.body:find("%Wmimosa%.isConnected%s*=") + end, + login_combos = { + {username = "configure", password = "mimosa"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + path .. "?q=index.login&mimosa_ajax=1", + nil, {username=user,password=pass}) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and (jout.role or 0) ~= 0 + end +}) + +table.insert(fingerprints, { + name = "IRTE Digital Radio Link", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Z-World Rabbit" + and response.body + and get_tag(response.body, "frame", {src="^objsum00%.html$"}) + end, + login_combos = { + {username = "", password = "0000"}, + {username = "", password = "111111"} + }, + login_check = function (host, port, path, user, pass) + local form1 = stdnse.output_table() + form1.infield5 = 1 + form1.infield6 = pass + local resp1 = http_post_multipart(host, port, + url.absolute(path, "pswd.cgi"), nil, form1) + if not (resp1.status == 200 and (resp1.body or ""):find("(['\"])password%.html%1")) then + return false + end + local resp2 = http_get_simple(host, port, + url.absolute(path, "password.html")) + return resp2.status == 200 + and get_tag(resp2.body or "", "input", {name="^infield5$", value="^2$"}) + end +}) + +table.insert(fingerprints, { + 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:find(">Motorola", 1, true) + and response.body:lower():find("motorola solutions", 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, { + 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:find(">Motorola", 1, true) + and response.body:lower():find("motorola wireless network management", 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, { + name = "Pakedge C36 Macrocell Controller", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and response.header["location"] == "./c36/login.php" + end, + login_combos = { + {username = "pakedge", password = "pakedgec"} + }, + login_check = function (host, port, path, user, pass) + local form = {rtype="login", + username=user, + password=pass} + local resp = http_post_simple(host, port, + url.absolute(path, "c36/ajax/login.php"), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.ok + end +}) + +table.insert(fingerprints, { + 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 form = {opcode="login", + url="/", + needxml=0, + uid=user, + passwd=pass} + local resp = http_post_simple(host, port, + url.absolute(path, "screens/wms/wms.login"), + nil, form) + 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 == 200 + and response.body + and response.body:find("/noauth/theme/airwave/favicon.ico", 1, true) + and response.body:lower():find("%shref%s*=%s*(['\"])[^'\"]-/mercury%.%d+%.css%1") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {credential_0=user, + credential_1=pass, + destination=url.absolute(path, "index.html")} + local resp = http_post_simple(host, port, url.absolute(path, "LOGIN"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/index%.html$") + end +}) + +table.insert(fingerprints, { + 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:find(">Nortel", 1, true) + and response.body:lower():find("nortel vpn router", 1, true) + end, + login_combos = { + {username = "admin", password = "setup"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "manage/bdy_sys.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "pfSense (var.1)", + cpe = "cpe:/a:bsdperimeter:pfsense", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/pfsense/login.css", 1, true) + and get_tag(response.body, "form", {name="^login_iform$"}) + end, + login_combos = { + {username = "admin", password = "pfsense"} + }, + login_check = function (host, port, path, user, pass) + local form = {usernamefld=user, + passwordfld=pass, + login="Login"} + local resp = http_post_simple(host, port, url.absolute(path, "index.php"), + nil, form) + return resp.status == 302 + and resp.header["location"] == path + and get_cookie(resp, "PHPSESSID", "^%x+$") + end +}) + +table.insert(fingerprints, { + name = "pfSense (var.2)", + cpe = "cpe:/a:pfsense:pfsense", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("pfSense", 1, true) + and get_tag(response.body, "input", {name="^__csrf_magic$"}) + end, + login_combos = { + {username = "admin", password = "pfsense"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local token = get_tag(resp1.body, "input", {type="^hidden$", name="^__csrf_magic$", value=""}) + if not token then return false end + local form = {[token.name]=token.value, + usernamefld=user, + passwordfld=pass, + login=""} + local resp2 = http_post_simple(host, port, url.absolute(path, "index.php"), + {cookies=resp1.cookies}, form) + return resp2.status == 302 + and resp2.header["location"] == path + and get_cookie(resp2, "PHPSESSID", "^%w+$") + end +}) + +table.insert(fingerprints, { + 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, { + 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) + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=url.absolute(path, "tmui/login.jsp")}))} + local resp = http_post_simple(host, port, + url.absolute(path, "tmui/logmein.html?"), + {header=header}, {username=user,passwd=pass}) + return resp.status == 302 + and get_cookie(resp, "BIGIPAuthCookie", "^%x+$") + end +}) + +table.insert(fingerprints, { + name = "F5 BIG-IQ", + cpe = "cpe:/a:f5:big-iq_centralized_management", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 301 + and response.header["server"] == "webd" + and (response.header["location"] or ""):find("/ui/login/?$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local header = {["Content-Type"]="application/json;charset=utf-8"} + local jin = {username=user, password=pass, needsToken=true} + json.make_object(jin) + local resp = http_post_simple(host, port, + url.absolute(path, "mgmt/shared/authn/login"), + {header=header}, json.generate(jin)) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.username == user and jout.token + end +}) + +table.insert(fingerprints, { + 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("citrix login", 1, true) + end, + login_combos = { + {username = "nsroot", password = "nsroot"} + }, + login_check = function (host, port, path, user, pass) + local form = {username=user, + password=pass, + url="", + timezone_offset=0} + local resp = http_post_simple(host, port, + url.absolute(path, "login/do_login"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/menu/neo$") + and get_cookie(resp, "startupapp") == "neo" + end +}) + +table.insert(fingerprints, { + name = "Citrix NetScaler MAS", + category = "routers", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/admin_ui/mas/ent/login%.html$") + end, + login_combos = { + {username = "nsroot", password = "nsroot"} + }, + login_check = function (host, port, path, user, pass) + local jin = {login={username=user,password=pass}} + json.make_object(jin) + local resp = http_post_simple(host, port, + url.absolute(path, "nitro/v1/config/login"), + nil, {object=json.generate(jin)}) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.errorcode == 0 and jout.resourceName == user + end +}) + +--- +--VoIP +--- +table.insert(fingerprints, { + name = "Aastra IP Phone", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^Aastra %d+i$") + end, + login_combos = { + {username = "admin", password = "22222"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Aastra AXS 5000", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local lurl = response.header["location"] or "" + if not (response.status == 302 and lurl:find("/rhm$")) then return false end + local resp = http_get_simple(host, port, lurl) + return http_auth_realm(resp) == "Aastra 5000" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "rhm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Aastra OpenCom 1000", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("OpenCom", 1, true) + and response.body:lower():find("opencom 1000", 1, true) + and get_tag(response.body, "frame", {src="/login%.html$"}) + end, + login_combos = { + {username = "Admin", password = "Admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, url.absolute(path, "login.html")) + if not (resp1.status == 200 and resp1.body) then return false end + local token = get_tag(resp1.body, "input", {name="^login$", value="^%x+$"}) + if not token then return false end + pass = stdnse.tohex(openssl.md5(pass)) + local form2 = {login=stdnse.tohex(openssl.md5(token.value .. pass)), + user=user, + password="", + ButtonOK="OK"} + local resp2 = http_post_simple(host, port, + url.absolute(path, "summary.html"), + nil, form2) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/%?uid=0x%x+$") + end +}) + +table.insert(fingerprints, { + name = "Cisco TelePresence", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/web/signin$") + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "web/signin/open"), nil, + {username=user, password=pass}) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.result == "ok" + end +}) + +table.insert(fingerprints, { + name = "Dialogic PowerMedia XMS Console", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/verifyLogin/", 1, true) + and response.body:lower():find("%s*dialogic xms admin console%s*") + end, + login_combos = { + {username = "viewer", password = "admin"}, + {username = "admin", password = "admin"}, + {username = "superadmin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "index.php/verifyLogin/login"), + nil, {usernameId=user, passwordId=pass}) + return resp.status == 200 + and get_cookie(resp, "ci_session", "USERNAME") + end +}) + +table.insert(fingerprints, { + name = "Dialogic PowerMedia XMS NodeController", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "NodeController" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Dialogic PowerMedia XMS RESTful API", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "XMS RESTful API" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Grandstream VoIP Device", + category = "voip", + paths = { + {path = "/cgi-bin/login"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Grandstream", 1, true) + and response.body:lower():find("grandstream ?device configuration") + and get_tag(response.body, "input", {name="^gnkey$", type="^hidden$", value="^0b82$"}) + end, + login_combos = { + {username = "", password = "admin"}, + {username = "", password = "123"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "dologin"), + nil, {P2=pass,Login="Login",gnkey="0b82"}) + return resp.status == 200 and get_cookie(resp, "session_id", "^%x+$") + end +}) + +table.insert(fingerprints, { + name = "Grandstream GXP2200", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("%Wdocument%.title%s*=%s*(['\"])GXP2200%1") + and response.body:lower():find("enterprise multimedia phone for android", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "user", password = "123"} + }, + login_check = function (host, port, path, user, pass) + local form = {action="login", + Username=user, + Secret=pass, + time=math.floor(stdnse.clock_ms())} + local resp = http_get_simple(host, port, + url.absolute(path, "manager?" .. url.build_query(form))) + return resp.status == 200 and get_cookie(resp, "phonecookie", "^%x+$") + end +}) + +table.insert(fingerprints, { + name = "Polycom SoundPoint (var.1)", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Polycom", 1, true) + and response.body:find("submitLoginInfo", 1, true) + and response.body:lower():find("polycom - configuration utility", 1, true) + and get_tag(response.body, "body", {onload="^document%.login%.password%.focus%(%)$"}) + end, + login_combos = { + {username = "Polycom", password = "456"}, + {username = "User", password = "123"} + }, + login_check = function (host, port, path, user, pass) + local qstr = url.build_query({t=os.date("!%a, %d %b %Y %H:%M:%S GMT")}) + return try_http_auth(host, port, url.absolute(path, "auth.htm?" .. qstr), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Polycom SoundPoint (var.2)", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Polycom", 1, true) + and response.body:find("submitLoginInfo", 1, true) + and response.body:lower():find("polycom - configuration utility", 1, true) + and get_tag(response.body, "input", {name="^password$", autocomplete="^off$"}) + end, + login_combos = { + {username = "Polycom", password = "456"}, + {username = "User", password = "123"} + }, + login_check = function (host, port, path, user, pass) + local creds = {username = user, password = pass, digest = false} + local resp = http_post_simple(host, port, + url.absolute(path, "form-submit/auth.htm"), + {auth=creds}, "") + return resp.status == 200 + and (resp.body or ""):find("|SUCCESS|", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Polycom SoundPoint (basic auth)", + cpe = "cpe:/h:polycom:soundpoint_ip_*", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.header["server"] == "Polycom SoundPoint IP Telephone HTTPd" + and http_auth_realm(response) == "SPIP Configuration" + end, + login_combos = { + {username = "Polycom", password = "456"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Polycom RSS 4000", + cpe = "cpe:/h:polycom:recording_and_streaming_server_4000", + category = "voip", + paths = { + {path = "/portal/login.jsf"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Polycom", 1, true) + and response.body:lower():find("polycom rss 4000", 1, true) + and get_tag(response.body, "input", {id="^loginform:username$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local vstate = get_tag(resp1.body, "input", {name="^javax%.faces%.viewstate$", value="^%-?%d+:%-?%d+$"}) + if not vstate then return false end + local opts2 = {header={["Faces-Request"]="partial/ajax"}, + cookies=resp1.cookies} + local form2 = {loginForm="loginForm", + ["loginForm:userName"]=user, + ["loginForm:password"]=pass, + ["loginForm:domain"]="LOCAL", + ["javax.faces.ViewState"]=vstate.value, + ["javax.faces.source"]="loginForm:loginBt", + ["javax.faces.partial.event"]="click", + ["javax.faces.partial.execute"]="loginForm:loginBt @component", + ["javax.faces.partial.render"]="@component", + ["org.richfaces.ajax.component"]="loginForm:loginBt", + ["loginForm:loginBt"]="loginForm:loginBt", + ["AJAX:EVENTS_COUNT"]=1, + ["javax.faces.partial.ajax"]="true"} + local resp2 = http_post_simple(host, port, path, opts2, form2) + return resp2.status == 200 + and (resp2.body or ""):find("checkLogin('')", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Polycom RMX 500", + cpe = "cpe:/h:polycom:rmx_500", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("", 1, true) + and response.body:lower():find("", 1, true) + end, + login_combos = { + {username = "POLYCOM", password = "POLYCOM"} + }, + login_check = function (host, port, path, user, pass) + local msg = [[ + + + + + <_CGI_NO_REFRESH value="YES" /> + + + + + + + + + <_CGI_UI_LANG value="en" /> + + <_CGI_TIME value="__TIME__" /> + + ]] + msg = msg:gsub("^%s+", ""):gsub("\n%s*", "") + msg = msg:gsub("__%w+__", {__USER__=xmlencode(user), + __PASS__=xmlencode(pass), + __IPADDR__=xmlencode(host.ip), + __TSTAMP__=math.floor(stdnse.clock_ms()), + __TIME__=xmlencode(os.date("!%a %b %d %Y %H:%M:%S GMT+0000"))}) + local qstr = url.build_query({_dst_in_xml_raw=msg}) + local resp = http_get_simple(host, port, + url.absolute(path, "cgi-bin/rmx_cgi?" .. qstr)) + return resp.status == 200 + and (resp.body or ""):find("%x+") + end +}) + +table.insert(fingerprints, { + name = "Polycom RMX 1000", + cpe = "cpe:/h:polycom:rmx_1000", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("", 1, true) + and response.body:lower():find("polycom rmx 1000", 1, true) + end, + login_combos = { + {username = "POLYCOM", password = "POLYCOM"} + }, + login_check = function (host, port, path, user, pass) + local msg = [[ + + + + + <_CGI_NO_REFRESH value="NO" /> + + + + + + + + <_CGI_UI_LANG value="en" /> + + <_CGI_TIME value="__TIME__" /> + + ]] + msg = msg:gsub("^%s+", ""):gsub("\n%s*", "") + msg = msg:gsub("__%w+__", {__USER__=xmlencode(user), + __PASS__=xmlencode(stdnse.tohex(pass)), + __TIME__=xmlencode(os.date("!%a %b %d %Y %H:%M:%S GMT+0000"))}) + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/rmx1000_cgi"), + nil, {_dst_in_xml_raw=msg}) + return resp.status == 200 + and (resp.body or ""):find("%x+") + end +}) + +table.insert(fingerprints, { + name = "Polycom RPAD", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Polycom RPAD" + and response.body + and get_refresh_url(response.body, "/edge/$") + end, + login_combos = { + {username = "LOCAL\\admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {j_username=base64.enc(user), + j_password=base64.enc(pass)} + local resp = http_post_simple(host, port, + url.absolute(path, "edge/security/check"), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(b64decode(resp.body:gsub("%s+","")) or "") + return jstatus and jout.success + end +}) + +table.insert(fingerprints, { + name = "Teles Gateway", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "TELES AG" + and response.body + and get_tag(response.body, "frame", {src="/common/navibar_[%w_]+_login%.html$"}) + end, + login_combos = { + {username = "teles-admin", password = "tcs-admin"}, + {username = "teles-user", password = "tcs-user"}, + {username = "teles-carrier", password = "tcs-carrier"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local frame = get_tag(resp1.body, "frame", {src="/common/navibar_[%w_]+_login%.html$"}) + if not frame then return false end + local nurl = url.absolute(path, frame.src) + local resp2 = http_get_simple(host, port, nurl) + if not (resp2.status == 200 and resp2.body) then return false end + local lurl = resp2.body:lower():match("]-%shref%s*=%s*['\"]?([^'\">%s]*)[^>]*>loginauthentication error: access denied, authorization required.", 1, true) + end, + login_combos = { + {username = "admin", password = "1234"}, + {username = "root", password = "5678"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Mediatrix iPBX", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("PBX Administration", 1, true) + and get_tag(response.body, "a", {href="^admin/$"}) + and response.body:lower():find("ipbx", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "admin/config.php"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Openstage IP Phone", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Unify", 1, true) + and get_tag(response.body, "frame", {src="[?&]page=webmp_user_login%f[&\0]"}) + end, + login_combos = { + {username = "", password = "123456"} + }, + login_check = function (host, port, path, user, pass) + local form = {page_submit="WEBMp_Admin_Login", + lang="en", + AdminPassword=pass} + local resp = http_post_simple(host, port, url.absolute(path, "page.cmd"), + nil, form) + return resp.status == 200 + and get_cookie(resp, "webm", "%d+|[%d-]*[1-9a-f][%d-]*") + end +}) + +table.insert(fingerprints, { + name = "Yealink IP Phone", + cpe = "cpe:/o:yealink:voip_phone_firmware", + category = "voip", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find(" IP [Pp]hone SIP%-%u%d+%u?$") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "user", password = "user"}, + {username = "var", password = "var"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +--- +--Digital recorders +--- +table.insert(fingerprints, { + 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 get_tag(response.body, "meta", {name="^author$", content="^dedicated micros "}) + end, + login_combos = { + {username = "dm", password = "web"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "frmpages/index.html"), + user, pass, true) + end +}) + +table.insert(fingerprints, { + 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 get_tag(response.body, "meta", {name="^author$", content="^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") + 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_auth(host, port, lurl, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "LevelOne WCS-0050 Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "LevelOne WCS-0050" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "LG Smart IP Device", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">LG Smart IP Device<", 1, true) + and get_tag(response.body, "frame", {src="^login_org%.php$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "digest.php"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "MOBOTIX Camera", + category = "security", + paths = { + {path = "/"}, + {path = "/control/userimage.html"} + }, + target_check = function (host, port, path, response) + return response.status == 401 + and http_auth_realm(response) + and response.body + and response.body:find("MOBOTIX AG", 1, true) + end, + login_combos = { + {username = "admin", password = "meinsm"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "OEM GoAhead-Webs IP Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.header["server"] == "GoAhead-Webs" + and http_auth_realm(response) == "GoAhead" + end, + login_combos = { + {username = "admin", password = "888888"}, + {username = "admin", password = "12345"}, + {username = "admin", password = "123456"}, + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "IPCC P2P Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.header["server"] == "GoAhead-Webs" + and http_auth_realm(response) == "WIFICAM" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "AXIS 2100 Network Camera", + cpe = "cpe:/h:axis:2100_network_camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Boa/%d+%.") + and response.body + and response.body:find("AXIS", 1, true) + and response.body:lower():find("axis ", 1, true) + end, + login_combos = { + {username = "root", password = "pass"}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "view/view.shtml"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "AXIS C/M/P/V Series Device", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if response.status == 302 then + if not (response.header["location"] or ""):find("/index%.shtml$") then + return false + end + response = http_get_simple(host, port, + url.absolute(path, "index.shtml")) + end + return response.status == 200 + and response.body + and response.body:find("/axis-cgi/pwdroot/set_language.cgi?", 1, true) + and response.body:lower():find("<title>index page", 1, true) + end, + login_combos = { + {username = "root", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp = http_get_simple(host, port, + url.absolute(path, "pwdroot/pwdRoot.shtml")) + return resp.status == 200 + and resp.body + and get_tag(resp.body, "input", {value="^" .. user .. "$"}) + and get_tag(resp.body, "input", {name="^pwd_confirm$"}) + end +}) + +table.insert(fingerprints, { + name = "AXIS Network Video Door Station", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if response.status == 302 then + if not (response.header["location"] or ""):find("/index%.shtml$") then + return false + end + response = http_get_simple(host, port, + url.absolute(path, "index.shtml")) + end + return response.status == 200 + and response.body + and response.body:find("%Wvar%s+refreshUrl%s*=%s*(['\"])[^'\"]-/view/view%.shtml%?id=%d+%1") + and response.body:lower():find("index page", 1, true) + end, + login_combos = { + {username = "root", password = "pass"} + }, + login_check = function (host, port, path, user, pass) + local form = {id=math.random(1000,30000), + imagepath=url.absolute(path, "mjpg/1/video.mjpg"), + size=1} + return try_http_auth(host, port, + url.absolute(path, "view/view.shtml?" .. url.build_query(form)), + user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "AXIS Entry Manager", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/webapp/pacs/index.shtml?id=", 1, true) + and (response.body:find("%Wvar%s+refreshUrl%s*=%s*(['\"])[^'\"]-/webapp/pacs/index%.shtml%?id=%d+%1") + or get_refresh_url(response.body, "/webapp/pacs/index%.shtml%?id=%d+$")) + and response.body:lower():find("index page", 1, true) + end, + login_combos = { + {username = "root", password = "pass"} + }, + login_check = function (host, port, path, user, pass) + local form = {action="list", + group="Properties.System.Language", + _=math.floor(stdnse.clock_ms())} + return try_http_auth(host, port, + url.absolute(path, "axis-cgi/param.cgi?" .. url.build_query(form)), + user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Panasonic Network Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("./live/index2.html?Language=", 1, true) + and (response.body:find("%Wlocation%.replace%((['\"])%./live/index2%.html%?Language=%d+%1") + or response.body:find("%Wwindow%.open%((['\"])%./live/index2%.html%?Language=%d+%1")) + and response.body:lower():find("%a%a%-%a%w+ ") + end, + login_combos = { + {username = "admin", password = "12345"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "live/index2.html?Language=0"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Sanyo Network Camera (no auth)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.body + and response.body:find("SANYO", 1, true) + and response.body:lower():find("<title>sanyo +network camera") + and get_tag(response.body, "form", {name="^lang_set$"})) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "cgi-bin/change_id.cgi")) + return resp.status == 200 + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + return true + end +}) + +table.insert(fingerprints, { + name = "Sanyo Network Camera (admin auth)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.body + and response.body:find("SANYO", 1, true) + and response.body:lower():find("sanyo +network camera") + and get_tag(response.body, "form", {name="^lang_set$"})) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "cgi-bin/change_id.cgi")) + return http_auth_realm(resp) == "You need advanced ID" + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin2", password = "admin2"}, + {username = "admin3", password = "admin3"}, + {username = "operator", password = "operator"}, + {username = "guest", password = "guest"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "cgi-bin/change_id.cgi?" .. math.floor(stdnse.clock_ms())) + return try_http_auth(host, port, lurl, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Sanyo Network Camera (user auth)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "You need ID" + and response.body + and response.body:lower():find("sanyo network camera", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin2", password = "admin2"}, + {username = "admin3", password = "admin3"}, + {username = "operator", password = "operator"}, + {username = "guest", password = "guest"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Sony Network Camera (Boa 1)", + cpe = "cpe:/h:sony:snc_*", + category = "security", + paths = { + {path = "/en/index.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Boa/%d+%.") + and response.body + and response.body:lower():find("%ssrc%s*=%s*(['\"])indexbar%.html%1") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "l4/index.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Sony Network Camera (Boa 2)", + cpe = "cpe:/h:sony:snc_*", + category = "security", + paths = { + {path = "/en/index.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Boa/%d+%.") + and response.body + and response.body:lower():find("sony network camera snc-", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local _, lurl = resp1.body:match("=%s*window%.open%(%s*(['\"])(.-)%1") + if not lurl then return false end + lurl = url.absolute(path, lurl) + return try_http_auth(host, port, lurl, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Sony Network Camera (NetEVI/Virgo)", + cpe = "cpe:/h:sony:snc_*", + category = "security", + paths = { + {path = "/index.html"} + }, + target_check = function (host, port, path, response) + local server = response.header["server"] or "" + return response.status == 200 + and server:find("^NetEVI/%d+%.") or server:find("^Virgo/%d+%.") + and response.body + and response.body:lower():find("<title>sony network camera snc-", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "home/l4/admin.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Sony Network Camera (thttpd)", + cpe = "cpe:/h:sony:snc_*", + category = "security", + 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:find("adm/file.cgi?next_file=setting.htm", 1, true) + and response.body:lower():find("<title>sony network camera snc-", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "adm/file.cgi?next_file=setting.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Basler Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:lower():find("<title>[^<]- web client [^<]- basler ag") + 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-bin/auth_if.cgi?Login"), + nil, {["Auth.Username"]=user, ["Auth.Password"]=pass}) + return resp.status == 200 + and (resp.body or ""):find("[{,]%s*success%s*:%s*true%s*[,}]") + end +}) + +table.insert(fingerprints, { + name = "IQinVision Camera (var.1)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local server = response.header["server"] or "" + return response.status == 401 + and response.body + and (server:find("^IQinVision Embedded ") + and response.body:find("%s*Please Authenticate%s*") + or server:find("^IQhttpD/%d+%.") + and response.body:find("Authorization required for the URL", 1, true)) + end, + login_combos = { + {username = "login", password = "access"}, + {username = "root", password = "system"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "IQinVision Camera (var.2)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 403 + and (response.header["server"] or ""):find("^IQinVision Embedded ") + and get_cookie(response, "SrvrNonce", "^%x+") + end, + login_combos = { + {username = "login", password = "access"}, + {username = "root", password = "system"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + local nonce = get_cookie(resp1, "SrvrNonce") + if not nonce then return false end + local creds = stdnse.tohex(openssl.md5(table.concat({nonce, user, + pass:upper()}, ":"))) + local cookies = ("SrvrNonce=%s; SrvrCreds=%s"):format(nonce, creds) + local resp2 = http_get_simple(host, port, path, {cookies=cookies}) + return resp2.status == 200 + end +}) + +table.insert(fingerprints, { + name = "IQinVision Camera (var.3)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local server = response.header["server"] or "" + if not (response.status == 200 + and response.body + and (server:find("^IQinVision Embedded ") + and response.body:find(">IQ", 1, true) + and response.body:lower():find("iq", 1, true) + or server:find("^IQhttpD/%d+%.") + and response.body:find("%Wself%.location%s*=%s*(['\"])dptzvid%.html%1"))) then + return false + end + local resp = http_get_simple(host, port, url.absolute(path, "accessset.html")) + return resp.status == 401 + end, + login_combos = { + {username = "root", password = "system"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "accessset.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "IQinVision Camera (var.4)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (have_openssl + and response.status == 200 + and (response.header["server"] or ""):find("^IQinVision Embedded ") + and response.body + and response.body:find(">IQ", 1, true) + and response.body:lower():find("<title>iq", 1, true)) then + return false + end + local resp = http_get_simple(host, port, url.absolute(path, "accessset.html")) + return resp.status == 403 + and get_cookie(resp, "SrvrNonce", "^%x+") + end, + login_combos = { + {username = "root", password = "system"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "accessset.html") + local resp1 = http_get_simple(host, port, lurl) + local nonce = get_cookie(resp1, "SrvrNonce") + if not nonce then return false end + local creds = stdnse.tohex(openssl.md5(table.concat({nonce, user, + 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 +}) + +table.insert(fingerprints, { + name = "Sentry360 FS-IP5000 Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Sentry360" + and response.body + and get_tag(response.body, "img", {src="^logo_cam_page%.png$"}) + end, + login_combos = { + {username = "Admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + local rnd1 = math.random(10000000, 99999999) + local rnd2 = math.random(10000000, 99999999) + local lurl = url.absolute(path, ("load.set?rnd=%d&rnd=%d"):format(rnd1, rnd2)) + return try_http_auth(host, port, lurl, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "SerVision TVG", + cpe = "cpe:/o:servision:hvg_video_gateway_firmware", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^2%.2%.") + and response.body + and response.body:find("TO_LOAD", 1, true) + and get_tag(response.body, "input", {name="^user_username$"}) + end, + login_combos = { + {username = "svuser", password = "servconf"}, + {username = "anybody", password = "Bantham"} + }, + login_check = function (host, port, path, user, pass) + local form = {user_username=user, + user_password=pass, + LOADED=1, + TO_LOAD="index.htm"} + local resp = http_post_simple(host, port, url.absolute(path, "index.htm"), + nil, form) + return resp.status == 201 + and (resp.body or ""):find("%WloadMain%((['\"])main%.htm%1%)") + end +}) + +table.insert(fingerprints, { + name = "Speco IP Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find('src="newlogin.html"', 1, true) + and response.body:lower():find("<title>speco ip camera", 1, true) + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_get_simple(host, port, + url.absolute(path, "httpapi?GetUserLevel&ipAddress="), + {auth={username=user, password=pass}}) + return resp.status == 200 + and (resp.body or ""):lower():find("userlevel:", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Brickcom Camera", + cpe = "cpe:/o:brickom:*", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^Brickcom%s") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "ACTi Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find('gPwd="123456"', 1, true) + and response.body:lower():find("web configurator", 1, true) + end, + login_combos = { + {username = "admin", password = "123456"} + }, + login_check = function (host, port, path, user, pass) + local lurl = ("cgi-bin/system?USER=%s&PWD=%s&LOGIN&SYSTEM_INFO"):format( + url.escape(user), url.escape(pass)) + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and (resp.body or ""):find("LOGIN='1'", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Ovislink AirLive BU", + cpe = "cpe:/h:ovislink:airlive_bu-*", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Boa/%d+%.") + and response.body + and response.body:find("controlmenu.htm", 1, true) + and get_tag(response.body, "frame", {src="^controlmenu%.htm$"}) + and response.body:lower():find("airlive", 1, true) + end, + login_combos = { + {username = "admin", password = "airlive"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "setting.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "American Dynamics IP Dome", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("gbl_locale", 1, true) + and response.body:lower():find("american dynamics", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {gbl_locale=1, + accessRoute="", + username=user, + password=pass} + local resp = http_post_simple(host, port, url.absolute(path, "index.php"), + nil, form) + return resp.status == 200 + and (resp.body or ""):find("gbl_username%s*=") + end +}) + +table.insert(fingerprints, { + name = "exacqVision", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.body + and response.body:find("%Wlocation%.replace%(%s*(['\"])login%.web%1%s*%)%s*;")) then + return false + end + local resp = http_get_simple(host, port, url.absolute(path, "login.web")) + return resp.status == 200 + and resp.body + and resp.body:find("exacqVision", 1, true) + and resp.body:lower():find("<title>login", 1, true) + end, + login_combos = { + {username = "admin", password = "admin256"} + }, + login_check = function (host, port, path, user, pass) + local form = {u=user, + p=pass, + l=1, + s=0, + output="json", + responseVersion=2, + save=1} + local resp = http_post_simple(host, port, url.absolute(path, "login.web"), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.login and jout.success + end +}) + +table.insert(fingerprints, { + name = "GeoVision Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "GeoHttpServer" + and response.body + and (response.body:find('action="webcam_login"', 1, true) + or response.body:find('action="phoneinfo"', 1, true)) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {id=user, + pwd=pass, + ViewType=2, + Login="Login"} + local resp = http_post_simple(host, port, + url.absolute(path, "webcam_login"), + nil, form) + return resp.status == 200 + and resp.body + and (resp.body:find('%sname%s*=%s*"IDKey"%f[%s][^>]-%svalue%s*=%s*"[%x-]+"') + or resp.body:find('%?IDKey=[%x-]+')) + end +}) + +table.insert(fingerprints, { + name = "GeoVision Web-Manager", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("GeoVision", 1, true) + and response.body:find("%Wlocation%.href%s*=%s*(['\"])ssi%.cgi/Login%.htm%1") + and response.body:lower():find("geovision ", 1, true) + end, + login_combos = { + {username = "guest", password = "guest"}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, + url.absolute(path, "ssi.cgi/Login.htm")) + if not (resp1.status == 200 and resp1.body) then return false end + local nonce1, nonce2 = resp1.body:match("%Wvar%s+cc1%s*=%s*['\"](%x+)['\"]%s*;%s*var%s+cc2%s*=%s*['\"](%x+)['\"]") + if not nonce1 then return false end + local hashfnc = function (p, a, b) return stdnse.tohex(openssl.md5(table.concat({a,p:lower(),b}))):upper() end + local form = {username="", + password="", + Apply="Apply", + umd5=hashfnc(user, nonce1, nonce2), + pmd5=hashfnc(pass, nonce2, nonce1), + browser=1} + local resp2 = http_post_simple(host, port, + url.absolute(path, "LoginPC.cgi"), + nil, form) + return resp2.status == 200 + and get_cookie(resp2, "CLIENT_ID", "^%d+$") + end +}) + +table.insert(fingerprints, { + name = "GeoVision WebControl", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^Welcome to GV%-%w+ WebControl$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Arecont Vision (no auth)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">Arecont Vision", 1, true) + and response.body:lower():find("<title>arecont vision camera", 1, true) + and get_tag(response.body, "div", {class="^avmenu$"}) + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + return true + end +}) + +table.insert(fingerprints, { + name = "Arecont Vision (basic auth)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Arecont Vision" + end, + login_combos = { + {username = "admin", password = ""}, + {username = "viewer", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Avigilon Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^Avigilon%-%d+$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Canon Camera", + cpe = "cpe:/h:canon:network_camera_server_vb*", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("img/canon_logo.gif", 1, true) + and get_tag(response.body, "img", {src="^img/canon_logo%.gif$"}) + and response.body:lower():find("network camera", 1, true) + end, + login_combos = { + {username = "root", password = "camera"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "admin/index.html?lang=en"), + user, pass, "any") + end +}) + +table.insert(fingerprints, { + name = "Brovotech IPCAM", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/cn/viewer_index%.asp$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "cn/viewer_index.asp"), + user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Grandstream Camera", + cpe = "cpe:/o:grandstream:gxv_device_firmware", + category = "security", + paths = { + {path = "/index.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "GS-Webs" + and response.body + and response.body:lower():find("%stype%s*=%s*['\"]application/x%-vnd%-npgs_") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "Pages/system.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Hikvision (var.1)", + category = "security", + paths = { + {path = "/index.asp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("%Wwindow%.location%.href%s*=%s*['\"]doc/page/login%.asp['\"?]") + and response.body:lower():find("index", 1, true) + end, + login_combos = { + {username = "admin", password = "12345"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_get_simple(host, port, + url.absolute(path, "PSIA/Custom/SelfExt/userCheck"), + {auth={username=user, password=pass}}) + return resp.status == 200 + and (resp.body or ""):lower():find("200", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Hikvision (var.2)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("%Wwindow%.location%.href%s*=%s*['\"]doc/page/login%.asp['\"?]") + and response.body:lower():find("index", 1, true) + end, + login_combos = { + {username = "admin", password = "12345"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_get_simple(host, port, + url.absolute(path, "ISAPI/Security/userCheck"), + {auth={username=user, password=pass}}) + return resp.status == 200 + and (resp.body or ""):lower():find("200", 1, true) + end +}) + +table.insert(fingerprints, { + name = "TI Megapixel IP Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Megapixel IP Camera" + and response.header["server"] == "HKVision-Webs" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "MayGion Camera (no auth)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "WebServer(IPCamera_Logo)" + and response.body + and get_tag(response.body, "iframe", {src="^video%.htm$"}) + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + return true + end +}) + +table.insert(fingerprints, { + name = "MayGion Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "WebServer(IPCamera_Logo)" + and response.body + and response.body:find("login.xml", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {user=user, + usr=user, + password=pass, + pwd=pass} + local lurl = "login.xml?" .. url.build_query(form) + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and get_cookie(resp, "user") == user + and get_cookie(resp, "password") == pass + and get_cookie(resp, "usrLevel") == "0" + end +}) + +table.insert(fingerprints, { + name = "OEM Boa IP Camera (var.1)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 401 + and (http_auth_realm(response) or ""):find(" IP Camera$") + and (response.header["server"] or ""):find("^Boa/%d+%.") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "OEM Boa IP Camera (var.2)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Boa/%d+%.") + and get_tag(response.body, "script", {src="^profile$"}) + and get_tag(response.body, "img", {id="^setting$",onclick="%f[%w]window%.location=(['\"])setting%.htm%1$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "setting.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "OEM Boa IP Camera (var.3)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Boa/%d+%.") + and get_tag(response.body, "script", {src="^profile$"}) + and response.body:lower():find("ip camera viewer", 1, true) + end, + login_combos = { + {username = "admin", password = "12345"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "setting.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "OEM Netcam", + category = "security", + paths = { + {path = "/"}, + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^[Nn]etcam$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Planet IP Cam", + category = "security", + paths = { + {path = "/"}, + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "PLANET IP CAM" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Planet IP Surveillance", + category = "security", + paths = { + {path = "/"}, + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("ipcam_language", 1, true) + and get_tag(response.body, "frame", {src="^asp/view%.asp$"}) + and response.body:lower():find("planet ip surveillance web management", 1, true) + end, + login_combos = { + {username = "admin", password = ""}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "asp/set.asp"), + user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "TP-Link IPC", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/web-static/dynaform/class.js", 1, true) + and response.body:lower():find("ipc", 1, true) + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local a = "RDpbLfCPsJZ7fiv" + local b = pass + local pwdlen = math.max(#a, #b) + a = table.pack(string.byte(a .. ("\187"):rep(pwdlen - #a), 1, -1)) + b = table.pack(string.byte(b .. ("\187"):rep(pwdlen - #b), 1, -1)) + local pad = "yLwVl0zKqws7LgKPRQ84Mdt708T1qQ3Ha7xv3H7NyU84p21BriUWBU43odz3iP4rBL3cD02KZciXTysVXiV8ngg6vL48rPJyAUw0HurW20xqxv9aYb4M9wK1Ae0wlro510qXeU07kV57fQMc8L6aLgMLwygtc0F10a0Dg70TOoouyFhdysuRMO51yY5ZlOZZLEal1h0t9YQW0Ko7oBwmCAHoic4HYbUyVeU3sfQ1xtXcPcf1aT303wAQhv66qzW" + local pwd = {} + for i = 1, pwdlen do + table.insert(pwd, pad:byte(1 + (a[i] ~ b[i]) % #pad)) + end + local header = {["Accept"]="application/json, text/plain, */*", + ["Content-Type"]="application/json;charset=utf-8"} + local jin = {method="do", + login={username=user, + password=string.char(table.unpack(pwd))}} + json.make_object(jin) + local resp = http_post_simple(host, port, path, {header=header}, + json.generate(jin)) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.stok and jout.error_code == 0 + end +}) + +table.insert(fingerprints, { + name = "Allnet Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "NetworkPTZ" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "D-Link Camera", + cpe = "cpe:/h:d-link:dcs-*", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^DCS%-%d+%u?%f[_\0]") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Microseven IP camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/hi3510/", 1, true) + and get_tag(response.body, "script", {src="/cgi%-bin/hi3510/param%.cgi%?cmd=getuserinfo$"}) + end, + login_combos = { + {username = "admin", password = "password"}, + {username = "guest", password = "guest"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "cgi-bin/hi3510/param.cgi?cmd=getuserinfo"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Milesight Camera (var.1)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and (response.body:find(">Milesight Network Camera", 1, true) + or response.body:find(">IPCAM Network Camera", 1, true)) + and get_tag(response.body, "input", {id="^secret$"}) + and not get_tag(response.body, "script", {src="/javascript/md5%.js%?"}) + end, + login_combos = { + {username = "admin", password = "ms1234"}, + {username = "operator", password = "ms1234"}, + {username = "viewer", password = "ms1234"} + }, + login_check = function (host, port, path, user, pass) + local userno = {admin=0, operator=1, viewer=2} + local creds = {tostring(userno[user]), + url.escape(user), + url.escape(pass)} + local lurl = "vb.htm?language=ie&checkpassword=" .. table.concat(creds, ":") + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and resp.body:find("OK checkpassword", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Milesight Camera (var.2)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and (response.body:find(">Milesight Network Camera", 1, true) + or response.body:find(">IPCAM Network Camera", 1, true)) + and get_tag(response.body, "input", {id="^secret$"}) + and get_tag(response.body, "script", {src="/javascript/md5%.js%?"}) +end, + login_combos = { + {username = "admin", password = "ms1234"}, + {username = "operator", password = "ms1234"}, + {username = "viewer", password = "ms1234"} + }, + login_check = function (host, port, path, user, pass) + local userno = {admin=0, operator=1, viewer=2} + local creds = {tostring(userno[user]), + url.escape(user), + stdnse.tohex(openssl.md5(pass))} + local lurl = "vb.htm?language=ie&checkpassword=" .. table.concat(creds, ":") + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and resp.body:find("OK checkpassword", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Milesight Camera (Alphafinity)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find(">Alphafinity Network Camera", 1, true) + and get_tag(response.body, "input", {id="^secret$"}) + and get_tag(response.body, "script", {src="/javascript/md5%.js%?"}) +end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local userno = {admin=0, operator=1, viewer=2} + local creds = {tostring(userno[user]), + url.escape(user), + stdnse.tohex(openssl.md5(pass))} + local lurl = "vb.htm?language=ie&checkpassword=" .. table.concat(creds, ":") + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and resp.body:find("OK checkpassword", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Milesight Camera (Beward)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and (response.body:find(">BEWARD Network HD camera", 1, true) + or response.body:find(">Beward Network Camera", 1, true)) + and get_tag(response.body, "input", {id="^secret$"}) + and get_tag(response.body, "script", {src="/javascript/md5%.js%?"}) +end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "testuser", password = "htyjdfwbz1"} + }, + login_check = function (host, port, path, user, pass) + local userno = {admin=0, testuser=1} + local creds = {tostring(userno[user]), + url.escape(user), + stdnse.tohex(openssl.md5(pass))} + local lurl = "vb.htm?language=ie&checkpassword=" .. table.concat(creds, ":") + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and resp.body:find("OK checkpassword", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Beward SIP Door Station", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 401 + and (http_auth_realm(response) or ""):find(" SIP Door Station %- %x+$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "OEM MegapixelIPCamera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) + return response.status == 401 + and (response.header["server"] or ""):find("^Mbedthis%-Appweb/%d+%.") + and (realm == "MegapixelIPCamera" or realm == "QuasarHDIPCamera") + end, + login_combos = { + {username = "Admin", password = "1234"}, + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Philips InSight", + cpe = "cpe:/h:philips:in.sight*", + category = "security", + 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:find(">Philips ", 1, true) + and response.body:lower():find("%salt%s*=%s*(['\"])philips insight wireless home monitor%1") + end, + login_combos = { + {username = "admin", password = "M100-4674448"}, + {username = "user", password = "M100-4674448"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "cgi-bin/v1/camera"), + user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Planex CS", + cpe = "cpe:/o:planex:cs-*", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^CS%-%u+%d+[%u%d]*$") + end, + login_combos = { + {username = "admin", password = "password"}, + {username = "supervisor", password = "dangerous"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Santec IPCamera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Santec-IPCamera" + end, + login_combos = { + {username = "admin", password = "9999"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "HD IPC IP camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and (response.header["server"] or ""):find("^thttpd/%d+%.") + and response.body + and get_refresh_url(response.body, "/web/index%.html$")) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "web/index.html")) + return resp.status == 200 + and resp.body + and resp.body:find("LonginPassword", 1, true) + and get_tag(resp.body, "input", {id="^longinpassword$"}) + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "guest", password = "guest"} + }, + login_check = function (host, port, path, user, pass) + local form = {["-name"]=user, + ["-passwd"]=pass, + ["-time"]=math.floor(stdnse.clock_ms())} + local lurl = url.absolute(path, "cgi-bin/hi3510/checkuser.cgi?" .. url.build_query(form)) + local resp = http_get_simple(host, port, lurl) + return resp.status == 200 + and resp.body + and resp.body:find("%f[%w]var%s+check%s*=%s*(['\"]?)1%1%s*;") + and resp.body:find("%f[%w]var%s+authLevel%s*=%s*['\"]?[1-9]") + end +}) + +table.insert(fingerprints, { + name = "3S Vision", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if response.header["server"] ~= "httpd" then return false end + local realm = http_auth_realm(response) or "" + return realm == "IP Video Server" + or realm == "IP SPEED DOME" + or realm:find("^[%w ]- IP Camera$") + end, + login_combos = { + {username = "3sadmin", password = "27988303"}, + {username = "root", password = "root"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Network Video Server (var.1)", + category = "security", + paths = { + {path = "/login.asp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("onLoginNVS", 1, true) + and response.body:lower():find("web service", 1, true) + and get_tag(response.body, "script", {["for"]="^WebCMS$", event="^CBK_LoginResult%("}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {username=user, + password=pass, + UserID=math.random(10000000, 99999999)} + local lurl = url.absolute(path, "webs/loginCMS") .. "?" + .. url.build_query(form) + local resp = http_get_simple(host, port, lurl) + return resp.status == 200 + and (resp.body or ""):find("%d") + end +}) + +table.insert(fingerprints, { + name = "Network Video Server (var.2)", + category = "security", + paths = { + {path = "/login.asp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("onLoginNVS", 1, true) + and response.body:lower():find("web service", 1, true) + and get_tag(response.body, "script", {["for"]="^NetVideoX$", event="^CBK_LoginResult%("}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {username=user, + password=pass, + UserID=math.random(10000000, 99999999)} + local lurl = url.absolute(path, "webs/httplogin") .. "?" + .. url.build_query(form) + local resp = http_get_simple(host, port, lurl) + return resp.status == 200 + and (resp.body or ""):find("%d") + end +}) + +table.insert(fingerprints, { + name = "Network Video Server (var.3)", + category = "security", + paths = { + {path = "/login.asp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("onLoginNVS", 1, true) + and get_tag(response.body, "script", {event="^CallBackLoginState%("}) + and get_tag(response.body, "script", {src="^script/base64%.js$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {action="list", + group="LOGIN", + UserID=math.random(10000000, 99999999)} + local lurl = url.absolute(path, "cgi-bin/login.cgi") .. "?" + .. url.build_query(form) + local resp = http_get_simple(host, port, lurl, + {auth={username=user, password=pass}}) + return resp.status == 200 + and (resp.body or ""):find("%f[%w]root.ERR.no=0%f[^%w]") + end +}) + +table.insert(fingerprints, { + name = "Pravis Systems DVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and get_refresh_url(response.body, "/cgi%-bin/design/html_template/Login%.html$") + and response.body:lower():find("login cgicc form", 1, true) + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/design/html_template/Login.cgi"), + nil, {login_txt_id=user, login_txt_pw=pass}) + return resp.status == 200 + and resp.body + and resp.body:find("%Wlocation%s*=%s*(['\"])webviewer%.cgi%1") + end +}) + +table.insert(fingerprints, { + name = "Foscam Netwave (var.1)", + cpe = "cpe:/o:foscam:ip_camera_firmware", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Netwave IP Camera" + and response.body + and get_tag(response.body, "script", {src="^check_user%.cgi$"}) + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "check_user.cgi"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Foscam Netwave (var.2)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Netwave IP Camera" + and response.body + and response.body:find("%Wwindow%.location%.href%s*=%s*(['\"])index1%.htm%1") + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "check_user.cgi") .. "?" + .. url.build_query({user=user, pwd=pass}) + return try_http_auth(host, port, lurl, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Foscam IP Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("IPCam", 1, true) + and response.body:lower():find("ipcam client", 1, true) + and response.body:lower():find("%ssrc%s*=%s*['\"]js/main%.js['\"?]") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {usr=user, + pwd=pass, + cmd="logIn", + usrName=user, + groupId=string.sub(math.floor(stdnse.clock_ms()), -9)} + local lurl = "cgi-bin/CGIProxy.fcgi?" .. url.build_query(form) + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and (resp.body or ""):find("0", 1, true) + end +}) + +table.insert(fingerprints, { + name = "ITX Web Remote Viewer", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if response.status == 200 + and get_refresh_url(response.body, "/redirect%.html$") then + response = http_get_simple(host, port, url.absolute(path, "redirect.html")) + end + return http_auth_realm(response) == "WEB Remote Viewer" + end, + login_combos = { + {username = "ADMIN", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "html/versioninfo.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "JVC VN-xxx Camera", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^JVC VN%-%w+ API Server%f[/\0]") + and response.body + and get_refresh_url(response.body, "/cgi%-bin/%w+%.cgi%?%w+%.html$") + end, + login_combos = { + {username = "admin", password = "jvc"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_get_simple(host, port, path) + local lurl = resp.status == 200 + and get_refresh_url(resp.body or "", "/cgi%-bin/%w+%.cgi%?%w+%.html$") + if not lurl then return false end + return try_http_auth(host, port, lurl, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "JVC VR-8xx DVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "VR-8xx" + end, + login_combos = { + {username = "admin", password = "jvc"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "JVC Broadcaster", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^JVC Broadcaster %d+%.%d+") + end, + login_combos = { + {username = "admin", password = "jvc1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "OEM DVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("%Wdocument%.location%.replace%(%s*(['\"])mlogin%.cgi%1%s*%)%s*;") + and response.body:lower():find("dvr login", 1, true) + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {c_userid=user, + c_password=pass, + c_target=2} + local resp = http_post_simple(host, port, + url.absolute(path, "direct_open_setup.cgi"), + nil, form) + return resp.status == 200 + and get_tag(resp.body or "", "script", {src="^setup%.js$"}) + end +}) + +table.insert(fingerprints, { + name = "Samsung DVR", + cpe = "cpe:/h:samsung:dvr", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Samsung", 1, true) + and response.body:lower():find("web viewer for samsung dvr", 1, true) + end, + login_combos = { + {username = "admin", password = "4321"} + }, + login_check = function (host, port, path, user, pass) + local cookie = ("DATA1=%s&DATA2=%s&SDATA3=%.15f"):format(base64.enc(user), + base64.enc(pass), + math.random()) + local form = {lang="en", + port=0, + close_user_session=0, + data1=base64.enc(user), + data2=stdnse.tohex(openssl.md5(pass))} + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/webviewer_cgi_login2"), + {cookies=cookie}, form) + return resp.status == 200 + and (resp.body or ""):find("%Wtop%.document%.location%.href%s*=%s*['\"]%.%./index%.htm[?'\"]") + end +}) + +table.insert(fingerprints, { + name = "Samsung iPOLiS", + cpe = "cpe:/a:samsung:ipolis_device_manager", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.body + and response.body:find("home/monitoring.cgi", 1, true) + and response.body:find("%Wdocument%.location%.replace%((['\"])[^'\"]-%f[^/'\"]home/monitoring%.cgi%1%)%s*;")) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "home/monitoring.cgi")) + return (http_auth_realm(resp) or ""):find("^iPolis%f[_\0]") + end, + login_combos = { + {username = "admin", password = "4321"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "home/monitoring.cgi"), + user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Truen TCAM (var.1)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/user/view.html", 1, true) + and get_tag(response.body, "frame", {src="/user/view%.html$"}) + and response.body:lower():find("video surveillance", 1, true) + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "user/view.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Truen TCAM (var.2)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local lurl = response.status == 200 + and get_refresh_url(response.body or "", "/user/view%.html$") + if not lurl then return false end + local resp = http_get_simple(host, port, lurl) + return (http_auth_realm(resp) or ""):find("^IPVideo_%x+$") + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "user/view.html"), + user, pass, "any") + end +}) + +table.insert(fingerprints, { + name = "TVT DVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.body + and response.body:find("Pages/login.htm", 1, true) + and response.body:find("%Wwindow%.location%.href%s*=%s*(['\"])Pages/login%.htm%1")) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "Pages/login.htm")) + return resp.status == 200 + and resp.body + and resp.body:find("IDCS_LOGIN_NBSP", 1, true) + end, + login_combos = { + {username = "admin", password = "123456"}, + {username = "admin", password = "1"} + }, + login_check = function (host, port, path, user, pass) + local auth = {username = user, password = pass} + local header = {["Content-Type"]="text/plain;charset=UTF-8"} + local msg = [=[ + + + ]=] + msg = msg:gsub("^%s+", ""):gsub("\n%s*", "") + local resp = http_post_simple(host, port, url.absolute(path, "doLogin"), + {auth=auth, header=header}, msg) + return resp.status == 200 + and (resp.body or ""):find("success", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Ubiquiti UniFi Video (var.1)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">UniFi Video<", 1, true) + and response.body:lower():find("unifi video", 1, true) + and get_tag(response.body, "main-view", {["ui-view"]=""}) + and get_tag(response.body, "script", {["data-headjs-load"]="^main%.js%f[\0?]"}) + end, + login_combos = { + {username = "ubnt", password = "ubnt"} + }, + login_check = function (host, port, path, user, pass) + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=url.absolute(path, "login")})), + ["Content-Type"]="application/json", + ["Accept"]="application/json, text/plain, */*"} + local jin = {username=user, password=pass} + json.make_object(jin) + local resp = http_post_simple(host, port, + url.absolute(path, "api/1.1/login"), + {cookies="ubntActiveUser=false", header=header}, + json.generate(jin)) + return resp.status == 200 + and get_cookie(resp, "authId", "^%w+$") + end +}) + +table.insert(fingerprints, { + name = "Ubiquiti UniFi Video (var.2)", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">UniFi Video<", 1, true) + and response.body:find("app-id=com.ubnt.unifivideo", 1, true) + and response.body:lower():find("unifi video", 1, true) + and get_tag(response.body, "meta", {name="^google%-play%-app$", content="^app%-id=com%.ubnt%.unifivideo$"}) + end, + login_combos = { + {username = "ubnt", password = "ubnt"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if resp1.status ~= 200 then return false end + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=url.absolute(path, "login")})), + ["Content-Type"]="application/json", + ["Accept"]="application/json, text/plain, */*"} + local jin = {username=user, password=pass} + json.make_object(jin) + local resp2 = http_post_simple(host, port, + url.absolute(path, "api/2.0/login"), + {cookies=resp1.cookies, header=header}, + json.generate(jin)) + return resp2.status == 200 + and get_cookie(resp2, "JSESSIONID_AV", "^%x+$") + end +}) + +table.insert(fingerprints, { + name = "Xiongmai NETSurveillance", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("%Wlocation%s*=%s*(['\"])Login%.htm%1%s*;") + and response.body:find("%Wvar%s+gHashCookie%s*=%s*new%s+Hash%.Cookie%(%s*(['\"])NetSuveillanceWebCookie%1%s*,") + end, + login_combos = { + {username = "admin", password = ""}, + {username = "default", password = "tluafed"} + }, + login_check = function (host, port, path, user, pass) + local cookie = "NetSuveillanceWebCookie=" + .. url.escape(('{"username":"%s"}'):format(user)) + local form = stdnse.output_table() + form.command = "login" + form.username = user + form.password = pass + local resp = http_post_simple(host, port, url.absolute(path, "Login.htm"), + {cookies=cookie}, form) + return resp.status == 200 + and (resp.body or ""):match("%Wvar%s+g_user%s*=%s*['\"](.-)['\"]%s*;") == user + end +}) + +table.insert(fingerprints, { + name = "AVTech AVC DVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("MM_goToURL", 1, true) + and response.body:lower():find("--- video web server ---", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {username=user, + password=pass, + Submit="Submit"} + local resp = http_post_simple(host, port, url.absolute(path, "home.htm"), + nil, form) + return resp.status == 200 + and (resp.body or ""):lower():find("::: login :::", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local creds = base64.enc(user .. ":" .. pass) + local lurl = ("cgi-bin/nobody/VerifyCode.cgi?account=%s&rnd=%.15f"):format( + creds, math.random()) + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and get_cookie(resp, "SSID") == creds + end +}) + +table.insert(fingerprints, { + name = "EverFocus ECORHD", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local realm = http_auth_realm(response) or "" + return realm:find("^ECOR%d+%-[%u%d]+$") + or realm:find("^ELUX%d+$") + end, + login_combos = { + {username = "admin", password = "11111111"}, + {username = "user1", password = "11111111"}, + {username = "user2", password = "11111111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Interlogix truVision", + category = "security", + paths = { + {path = "/index.asp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Interlogix-Webs" + and response.body + and response.body:find("%Wwindow%.location%.href%s*=%s*(['\"])doc/page/login%.asp%1") + end, + login_combos = { + {username = "admin", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + local header = {["Content-Type"]="text/xml"} + local creds = {username = user, password = pass, digest = false} + local ipaddr = ("192.168.%d.%d"):format(math.random(254), math.random(254)) + local macaddr = random_hex(12):gsub("..", ":%1"):sub(2) + local msg = [[ + + + __IPADDR__ + __MACADDR__ + ]] + msg = msg:gsub("^%s+", ""):gsub("\n%s*", "") + msg = msg:gsub("__%w+__", {__IPADDR__=ipaddr, __MACADDR__=macaddr}) + local resp = http_post_simple(host, port, + url.absolute(path, "PSIA/Custom/SelfExt/userCheckEx"), + {header=header, auth=creds}, msg) + return resp.status == 200 + and (resp.body or ""):find("200", 1, true) + end +}) + +table.insert(fingerprints, { + name = "LILIN NVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^Merit LILIN") + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "NUUO NVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NUUO", 1, true) + and response.body:lower():find("nuuo network video recorder login", 1, true) + and get_tag(response.body, "form", {name="^mainform$", action="^index%.php$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {language="English", + login=user, + password=pass, + submit=" Login "} + local resp = http_post_simple(host, port, url.absolute(path, "index.php"), + nil, form) + return resp.status == 302 + and resp.header["location"] == "screen.php" + end +}) + +table.insert(fingerprints, { + name = "NUUO Titan NVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NUUO", 1, true) + and response.body:lower():find("[%w%s]*network video recorder login") + and get_tag(response.body, "form", {name="^mainform$", action="^login%.php$"}) + and get_tag(response.body, "img", {type="^submit$", value="^login$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {language="en", + user=user, + pass=pass, + browser_engine="firefox"} + local resp = http_post_simple(host, port, url.absolute(path, "login.php"), + nil, form) + return (resp.status == 302 + and (resp.header["location"] or ""):find("/setting%.php$")) + or (resp.status == 200 + and (resp.body or ""):find("%snexpage%s*=%s*(['\"])setting%.php%1")) + end +}) + +table.insert(fingerprints, { + name = "NUUO Solo NVR", + cpe = "cpe:/o:nuuo:nvrsolo", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NUUO", 1, true) + and response.body:lower():find("[%w%s]*network video recorder login") + and get_tag(response.body, "form", {name="^mainform$", action="^login%.php$"}) + and get_tag(response.body, "input", {type="^submit$", name="^submit$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {language="en", + user=user, + pass=pass, + submit="Login"} + local resp = http_post_simple(host, port, url.absolute(path, "login.php"), + nil, form) + return (resp.status == 302 + and (resp.header["location"] or ""):find("/setting%.php$")) + or (resp.status == 200 + and (resp.body or ""):find("%snexpage%s*=%s*(['\"])setting%.php%1")) + end +}) + +table.insert(fingerprints, { + name = "NUUO Solo NVR OEM", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NUUO", 1, true) + and response.body:lower():find("[%w%s]*network video recorder login") + and get_tag(response.body, "form", {name="^mainform$", action="^login%.php$"}) + and get_tag(response.body, "input", {type="^image$", name="^submit$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {["submit.x"]=0, + ["submit.y"]=0, + language="en", + user=user, + pass=pass, + browser_engine="firefox", + base_url=""} + local resp = http_post_simple(host, port, url.absolute(path, "login.php"), + nil, form) + return (resp.status == 302 + and (resp.header["location"] or ""):find("/setting%.php$")) + or (resp.status == 200 + and (resp.body or ""):find("%snexpage%s*=%s*(['\"])setting%.php%1")) + end +}) + +table.insert(fingerprints, { + name = "VideoIQ iCVR", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("?wicket:bookmarkablePage=:com.videoiq.fusion.camerawebapi.ui.pages.LoginPage", 1, true) + end, + login_combos = { + {username = "supervisor", password = "supervisor"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + local lurl = (resp1.header["location"] or ""):match("%f[/]/%f[^/].*") + if not (resp1.status == 302 and lurl) then return false end + local form = {loginForm1_hf_0="", + userName=user, + password=pass, + login=""} + local resp2 = http_post_simple(host, port, + lurl .. "&wicket:interface=:0:loginPanel:loginForm::IFormSubmitListener::", + {cookies=resp1.cookies}, form) + return resp2.status == 302 + end +}) + +table.insert(fingerprints, { + name = "Dahua Security", + cpe = "cpe:/o:dahuasecurity:dvr_firmware", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and (response.body:find("js/loginEx.js", 1, true) + and get_tag(response.body, "script", {src="^js/loginEx%.js%f[?\0]"}) + and get_tag(response.body, "script", {src="^jsCore/rpcCore%.js%f[?\0]"}) + or response.body:find("/js/merge.js", 1, true) + and get_tag(response.body, "script", {src="/js/merge%.js$"}) + and get_tag(response.body, "div", {id="^download_plugins$"}) + or response.body:find("jsBase/widget/js/dui.tab.js", 1, true) + and get_tag(response.body, "script", {src="^jsBase/widget/js/dui%.tab%.js%f[?\0]"}) + and get_tag(response.body, "script", {src="^jsCore/common%.js%f[?\0]"})) + end, + login_combos = { + {username = "666666", password = "666666"}, + {username = "admin", password = "admin"}, + {username = "anonymity", password = "anonymity"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "RPC2_Login") + local opts = {cookies="DHLangCookie30=English", + header={["X-Request"]="JSON"}} + local jin = {method="global.login", + params={userName=user, + password="", + clientType="Web3.0"}, + id=10000} + json.make_object(jin) + local resp1 = http_post_simple(host, port, lurl, opts, json.generate(jin)) + if not (resp1.status == 200 and resp1.body) then return false end + local jstatus, jout = json.parse(resp1.body) + local params = jstatus and jout.params + if not params then return false end + local passtype + if not params.encryption then + elseif params.encryption == "Basic" then + pass = base64.enc(user .. ":" .. pass) + elseif params.encryption == "Default" then + local hashfnc = function (...) + local text = table.concat({...}, ":") + return stdnse.tohex(openssl.md5(text)):upper() + end + if not (params.random and params.realm) then return false end + pass = hashfnc(user, params.random, hashfnc(user, params.realm, pass)) + passtype = "Default" + elseif params.encryption == "OldDigest" then + local hash = openssl.md5(pass) + local ptbl = {} + for i = 1, #hash, 2 do + local a, b = hash:byte(i, i + 1) + a = (a + b) % 62 + if a <= 9 then + b = 48 + elseif a <= 35 then + b = 55 + else + b = 61 + end + table.insert(ptbl, string.char(a + b)) + end + pass = table.concat(ptbl) + else + return false + end + opts.cookies = opts.cookies .. "; DhWebClientSessionID=" .. jout.session + jin.session = jout.session + jin.params.password = pass + jin.params.passwordType = passtype + jin.params.authorityType = params.encryption + local resp2 = http_post_simple(host, port, lurl, opts, json.generate(jin)) + if not (resp2.status == 200 and resp2.body) then return false end + jstatus, jout = json.parse(resp2.body) + return jstatus and jout.result + end +}) + +table.insert(fingerprints, { + name = "Digital Watchdog", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 301 + and (response.header["location"] or ""):find("/static/index%.html$") + and (response.header["server"] or ""):find("(Digital Watchdog)", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "api/getCurrentUser") + local resp1 = http_get_simple(host, port, lurl, {cookies="Authorization=Digest"}) + local realm = get_cookie(resp1, "realm") + local nonce = get_cookie(resp1, "nonce") + if not (resp1.status == 401 and realm and nonce) then return false end + user = user:lower() + local hashfnc = function (...) + local text = table.concat({...}, ":") + return stdnse.tohex(openssl.md5(text)) + end + local hash = hashfnc(hashfnc(user, realm, pass), nonce, hashfnc("GET:")) + local auth = url.escape(base64.enc(table.concat({user, nonce, hash}, ":"))) + table.insert(resp1.cookies, {name="Authorization", value="Digest", path=path}) + table.insert(resp1.cookies, {name="auth", value=auth, path=path}) + local resp2 = http_get_simple(host, port, lurl, {cookies=resp1.cookies}) + return resp2.status == 200 + and resp2.header["content-type"] == "application/json" + end +}) + +table.insert(fingerprints, { + name = "Loxone Intercom Video", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("HyNetOS/%d+%.") + and response.body + and response.body:find("Loxone", 1, true) + and response.body:lower():find("loxone intercom video", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "setup.cgi"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Loxone Smart Home", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Loxone", 1, true) + and response.body:lower():find("loxone smart home", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, + url.absolute(path, "jdev/cfg/apiKey")) + if not (resp1.status == 200 and resp1.body) then return false end + local jstatus, jout = json.parse(resp1.body) + if not (jstatus and jout.LL.value) then return false end + jstatus, jout = json.parse(jout.LL.value:gsub("'", '"')) + if not (jstatus and jout.key) then return false end + local key = stdnse.fromhex(jout.key) + local auth = stdnse.tohex(openssl.hmac("SHA1", key, user .. ":" .. pass)) + local lurl = "jdev/sps/LoxAPPversion3?" .. url.build_query({auth=auth,user=user}) + local resp2 = http_get_simple(host, port, url.absolute(path, lurl)) + return resp2.status == 200 + end +}) + +table.insert(fingerprints, { + name = "Automa Lilliput2", + category = "security", + paths = { + {path = "/login.php"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Automa", 1, true) + and response.body:lower():find("[^<]-%sautoma srl") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, path, nil, + {username=user,password=pass,submit="Login"}) + return resp.status == 302 + and resp.header["location"] == "index.php" + end +}) + +table.insert(fingerprints, { + name = "Siedle Door Controller", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Z-World Rabbit" + and response.body + and response.body:lower():find("", 1, true) + and response.body:lower():find("%Wparent%.location%s*=%s*(['\"])[^'\"]-/index%.zht%1") + end, + login_combos = { + {username = "Service", password = "Siedle"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, url.absolute(path, "login.zht")) + if not (resp1.status == 200 and resp1.body) then return false end + local lang = resp1.body:lower():match("]-%sname%s*=%s*['\"]m_webdata%.m_cgilogin%.m_lang['\"].-]-%sselected%f[%s>][^>]*)") + lang = (lang or ""):match("%svalue%s*=%s*['\"](%w+)['\"]") + if not lang then return false end + local form2 = stdnse.output_table() + form2["m_webdata.m_cgiLogin.m_user"] = user + form2["m_webdata.m_cgiLogin.m_passwd"] = pass + form2["m_webdata.m_cgiLogin.m_lang"] = lang + form2["action.x"] = 0 + form2["action.y"] = 0 + local resp2 = http_post_simple(host, port, url.absolute(path, "login.cgi"), + nil, form2) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/index%.zht$") + and get_cookie(resp2, "DCRABBIT", "^%-?%d+$") + end +}) + +table.insert(fingerprints, { + name = "Genetec Synergis", + category = "security", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.header["server"] == "Microsoft-HTTPAPI/2.0" + and response.body + and get_refresh_url(response.body, "/ui$")) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "ui/LogOn?ReturnUrl=%2fui")) + return resp.status == 200 + and resp.body + and resp.body:find("/genetec.") + end, + login_combos = { + {username = "admin", password = "softwire"} + }, + login_check = function (host, port, path, user, pass) + local form = {UserName=user, + Password=pass, + Language="En", + TimeZoneOffset=0} + local resp = http_post_simple(host, port, + url.absolute(path, "ui/LogOn?ReturnUrl=%2fui"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/ui$") + end +}) + +--- +--Industrial systems +--- +table.insert(fingerprints, { + 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_auth(host, port, + url.absolute(path, "secure/embedded/http_passwd_config.htm?Language=English"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Schneider Xflow", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("Xflow", 1, true) + and get_tag(response.body, "input", {name="^rsakey1$"}) + end, + login_combos = { + {username = "TEST", password = "TEST"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local rsakey1 = get_tag(resp1.body, "input", {name="^rsakey1$", value="^%d+$"}) + local rsakey2 = get_tag(resp1.body, "input", {name="^rsakey2$", value="^%d+$"}) + if not (rsakey1 and rsakey2) then return false end + local p = openssl.bignum_dec2bn(rsakey1.value) + local m = openssl.bignum_dec2bn(rsakey2.value) + local encpass = {} + local r = 0 + for _, s in ipairs({pass:byte(1, -1)}) do + local a = openssl.bignum_dec2bn(r + s) + local b = openssl.bignum_bn2dec(openssl.bignum_mod_exp(a, p, m)) + table.insert(encpass, ("%04x"):format(b)) + r = s + end + table.insert(encpass, 1, ("0000"):rep(16-#encpass)) + local form2 = {language="EN", + login="home.xml", + username=user, + rsakey1=rsakey1.value, + rsakey2=rsakey2.value, + pwd=table.concat(encpass):upper(), + enter="Log in"} + local resp2 = http_post_simple(host, port, url.absolute(path, "kw"), + nil, form2) + return resp2.status == 200 + and (resp2.body or ""):find("%Wvar%s+sessionid%s*=%s*(['\"])%x+%1") + end +}) + +table.insert(fingerprints, { + name = "TCS Basys Controls Communication Center", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Private" + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Adcon Telemetry Gateway", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Adcon", 1, true) + and response.body:lower():find("%s*adcon telemetry gateway%s*") + and get_tag(response.body, "a", {href="%f[%w]configurator%.jnlp$"}) + end, + login_combos = { + {username = "root", password = "840sw"}, + {username = "adv", password = "addvantage"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "getconfig"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Lantronix ThinWeb Manager", + category = "industrial", + 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:find("Lantronix", 1, true) + and response.body:lower():find("lantronix %w*web manager%W") + end, + login_combos = { + {username = "", password = "system"} + }, + login_check = function (host, port, path, user, pass) + local resp0 = http_get_simple(host, port, path) + if not (resp0.status == 200 and resp0.body) then return false end + local lurl = get_tag(resp0.body, "frame", {src="^summary%.html$"}) + and "server.html" + or resp0.body:lower():match("<a%f[%s][^>]-%shref%s*=%s*['\"]([^'\"]+)['\"]%s*>server properties</a>") + if not lurl then return false end + lurl = url.absolute(path, lurl) + local resp1 = http_get_simple(host, port, lurl) + local nonce = resp1.status == 403 and get_cookie(resp1, "SrvrNonce", ".") + if not nonce then return false end + 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 +}) + +table.insert(fingerprints, { + name = "Lantronix XPort", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("secure/ltx_conf.htm", 1, true) + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "secure/ltx_conf.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Moxa MiiNePort", + cpe = "cpe:/o:moxa:miineport_*", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 307 + and (response.header["location"] or ""):find("/moxa/home%.htm$") + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {Username=user, + Password="", + MD5Password=stdnse.tohex(openssl.md5(pass)), + Submit="Login"} + local resp = http_post_simple(host, port, + url.absolute(path, "moxa/Login.htm"), + nil, form) + return resp.status == 200 + and (resp.body or ""):find("%Wwindow%.open%((['\"])home%.htm%1") + end +}) + +table.insert(fingerprints, { + name = "MBus Webserver", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "MBus Webserver" + and response.header["server"] == "MBus WebServer" + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Silex Server (var.1)", + cpe = "cpe:/o:silex:*", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/status/devstat.htm", 1, true) + and response.body:lower():find("<title>%a%a%a?%-%w%w%-?%w+") + end, + login_combos = { + {username="root", password=""}, + {username="admin", password="admin"}, + {username="admin", password="1234"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_get_simple(host, port, path) + if not (resp.status == 200 and resp.body) then return false end + local frm = get_tag(resp.body, "frame", {src="/%w+/status/devstat%.htm$"}) + if not frm then return false end + local lang = frm.src:match("/(%w+)/status/devstat%.htm$") + return try_http_auth(host, port, + url.absolute(path, lang .. "/mnt/adpass.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Silex Server (var.2)", + cpe = "cpe:/o:silex:*", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("status.hti?", 1, true) + and response.body:lower():find("silex ", 1, true) + end, + login_combos = { + {username="", password="ACCESS"} + }, + login_check = function (host, port, path, user, pass) + local form = {access="", + password="", + language=0, + access_psw=pass, + action="Submit"} + local resp = http_post_simple(host, port, url.absolute(path, "login"), + nil, form) + return resp.status == 200 + and get_tag(resp.body or "", "frame", {src="^status%.hti%?access=%x+&"}) + end +}) + +table.insert(fingerprints, { + name = "Wago I/O System 750", + cpe = "cpe:/h:wago:wago_i%2fo_system*", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/webserv/index%.ssi$") + end, + login_combos = { + {username="admin", password="wago"}, + {username="user", password="user"}, + {username="guest", password="guest"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "webserv/cplcfg/security.ssi"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Wago TO-PASS", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "WAGO TO-PASS" + end, + login_combos = { + {username="admin", password="wago"}, + {username="user", password="user"}, + {username="guest", password="guest"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "ProMinent Controller", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "Z-World Rabbit" + and response.body + and get_tag(response.body, "frame", {src="^right%.shtml$"}) + end, + login_combos = { + {username = "Operator1", password = "1"}, + {username = "Operator2", password = "2"}, + {username = "Operator3", password = "3"}, + {username = "Operator4", password = "4"}, + {username = "Configure5", password = "5"}, + {username = "Configure6", password = "6"}, + {username = "Configure7", password = "7"}, + {username = "admin", password = "AAAA"} + }, + login_check = function (host, port, path, user, pass) + local usermap = {["Operator1"]=1, + ["Operator2"]=2, + ["Operator3"]=3, + ["Operator4"]=4, + ["Configure5"]=5, + ["Configure6"]=6, + ["Configure7"]=7, + ["admin"]=8} + local lurl = ("taco.cgi?F0=AH&F1=%d&F2=%s"):format(usermap[user],pass) + local resp = http_get_simple(host, port, url.absolute(path, lurl)) + return resp.status == 200 + and (get_cookie(resp, "DCRABBIT") or ""):lower() == user:lower() + end +}) + +table.insert(fingerprints, { + name = "Emerson EC2", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("EC2", 1, true) + and response.body:lower():find("<title>ec2 %d+ ") + and get_tag(response.body, "frame", {src="^bckgnd%.html$"}) + end, + login_combos = { + {username = "EmersonID", password = "12"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "tcp_ip.shtml.shtml"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Emerson Xweb", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/cgi-bin/xweb500.cgi", 1, true) + and response.body:find("%WUrl%s*=%s*(['\"])[^'\"]-/cgi%-bin/xweb500%.cgi%?res=%d%1") + end, + login_combos = { + {username = "Admin", password = "Admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {pg=2, + action=2, + act=0, + login=user, + passwd=pass} + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/user.cgi"), + nil, form) + return resp.status == 200 + and resp.body + and resp.body:find("%Wvar%s+value%s*=%s*(['\"])" .. user .. "%1") + and resp.body:find("%Wlocation%.href%s*=%s*(['\"])[^'\"]-/index/indexFr%.html%1") + end +}) + +table.insert(fingerprints, { + name = "Heatmiser Wifi Thermostat", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Heatmiser", 1, true) + and response.body:lower():find("<title>heatmiser wifi thermostat", 1, true) + and get_tag(response.body, "input", {name="^lgpw$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, path, nil, {lgnm=user,lgpw=pass}) + return resp.status == 302 + and (resp.header["location"] or ""):find("/main%.htm$") + end +}) + +table.insert(fingerprints, { + name = "Heatmiser NetMonitor 1.x", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NetMonitor", 1, true) + and response.body:lower():find("netmonitor ", 1, true) + and get_tag(response.body, "input", {name="^loginname$"}) + 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, "view_stats.htm"), nil, + {loginname=user, loginpassword=pass}) + return resp.status == 200 + and get_tag(resp.body or "", "a", {href="^setup_stats%.htm$"}) + end +}) + +table.insert(fingerprints, { + name = "Heatmiser NetMonitor 3.0x", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Netmonitor", 1, true) + and response.body:find("loginState", 1, true) + and response.body:lower():find("<title>netmonitor ", 1, true) + and get_tag(response.body, "input", {name="^loginun$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_post_simple(host, port, url.absolute(path, "main.htm"), + nil, {loginun=user, loginpw=pass}) + if not (resp1.status == 200 and (resp1.body or ""):find("(['\"]?)left%.htm%1")) then + return false + end + local resp2 = http_get_simple(host, port, url.absolute(path, "left.htm")) + return resp2.status == 200 + and get_tag(resp2.body or "", "input", {name="^loginstate$", value="^1$"}) + end +}) + +table.insert(fingerprints, { + name = "Heatmiser NetMonitor 3.x", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Netmonitor", 1, true) + and response.body:find("hmcookies", 1, true) + and response.body:lower():find("<title>netmonitor ", 1, true) + and get_tag(response.body, "input", {name="^loginun$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local idx = get_tag(resp1.body, "input", {name="^hmckidx$", value="^%d$"}) + if not idx then return false end + idx = idx.value + local form = {curckidx=idx, + loginun=user, + loginpw=pass} + local resp2 = http_post_simple(host, port, url.absolute(path, "main.htm"), + {cookies="hmcookie="..idx}, form) + if not (resp2.status == 200 and resp2.body) then return false end + local hmcookies = get_tag(resp2.body, "input", {name="^hmcookies$", value="^%d+$"}) + return hmcookies + and hmcookies.value:sub(idx + 1, idx + 1) == "1" + end +}) + +table.insert(fingerprints, { + name = "Jacarta interSeptor", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">Jacarta ", 1, true) + and response.body:lower():find("<title>jacarta interseptor", 1, true) + and get_tag(response.body, "frame", {src="/pagecompre.html$"}) + end, + login_combos = { + {username = "interSeptor", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "PageAControl.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Phasefale JouleAlarm/JouleTemp", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Phasefale Joule", 1, true) + and response.body:lower():find("<title>phasefale joule", 1, true) + and get_tag(response.body, "form", {action="/set/set%.html$"}) + end, + login_combos = { + {username = "admin", password = "pass"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "set/set.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Proliphix Thermostat", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("index.shtml", 1, true) + and response.body:find("%WprintNavLine%(%s*(['\"])Login%1%s*,%s*(['\"])index%.shtml%2%s*%)") + and response.body:lower():find("<title>thermostat [^<]-%- status & control") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "user", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "index.shtml"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "CS121 UPS Web/SNMP Manager", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^HyNetOS/%d+%.") + and response.body + and response.body:lower():find("cs121 snmp/web adapter", 1, true) + end, + login_combos = { + {username = "admin", password = "cs121-snmp"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "admin/net.shtml"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + 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:find(">Netman ", 1, true) + and response.body:lower():find("netman 204 login", 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, { + name = "APC Management Card (basic auth)", + cpe = "cpe:/h:apc:ap*", + 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"}, + {username = "device", password = "apc"}, + {username = "readonly", password = "apc"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "APC Management Card", + cpe = "cpe:/h:apc:ap*", + category = "industrial", + paths = { + {path = "/logon.htm"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and (response.body:find("apclogo", 1, true) + or response.body:find("www.apc.com", 1, true)) + and response.body:lower():find("[^<]*log on") + and get_tag(response.body, "input", {name="^login_username$"}) + end, + login_combos = { + {username = "apc", password = "apc"}, + {username = "device", password = "apc"}, + {username = "readonly", password = "apc"} + }, + login_check = function (host, port, path, user, pass) + local form = {login_username=user, + login_password=pass, + submit="Log On"} + local resp = http_post_simple(host, port, + url.absolute(path, "Forms/login1"), + nil, form) + local loc = resp.header["location"] + if not (resp.status == 303 and loc) then return false end + if loc:find("/home%.htm$") then return true end + for _, ck in ipairs(resp.cookies or {}) do + if ck.name:find("^APC") then return true end + end + return false + end +}) + +table.insert(fingerprints, { + name = "APC InfraStruXure Central", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("www.apc.com", 1, true) + and (response.body:lower():find("infrastruxure central ", 1, true) + or response.body:lower():find("<title>struxureware central ", 1, true)) + and get_tag(response.body, "a", {href="^nbc/status/Status$"}) + end, + login_combos = { + {username = "apc", password = "apc"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "nbc/status/Status"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "APC InfraStruXure PDU", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "InfraStruXure PDU" + end, + login_combos = { + {username = "device", password = "apc"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "InfraPower PPS-02-S", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and response.header["location"] == "?/3/login" + and (response.header["server"] or ""):find("^lighttpd/%d+%.") + and get_cookie(response, "PHPSESSID", "^%w+$") + end, + login_combos = { + {username = "00000000", password = "00000000"} + }, + login_check = function (host, port, path, user, pass) + local form = {status=1, + usr=user, + psw=pass, + ["t-tag"]=os.date("!%m%d%H%M%Y")} + local resp = http_post_simple(host, port, url.absolute(path, "?/3/login"), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.callback + end +}) + +table.insert(fingerprints, { + name = "iBoot", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "iBoot" + end, + login_combos = { + {username = "", password = "PASS"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "iBoot G2", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return (http_auth_realm(response) or ""):find("^iBoot%-G2S?$") + end, + login_combos = { + {username = "admin", password = "admin"}, + {username = "user", password = "user"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "iBoot Bar", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find(">iBoot", 1, true) + and response.body:lower():find("<title>iboot bar ", 1, true) + and get_tag(response.body, "input", {name="^password$"}) + 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.cgi"), + nil, {name=user,password=pass}) + return resp.status == 200 + and get_cookie(resp, "DCRABBIT", "^%d+$") + and (resp.body or ""):find("%Wlocation%s*=%s*(['\"])index%.ztm%1") + end +}) + +table.insert(fingerprints, { + name = "HP Power Manager", + cpe = "cpe:/a:hp:power_manager_remote_agent", + category = "industrial", + paths = { + {path = "/index.asp"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("HP", 1, true) + and response.body:lower():find("<title>hp power manager", 1, true) + and get_tag(response.body, "form", {action="/goform/formlogin$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {HtmlOnly="true", + Login=user, + Password=pass, + loginButton="Submit Login"} + local resp = http_post_simple(host, port, + url.absolute(path, "goform/formLogin"), + nil, form) + return resp.status == 200 + and (resp.body or ""):find("%Wtop%.location%.href%s*=%s*(['\"])[^'\"]-/Contents/index%.asp%1") + end +}) + +table.insert(fingerprints, { + name = "Sunny WebBox (var.1)", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Sunny Webbox", 1, true) + and get_refresh_url(response.body, "/culture/index%.dml$") + end, + login_combos = { + {username = "User", password = "0000"}, + {username = "Installer", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + local form = {Language="LangEL", + Userlevels=user, + password=pass} + local resp = http_post_simple(host, port, + url.absolute(path, "culture/login"), + nil, form) + return resp.status == 200 + and get_tag(resp.body or "", "page", {id="^DeviceOverview$"}) + end +}) + +table.insert(fingerprints, { + name = "Sunny Central (var.1)", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["location"] or ""):find("/SunnyCentral/public$") + end, + login_combos = { + {username = "User", password = "0000"}, + {username = "Installer", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + local usrlvl = {User=0,Installer=1} + local header = {["Content-Type"]="application/json;charset=utf-8"} + local jin = {password=pass, + msg="", + userLevel=usrlvl[user], + parameters={}} + json.make_object(jin) + local resp = http_post_simple(host, port, + url.absolute(path, "home/login"), + {header=header}, json.generate(jin)) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.data and jout.data.ret + end +}) + +table.insert(fingerprints, { + name = "Sunny WebBox/Central (var.2)", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Sunny ", 1, true) + and response.body:lower():find("sunny %a+") + and get_tag(response.body, "frame", {src="^home_frameset%.htm$"}) + end, + login_combos = { + {username = "", password = "sma"} + }, + login_check = function (host, port, path, user, pass) + local form = {Language="en", + Password=pass, + ButtonLogin="Login"} + local resp = http_post_simple(host, port, url.absolute(path, "login"), + nil, form) + if not (resp.status == 200 + and (resp.body or ""):find("top.frames[2].location.reload()", 1, true)) then + return false + end + http_post_simple(host, port, + url.absolute(path, "home_frameset.htm?Logout=true"), + nil, {ButtonLogin="Abmelden"}) + return true + end +}) + +table.insert(fingerprints, { + name = "Sunny Central (var.3)", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Sunny ", 1, true) + and response.body:lower():find("sunny central ") + and get_tag(response.body, "input", {name="^action$"}) + and get_tag(response.body, "input", {name="^command$"}) + end, + login_combos = { + {username = "user", password = "sma"}, + {username = "installer", password = "sma"} + }, + login_check = function (host, port, path, user, pass) + local form = {action="login", + command="auth", + uname=user, + language="en", + pass=pass, + _ie_dummy=""} + local resp = http_post_simple(host, port, path, nil, form) + return resp.status == 200 + and get_tag(resp.body or "", "input", {name="^action$", value="^solar$"}) + end +}) + +table.insert(fingerprints, { + name = "Deva Broadcast", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("devabroadcast.com", 1, true) + and (get_tag(response.body, "form", {action="^login%.shtml$"}) + or get_tag(response.body, "li", {["data-c"]="^lgn$"})) + end, + login_combos = { + {username = "user", password = "pass"}, + {username = "admin", password = "pass"} + }, + login_check = function (host, port, path, user, pass) + local form = stdnse.output_table() + form.user = user + form.pass = pass + local resp = http_post_simple(host, port, url.absolute(path, "login.shtml"), + nil, form) + return resp.status == 303 + and (resp.header["location"] or ""):find("/main%.shtml$") + end +}) + +table.insert(fingerprints, { + name = "Deva Broadcast (basic auth)", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("devabroadcast.com", 1, true) + and get_tag(response.body, "a", {href="/secure/net%.htm$"}) + end, + login_combos = { + {username = "user", password = "pass"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "secure/net.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Harmonic NSG 9000", + category = "industrial", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NSG 9000", 1, true) + and response.body:find("(['\"])/AUTH/a%1") + and response.body:lower():find("<title[^>]*>nsg 9000%-") + end, + login_combos = { + {username = "admin", password = "nsgadmin"}, + {username = "guest", password = "nsgguest"}, + {username = "config", password = "nsgconfig"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "AUTH/a"), + user, pass, false) + end +}) + +--- +--Printers +--- +table.insert(fingerprints, { + name = "Canon imageRunner Advance", + cpe = "cpe:/a:canon:imagerunner", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("CANON", 1, true) + and response.body:lower():find("<title>default authentication", 1, true) + and get_tag(response.body, "input", {name="^deptid$"}) + end, + login_combos = { + {username = "7654321", password = "7654321"} + }, + login_check = function (host, port, path, user, pass) + local form = {uri=path, + user_type_generic="", + deptid=user, + password=pass} + local resp = http_post_simple(host, port, url.absolute(path, "login"), + nil, form) + return resp.status == 302 + and get_cookie(resp, "com.canon.meap.service.login.session", "^%-?%d+$") + end +}) + +table.insert(fingerprints, { + name = "Kyocera Command Center", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("start.htm", 1, true) + and get_tag(response.body, "frame", {src="/start/start%.htm$"}) + and response.body:lower():find("<title>kyocera command center", 1, true) + end, + login_combos = { + {username = "", password = "admin00"} + }, + login_check = function (host, port, path, user, pass) + local form = {okhtmfile=url.absolute(path, "opt1/index.htm"), + failhtmfile=url.absolute(path, "start/StartAccessDenied.htm"), + func="authLogin", + arg01_UserName=user, + arg02_Password=pass, + arg03_LoginType="", + submit001="OK", + language="../opt1/index.htm"} + local resp = http_post_simple(host, port, + url.absolute(path, "start/login.cgi"), + nil, form) + return resp.status == 200 + and get_cookie(resp, "level") == "3" + end +}) + +table.insert(fingerprints, { + name = "Kyocera Command Center (basic auth)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^KM%-httpd/%d+%.") + and response.body + and response.body:find("start.htm", 1, true) + and get_tag(response.body, "frame", {src="/start/start%.htm$"}) + end, + login_combos = { + {username = "", password = ""}, + {username = "Admin", password = "Admin"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "basic/DevDef.htm") + local resp = http_get_simple(host, port, lurl) + if resp.status == 200 then return user == "" end + return try_http_auth(host, port, lurl, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Kyocera Command Center RX", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Start_Wlm.htm", 1, true) + and get_tag(response.body, "frame", {src="/startwlm/start_wlm%.htm$"}) + end, + login_combos = { + {username = "Admin", password = "Admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {failhtmfile=url.absolute(path, "startwlm/Start_Wlm.htm"), + okhtmfile=url.absolute(path, "startwlm/Start_Wlm.htm"), + func="authLogin", + arg03_LoginType="_mode_off", + arg04_LoginFrom="_wlm_login", + language="../wlmeng/index.htm", + privid="", + publicid="", + attrtype="", + attrname="", + arg01_UserName=user, + arg02_Password=pass, + arg05_AccountId="", + Login="Login", + arg06_DomainName="", + hndHeight=0} + local lurl = url.absolute(path, "startwlm/login.cgi") + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=lurl}))} + local resp = http_post_simple(host, port, lurl, {header=header}, form) + return resp.status == 200 + and get_cookie(resp, "level") == "1" + end +}) + +table.insert(fingerprints, { + 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) + local resp0 = http.get(host, port, path) + if not (resp0.status == 200 and resp0.body) then return false end + local lurl = resp0.body:match("%Wlocation%.href%s*=%s*['\"](/[^'\"]-/)mainFrame%.cgi['\"]") + if not lurl then return false end + local resp1 = http_get_simple(host, port, url.absolute(lurl, "authForm.cgi"), + {cookies="cookieOnOffChecker=on"}) + if not (resp1.status == 200 and resp1.body) then return false end + local token = get_tag(resp1.body, "input", {type="^hidden$", name="^wimToken$", value=""}) + if not token then return false end + local form = {wimToken = token.value, + 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 get_cookie(resp2, "wimsesid", "^%d+$") + end +}) + +table.insert(fingerprints, { + name = "Samsung SyncThru (var.1)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("SyncThru", 1, true) + and response.body:lower():find("syncthru web service", 1, true) + and get_tag(response.body, "frame", {src="^top_frame%.html$"}) + end, + login_combos = { + {username = "", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp = http_get_simple(host, port, + url.absolute(path, "Maintenance/security.htm")) + return resp.status == 200 + and (resp.body or ""):find("%Wvar%s+secEnabled%s*=%s*(['\"])%1%s*;") + end +}) + +table.insert(fingerprints, { + name = "Samsung SyncThru (var.2)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("SyncThru", 1, true) + and response.body:lower():find("syncthru web service", 1, true) + and get_tag(response.body, "frame", {src="^first_top_frame%.html$"}) + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + local form = {j_username=base64.enc(user), + j_password=base64.enc(pass), + j_domain=base64.enc("LOCAL"), + context=url.absolute(path, "sws.login"), + j_targetAuthSuccess=url.absolute(path, "sws.login/gnb/loggedinView.sws?loginBG=login_bg.gif&basedURL=/&sws=N&isPinCode=false"), + IDUserId=user, + IDUserPw=pass, + IDDomain="LOCAL", + isPinCode="true", + isIdOnly="true"} + local resp = http_post_simple(host, port, + url.absolute(path, "sws.application/j_spring_security_check_pre_installed"), + nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/loggedinView%.sws%f[;?\0]") + and get_cookie(resp, "UserRole") == "Admin" + end +}) + +table.insert(fingerprints, { + name = "Sharp Printer", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["server"] or ""):find("^Rapid Logic/%d+%.") + and (response.header["location"] or ""):find("/main%.html$") + end, + login_combos = { + {username = "Administrator", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local usermap = {Administrator = 3} + local lurl = url.absolute(path, "login.html?") .. url.absolute(path, "main.html") + local resp1 = http_get_simple(host, port, lurl) + if not (resp1.status == 200 and resp1.body) then return false end + local ltype = get_tag(resp1.body, "input", {type="^hidden$", name="^ggt_hidden%(10008%)$", value="^%d+$"}) + if not ltype then return false end + local token = get_tag(resp1.body, "input", {type="^hidden$", name="^token2$", value="^%x+$"}) + if not token then return false end + local form2 = {["ggt_select(10009)"]=usermap[user], + ["ggt_textbox(10003)"]=pass, + action="loginbtn", + token2=token.value, + ordinate=0, + ["ggt_hidden(10008)"]=ltype.value} + local resp2 = http_post_simple(host, port, lurl, + {cookies=resp1.cookies}, form2) + return resp2.status == 302 + and (resp2.header["location"] or ""):find("/main%.html$") + end +}) + +table.insert(fingerprints, { + name = "Sharp Printer (basic auth)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["extend-sharp-setting-status"] == "0" + and response.body + and get_tag(response.body, "frame", {src="^link_user%.html$"}) + end, + login_combos = { + {username = "admin", password = "Sharp"}, + {username = "user", password = "Sharp"}, + {username = "admin", password = "1234"}, + {username = "user", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "condition_def.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Toshiba TopAccess HD", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("/js/TopAccessUtil.js", 1, true) + end, + login_combos = { + {username = "admin", password = "123456"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + local token = resp1.status == 200 and get_cookie(resp1, "session", ".") + if not token then return false end + local ipaddr = token:match("^(.+)%.") + if not ipaddr then return false end + local header = {["Content-Type"]="text/plain", ["csrfpId"]=token} + local msg = [[ + + + + + + + + + + + + + + + __USER__ + __PASS__ + __IPADDR__ + + + TOP_ACCESS + + + + + + Authentication/UserCredential + TOPACCESS + + + + + LoginPassword__PASS__ + LoginUser__USER__ + + + ]] + msg = msg:gsub("^%s+", ""):gsub("\n%s*", "") + msg = msg:gsub("__%w+__", {__USER__=xmlencode(user), + __PASS__=xmlencode(pass), + __IPADDR__=ipaddr}) + local resp2 = http_post_simple(host, port, + url.absolute(path, "contentwebserver"), + {cookies=resp1.cookies, header=header}, msg) + return resp2.status == 200 + and (resp2.body or ""):find(".-STATUS_OK.-") + end +}) + +table.insert(fingerprints, { + name = "Toshiba TopAccess SY", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 301 + and (response.header["location"] or ""):find("/TopAccess/default%.htm$") + end, + login_combos = { + {username = "Admin", password = "123456"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "ADMIN/Login"), + nil, {USERNAME=user,PASS=pass}) + return resp.status == 301 and get_cookie(resp, "sessid", "^0,%x+$") + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (var.1)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("XEROX WORKCENTRE", 1, true) + and get_tag(response.body, "frame", {src="/header%.php%?tab=status$"}) + 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"} + local resp = http_post_simple(host, port, + url.absolute(path, "userpost/xerox.set"), + nil, form) + return resp.status == 200 + and (resp.body or ""):find("%Wwindow%.opener%.top%.location%s*=%s*window%.opener%.top%.location%.pathname%s*;") + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (var.2)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and (response.body or ""):find("RedirectToSWS()", 1, true)) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "sws/index.html")) + return resp.status == 200 + and resp.body + and resp.body:find("CentreWare", 1, true) + and resp.body:lower():find("[^<]-%f[%w]centreware%f[%W]") + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + local auth = "Basic " .. base64.enc(user .. ":" .. pass) + local resp = http_post_simple(host, port, + url.absolute(path, "sws/app/gnb/login/login.jsp"), + nil, {Authentication=auth}) + return resp.status == 200 + and (resp.body or ""):find("%Wsuccess%s*:%s*true%W") + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (basic auth)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "CentreWare Internet Services" + end, + login_combos = { + {username = "11111", password = "x-admin"}, + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (basic auth var.1)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.body + and response.body:find("hdstat.htm", 1, true) + and get_tag(response.body, "frame", {src="^hdstat%.htm$"})) then + return false + end + local lcbody = response.body:lower() + return lcbody:find("<title>[%w%s]*workcentre%s") + or lcbody:find("<title>%s*internet services%W") + or lcbody:find("<title>%s*docucolor%W") + end, + login_combos = { + {username = "11111", password = "x-admin"}, + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "prscauthconf.htm"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (basic auth var.2)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and (response.body or ""):find("ChangeDefWebLanguage()", 1, true)) then + return false + end + local resp = http_get_simple(host, port, url.absolute(path, "home.html")) + return (http_auth_realm(resp) or ""):find("%f[%w]WorkCentre%f[%W]") + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "home.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (basic auth var.3)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and (response.body or ""):find("ChangeDefWebLanguage()", 1, true)) then + return false + end + local resp = http_get_simple(host, port, url.absolute(path, "home.html")) + return resp.status == 200 + and resp.body + and resp.body:find("Xerox", 1, true) + and resp.body:lower():find("<title>[^<]-%f[%w]xerox%f[%W]") + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "properties/securitysettings.html"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (basic auth var.4)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Xerox", 1, true) + and response.body:find("/status/statusAlerts.dhtml", 1, true) + and response.body:find("/tabsFrame.dhtml", 1, true) + and get_tag(response.body, "frame", {src="/tabsframe%.dhtml$"}) + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "properties/maintenance/maintenance.dhtml"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (basic auth var.5)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and not response.header["server"] + and response.body + and response.body:find("Xerox", 1, true) + and response.body:find("/js/deviceStatus.dhtml", 1, true) + and response.body:find("/tabsFrame.dhtml", 1, true) + and get_tag(response.body, "frame", {src="/tabsframe%.dhtml$"}) + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "reloadMaintenance.dhtml"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Xerox CentreWare (basic auth var.6)", + category = "printer", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and (response.header["server"] or ""):find("^Xerox_MicroServer") + and response.body + and response.body:find("Xerox", 1, true) + and response.body:find("/js/deviceStatus.dhtml", 1, true) + and response.body:find("/tabsFrame.dhtml", 1, true) + and get_tag(response.body, "frame", {src="/tabsframe%.dhtml$"}) + end, + login_combos = { + {username = "admin", password = "1111"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, + url.absolute(path, "properties/upgrade/m_software.dhtml"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + 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%f[%s][^>]-%shref%s*=%s*(['\"])config%.html%1[^>]*>view printer configuration</a>") + end, + login_combos = { + {username = "", password = "1234"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "authorize"), + nil, {["0"]=pass}) + return resp.status == 200 + and (resp.body or ""):find(">Access Granted.", 1, true) + end +}) + +table.insert(fingerprints, { + 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_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + 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 get_refresh_url(response.body, "^wt2parser%.cgi%?home_%w+$") + end, + login_combos = { + {username = "Administrator", password = ""}, + {username = "Administrator", password = "Fiery.1"} + }, + login_check = function (host, port, path, user, pass) + 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> + ]] + soapmsg = soapmsg:gsub("%f[^\0\n]%s+", "") + 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 +}) + +--- +--Storage +--- +table.insert(fingerprints, { + name = "Areca RAID", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "Raid Console" + end, + login_combos = { + {username = "admin", password = "0000"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "Asustor ADM", + cpe = "cpe:/o:asustor:data_master", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and get_refresh_url(response.body, "^portal/%?%x+$") + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {account=user, + password=pass, + ["two-step-auth"]="true"} + local resp = http_post_simple(host, port, + url.absolute(path, "portal/apis/login.cgi?act=login&_dc=" .. stdnse.clock_ms()), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.success and jout.account == user + end +}) + +table.insert(fingerprints, { + name = "HP StorageWorks SMU", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and response.body + and response.body:find("checkAuthentication", 1, true) + and get_tag(response.body, "script", {src="^js/js_brandstrings%.js$"}) + 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, "api/"), + {header=header}, "/api/login/" .. creds) + return resp.status == 200 + and (resp.header["command-status"] or ""):find("^1 ") + end +}) + +table.insert(fingerprints, { + name = "HP 3PAR SSMC", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("StoreServ Management Console", 1, true) + and response.body:lower():find("<title>storeserv management console") + and get_tag(response.body, "link", {href="^ssmc/css/"}) + end, + login_combos = { + {username = "", password = ""}, + {username = "3paradm", password = "3pardata"}, + {username = "3parcust", password = "3parInServ"} + }, + login_check = function (host, port, path, user, pass) + if user == "" then + local resp = http_get_simple(host, port, + url.absolute(path, "foundation/REST/trustedservice/admincredentials")) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.isAdminPasswordSet == false + end + local header = {["Accept"]="application/json, text/plain, */*", + ["Content-Type"]="application/json;charset=utf-8"} + local jin = {username=user, + password=pass, + adminLogin=false, + authLoginDomain="LOCAL"} + json.make_object(jin) + local resp = http_post_simple(host, port, + url.absolute(path, "foundation/REST/sessionservice/sessions"), + {header=header}, json.generate(jin)) + return resp.status == 201 + and (resp.header["location"] or ""):find("/foundation/REST/sessionservice/sessions/%w+$") + end +}) + +table.insert(fingerprints, { + 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("[^<]-%sibm storwize v3700%s*") + 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 +}) + +table.insert(fingerprints, { + name = "NAS4Free", + cpe = "cpe:/a:nas4free:nas4free", + category = "storage", + paths = { + {path = "/login.php"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("NAS4Free", 1, true) + and response.body:find("?channels=#nas4free", 1, true) + end, + login_combos = { + {username = "admin", password = "nas4free"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, path, nil, + {username=user,password=pass}) + return resp.status == 302 + and resp.header["location"] == "index.php" + end +}) + +table.insert(fingerprints, { + name = "Netgear ReadyNAS RAIDiator", + cpe = "cpe:/o:netgear:raidiator", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and get_refresh_url(response.body, "/shares/$") + and response.body:lower():find("netgear") + end, + login_combos = { + {username = "admin", password = "netgear1"}, + {username = "admin", password = "infrant1"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, url.absolute(path, "shares/"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Netgear ReadyNAS OS 6", + category = "storage", + paths = { + {path = "/admin/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "ReadyNAS Admin" + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "Netgear ReadyDATA OS", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return http_auth_realm(response) == "ReadyDATAOS" + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + return try_http_auth(host, port, path, user, pass, true) + end +}) + +table.insert(fingerprints, { + name = "OpenMediaVault", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("openmediavault", 1, true) + and response.body:lower():find("%ssrc%s*=%s*(['\"])[^'\"]-js/omv/rpc%.js%1") + end, + login_combos = { + {username = "admin", password = "openmediavault"} + }, + login_check = function (host, port, path, user, pass) + local header = {["Accept"]="application/json, */*", + ["Content-Type"]="application/json"} + local jin = {service="Session", + method="login", + params={username=user,password=pass}, + options=json.NULL} + json.make_object(jin) + local resp = http_post_simple(host, port, url.absolute(path, "rpc.php"), + {header=header}, json.generate(jin)) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.response + and jout.response.authenticated and jout.response.username == user + end +}) + +table.insert(fingerprints, { + name = "Pure Storage", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Pure Storage", 1, true) + and response.body:lower():find("pure storage ", 1, true) + and get_tag(response.body, "form", {onsubmit="^pure%.page%.login%("}) + end, + login_combos = { + {username = "pureuser", password = "pureuser"} + }, + login_check = function (host, port, path, user, pass) + local jin = {username=user, + password=pass, + handler="session.query", + operation="login"} + json.make_object(jin) + local resp = http_post_simple(host, port, url.absolute(path, "login"), + nil, {json=json.generate(jin)}) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.userSession and jout.userSession.user == user + end +}) + +table.insert(fingerprints, { + name = "Quest DR", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Quest Software", 1, true) + and response.body:lower():find("<cui-login-screen>", 1, true) + end, + login_combos = { + {username = "administrator", password = "St0r@ge!"} + }, + login_check = function (host, port, path, user, pass) + local header = {["Accept"]="application/json, text/plain, */*", + ["Content-Type"]="application/json;charset=utf-8"} + local jin = {jsonrpc="2.0", + method="Logon", + params={UserName=user,Password=pass}, + id=1} + json.make_object(jin) + local resp = http_post_simple(host, port, + url.absolute(path, "ws/v1.0/jsonrpc"), + {header=header}, json.generate(jin)) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + if not (jstatus and jout.result) then return false end + for _, obj in ipairs(jout.result.objects or {}) do + if obj.SessionCookie then return true end + end + return false + end +}) + +table.insert(fingerprints, { + name = "Seagate BlackArmor NAS (var.1)", + cpe = "cpe:/o:seagate:blackarmor_nas_*", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Seagate", 1, true) + and response.body:lower():find("<title>seagate nas - ", 1, true) + and get_tag(response.body, "input", {name="^p_user$"}) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local form = {p_user=user, + p_pass=pass, + lang="en", + xx=1, + loginnow="Login"} + local resp = http_post_simple(host, port, path, nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/admin/system_status.php?", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Seagate BlackArmor NAS (var.2)", + cpe = "cpe:/o:seagate:blackarmor_nas_*", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("BlackArmor", 1, true) + and response.body:find("/index.php/mv_login/validate_user", 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, "index.php/mv_login/validate_user"), + {header={["Accept"]="text/html, text/plain, */*"}}, + {username=user,password=pass}) + return resp.status == 302 + and (resp.header["location"] or ""):find("/index.php/mv_home/admin_dashboard", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Toshiba Canvio", + category = "storage", + paths = { + {path = "/login.php"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("Canvio", 1, true) + and response.body:find("/sconfig/cgi/hook_login.php", 1, true) + end, + login_combos = { + {username = "admin", password = "admin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local session, pageid = resp1.body:match("%Wfunction%s+mkPOSTParam%s*%(" + .. "[^}]-%Wvar%s+s%s*=%s*['\"](%x+)" + .. "[^}]-%Wvar%s+p%s*=%s*['\"](%x+)") + local action = resp1.body:match("%WpostParam%.aCtIoN%s*=%s*['\"](%x+)") + if not (session and action) then return false end + local form2 = {rn = math.random(1000000000000000,9999999999999999), + session = session, + pageid = pageid, + aCtIoN = action, + UsErNaMe = user, + PaSsWoRD = pass} + local resp2 = http_post_simple(host, port, + url.absolute(path, "sconfig/cgi/hook_login.php"), + {cookies="PHPSESSID="..session}, form2) + if not (resp2.status == 200 and resp2.body) then return false end + local jstatus, jout = json.parse(resp2.body) + return jstatus and jout.err == 0 + end +}) + +table.insert(fingerprints, { + name = "Western Digital My Cloud", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and get_cookie(response, "PHPSESSID", "^%x+$") + and response.body + and response.body:find("/cgi-bin/login_mgr.cgi", 1, true) + and response.body:find("%Wcmd:%s*(['\"])wd_login%1") + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, path) + if not (resp1.status == 200 and resp1.body) then return false end + local form = {cmd="wd_login", + username=user, + pwd=base64.enc(pass), + port=""} + local resp2 = http_post_simple(host, port, + url.absolute(path, "cgi-bin/login_mgr.cgi"), + {cookies=resp1.cookies}, form) + return resp2.status == 200 + and (resp2.body or ""):find("<config>.*<res>[1-9]</res>.*</config>") + end +}) + +table.insert(fingerprints, { + name = "WiseGiga", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("WISEGIGA", 1, true) + and response.body:lower():find("<title>wisegiga", 1, true) + and get_tag(response.body, "a", {href="/webfolder/$"}) + end, + login_combos = { + {username = "guest", password = "guest09#$"}, + {username = "root", password = "admin09#$"} + }, + login_check = function (host, port, path, user, pass) + local form = {id=user, + passwd=pass, + remember_check=0, + sel_lang="en"} + local resp = http_post_simple(host, port, + url.absolute(path, "webfolder/login_check.php"), + nil, form) + return resp.status == 200 + and (resp.body or ""):find("%Wlocation%.href%s*=%s*(['\"])[Mm]ain%.php%1") + end +}) + +table.insert(fingerprints, { + name = "D-Link SharePort Web Access", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return have_openssl + and response.status == 200 + and (response.header["server"] or ""):find(" WEBACCESS/.- DIR%-%d+") + and response.body + and response.body:find("hex_hmac_md5", 1, true) + and response.body:lower():find("d%-link systems[^<]+ login") + end, + login_combos = { + {username = "admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local get_lurl = function () + return url.absolute(path, "dws/api/Login?" + .. math.floor(stdnse.clock_ms())) + end + local resp1 = http_get_simple(host, port, get_lurl()) + if not (resp1.status == 200 and resp1.body) then return false end + local jstatus, jout = json.parse(resp1.body) + if not (jstatus and jout.uid and jout.challenge) then return false end + local auth = stdnse.tohex(openssl.hmac("MD5", pass, user .. jout.challenge)) + local resp2 = http_post_simple(host, port, get_lurl(), + {cookies = "uid=" .. jout.uid}, + {id=user, password=auth}) + if not (resp2.status == 200 and resp2.body) then return false end + jstatus, jout = json.parse(resp2.body) + return jstatus and jout.status == "ok" + end +}) + +table.insert(fingerprints, { + name = "EMC VMAX vApp Manager", + category = "storage", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("VMAX", 1, true) + and response.body:lower():find("[^<]+ vmax") + and get_refresh_url(response.body, "/SE/?$") + end, + login_combos = { + {username = "smc", password = "smc"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "SE/app"), + nil, {user=user, passwd=pass}) + return resp.status == 200 + and get_cookie(resp, "JSESSIONID", ".") + and (resp.body or ""):find("=%s*['\"]login=success&") + end +}) + +--- +--Virtualization systems +--- +table.insert(fingerprints, { + 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_auth(host, port, + url.absolute(path, "folder?dcPath=ha-datacenter"), + user, pass, false) + end +}) + +table.insert(fingerprints, { + name = "VMware vCloud Connector", + category = "virtualization", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if not (response.status == 200 + and response.body + and response.body:find("com.vmware.vami.", 1, true) + and get_tag(response.body, "script", {src="^com%.vmware%.vami%.CoreWrapper%."})) then + return false + end + local resp = http_get_simple(host, port, + url.absolute(path, "service/core/view-deploy.xml")) + return resp.status == 200 + and resp.body + and resp.body:find("Core", 1, true) + and get_tag(resp.body, "property", {value="^vCloud Connector Node$"}) + end, + login_combos = { + {username = "admin", password = "vmware"} + }, + login_check = function (host, port, path, user, pass) + local header = {Authorization="Basic " .. base64.enc(user .. ":" .. pass), + CIMProtocolVersion="1.0", + CIMOperation="MethodCall", + CIMMethod=urlencode_all("CreateSessionToken"):upper(), + CIMObject=urlencode_all("root/cimv2:VAMI_Authentication"):upper(), + ["Content-Type"]="application/xml; charset=UTF-8"} + local msg = [[ + + + + + + + + + + + + + + + + ]] + msg = msg:gsub("^%s+", ""):gsub("\n%s*", "") + local resp = http_post_simple(host, port, url.absolute(path, "cimom"), + {header=header}, msg) + return resp.status == 200 + and (resp.body or ""):find("[^<]+ explorer") + end, + login_combos = { + {username = "Admin", password = ""} + }, + login_check = function (host, port, path, user, pass) + local form = {action="login", + token="", + loginUsername=user, + loginPassword=pass, + language="en"} + local resp = http_post_simple(host, port, path, nil, form) + return resp.status == 302 + and (resp.header["location"] or ""):find("/home%.php$") + and get_cookie(resp, "avctSessionId", "^%d+$") + end +}) + +table.insert(fingerprints, { + name = "Bomgar Appliance", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + if response.header["server"] ~= "Bomgar" then return false end + local resp = http_get_simple(host, port, + url.absolute(path, "appliance/")) + return resp.status == 302 + and get_cookie(resp, "gw_s", "^%w+$") + and (resp.header["location"] or ""):find("/appliance/login%.ns$") + end, + login_combos = { + {username = "admin", password = "password"} + }, + login_check = function (host, port, path, user, pass) + local lurl = url.absolute(path, "appliance/login.ns") + local resp1 = http_get_simple(host, port, lurl) + if not (resp1.status == 200 and resp1.body) then return false end + local formid = get_tag(resp1.body, "input", {type="^hidden$", name="^form_id$", value="^[%w+/]+=*$"}) + if not formid then return false end + local form2 = {fake_password="", + form_id=formid.value, + ["login[username]"]=user, + ["login[password]"]=pass, + ["login[submit]"]="Login", + submit_button="Login"} + local header = {["Referer"]=url.build(url_build_defaults(host, port, {path=lurl}))} + local resp2 = http_post_simple(host, port, lurl, + {cookies=resp1.cookies, header=header}, form2) + return resp2.status == 200 + and get_tag(resp2.body or "", "input", {id="^new_password2$"}) + end +}) + +table.insert(fingerprints, { + name = "Dell ERA", + category = "console", + paths = { + {path = "/applet.html"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "RMC Webserver 2.0" + and response.body + and response.body:find("DRSCAppletInterface.class", 1, true) + end, + login_combos = { + {username = "root", password = "calvin"} + }, + login_check = function (host, port, path, user, pass) + local resp1 = http_get_simple(host, port, url.absolute(path, "cgi/challenge")) + if resp1.status ~= 200 then return false end + local url2 = ("cgi/login?user=%s&hash=%s"):format(user, pass) + local resp2 = http_get_simple(host, port, url.absolute(path, url2), + {cookies=resp1.cookies}) + return resp2.status == 200 + and (resp2.body or ""):find("0x0", 1, true) + end +}) + +table.insert(fingerprints, { + name = "Dell DRAC4", + cpe = "cpe:/h:dell:remote_access_card", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.header["server"] == "RMC Webserver 2.0" + and response.body + and response.body:find("DRAC 4", 1, true) + and response.body:find("%Wvar%s+s_oemProductName%s*=%s*(['\"])DRAC 4%1") + end, + login_combos = { + {username = "root", password = "calvin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "cgi/login"), + nil, {user=user, hash=pass}) + return resp.status == 200 + and (resp.body or ""):find("%Wtop%.location%.replace%(%s*(['\"])[^'\"]-/cgi/main%1%s*%)") + end +}) + +table.insert(fingerprints, { + name = "Dell DRAC5", + cpe = "cpe:/h:dell:remote_access_card", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("%Wtop%.document%.location%.replace%(%s*(['\"])[^'\"]-/cgi%-bin/webcgi/index%1%s*%)") + and response.body:lower():find("remote access controller", 1, true) + end, + login_combos = { + {username = "root", password = "calvin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, + url.absolute(path, "cgi-bin/webcgi/login"), + nil, {user=user, password=pass}) + return resp.status == 302 + and (resp.header["location"] or ""):find("/cgi%-bin/webcgi/main$") + end +}) + +table.insert(fingerprints, { + name = "Dell iDRAC6 (lighttpd)", + cpe = "cpe:/o:dell:idrac6_firmware", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 302 + and (response.header["server"] or ""):find("^lighttpd/%d+%.") + and (response.header["location"] or ""):find("/Applications/dellUI/login%.htm$") + end, + login_combos = { + {username = "root", password = "calvin"} + }, + login_check = function (host, port, path, user, pass) + local form = {WEBVAR_PASSWORD=pass, + WEBVAR_USERNAME=user, + WEBVAR_ISCMCLOGIN=0} + local resp = http_post_simple(host, port, + url.absolute(path, "Applications/dellUI/RPC/WEBSES/create.asp"), + nil, form) + return resp.status == 200 + and (resp.body or ""):match("'USERNAME'%s*:%s*'(.-)'") == user + end +}) + +table.insert(fingerprints, { + name = "Dell iDRAC6/7 (Mbedthis)", + cpe = "cpe:/o:dell:idrac7_firmware", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + local idrac6 = response.status == 301 + and (response.header["server"] or ""):find("^Mbedthis%-Appweb/%d+%.") + local idrac7 = response.status == 302 + and response.header["server"] == "Embedthis-http" + return (idrac6 or idrac7) + and (response.header["location"] or ""):find("/start%.html$") + end, + login_combos = { + {username = "root", password = "calvin"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "data/login"), + nil, {user=user, password=pass}) + return resp.status == 200 + and (resp.body or ""):find("0", 1, true) + end +}) + +table.insert(fingerprints, { + name = "HP 9000 iLO", + cpe = "cpe:/h:hp:integrated_lights-out", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 200 + and response.body + and response.body:find("HP 9000", 1, true) + and response.body:find("loginId", 1, true) + and response.body:lower():find("hp ilo login", 1, true) + end, + login_combos = { + {username = "Admin", password = "Admin"}, + {username = "Oper", password = "Oper"} + }, + login_check = function (host, port, path, user, pass) + local resp = http_post_simple(host, port, url.absolute(path, "signin.html"), + nil, {loginId=user, password=pass}) + return resp.status == 200 + and get_refresh_url(resp.body or "", "/home%.html$") + and get_cookie(resp, "MPID", "^%x+$") + end +}) + +table.insert(fingerprints, { + name = "IBM Integrated Management Module", + cpe = "cpe:/o:ibm:integrated_management_module_firmware", + category = "console", + paths = { + {path = "/"} + }, + target_check = function (host, port, path, response) + return response.status == 301 + and (response.header["location"] or ""):find("/designs/imm/index%.php$") + end, + login_combos = { + {username = "USERID", password = "PASSW0RD"} + }, + login_check = function (host, port, path, user, pass) + local form = {user=user, + password=pass, + SessionTimeout=1200} + local resp = http_post_simple(host, port, url.absolute(path, "data/login"), + nil, form) + if not (resp.status == 200 and resp.body) then return false end + local jstatus, jout = json.parse(resp.body) + return jstatus and jout.authResult == "0" + end +}) + +table.insert(fingerprints, { + name = "Supermicro IPMI", + cpe = "cpe:/o:supermicro:intelligent_platform_management_firmware", + 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 +}) diff --git a/templates/passive/Default_Credentials_1.sh b/templates/passive/Default_Credentials_1.sh new file mode 100644 index 0000000..9ae8d4d --- /dev/null +++ b/templates/passive/Default_Credentials_1.sh @@ -0,0 +1,8 @@ +AUTHOR='@xer0dayz' +VULN_NAME='Default Credentials 1' +FILENAME="$LOOT_DIR/credentials/brutex-$TARGET.txt" +MATCH="password\:\ " +SEVERITY='P1 - CRITICAL' +GREP_OPTIONS='-i' +SEARCH='positive' +SECONDARY_COMMANDS='' \ No newline at end of file diff --git a/templates/passive/Default_Credentials_2.sh b/templates/passive/Default_Credentials_2.sh new file mode 100644 index 0000000..04a19f6 --- /dev/null +++ b/templates/passive/Default_Credentials_2.sh @@ -0,0 +1,8 @@ +AUTHOR='@xer0dayz' +VULN_NAME='Default Credentials 2' +FILENAME="$LOOT_DIR/credentials/brutex-$TARGET-*.txt" +MATCH="password\:\ " +SEVERITY='P1 - CRITICAL' +GREP_OPTIONS='-i' +SEARCH='positive' +SECONDARY_COMMANDS='' \ No newline at end of file