local base64 = require "base64"
local http = require "http"
local json = require "json"
local math = require "math"
local shortport = require "shortport"
local stdnse = require "stdnse"
local table = require "table"
local url = require "url"
local have_openssl, openssl = pcall(require, 'openssl')
---
-- http-default-accounts-fingerprints.lua
-- This file contains fingerprint data for http-default-accounts.nse
--
-- STRUCTURE:
-- * 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)
-- * login_check - Login function of the target
---
-- Recursively copy a table.
-- Only recurs when a value is a table, other values are copied by assignment.
local function tcopy (t)
local tc = {};
for k,v in pairs(t) do
if type(v) == "table" then
tc[k] = tcopy(v);
else
tc[k] = v;
end
end
return tc;
end
---
-- Requests given path using http.get() but disabling cache and redirects.
-- @param host The host to connect to
-- @param port The port to connect to
-- @param path The path to retrieve
-- @param options [optional] A table of HTTP request options
-- @return A response table (see library http.lua for description)
---
local function http_get_simple (host, port, path, options)
local opts = tcopy(options or {})
opts.bypass_cache = true
opts.no_cache = true
opts.redirect_ok = false
return http.get(host, port, path, opts)
end
---
-- Requests given path using http.post() but disabling cache and redirects.
-- (The current implementation of http.post() does not use either; this is
-- a defensive wrapper to guard against future problems.)
-- @param host The host to connect to
-- @param port The port to connect to
-- @param path The path to retrieve
-- @param options [optional] A table of HTTP request options
-- @param postdata A string or a table of data to be posted
-- @return A response table (see library http.lua for description)
---
local function http_post_simple (host, port, path, options, postdata)
local opts = tcopy(options or {})
opts.no_cache = true
opts.redirect_ok = false
return http.post(host, port, path, opts, nil, postdata)
end
---
-- Requests given path using basic authentication.
-- @param host Host table
-- @param port Port table
-- @param path Path to request
-- @param user Username for Basic Auth
-- @param pass Password for Basic Auth
-- @param digest_auth Digest Authentication
-- @return True if login in was successful
---
local function try_http_basic_login(host, port, path, user, pass, digest_auth)
local credentials = {username = user, password = pass, digest = digest_auth}
local resp = http_get_simple(host, port, path, {auth=credentials})
return resp.status
and resp.status ~= 401
and resp.status ~= 403
and resp.status ~= 404
end
---
-- Tries to login with a http post, if the FAIL string is not found
-- we assume login in was successful
-- @param host Host table
-- @param port Port table
-- @param target Target file
-- @param failstr String shown when login in fails
-- @param params Post parameters
-- @param follow_redirects True if you want redirects to be followed
-- @return True if login in was successful
---
local function try_http_post_login(host, port, path, target, failstr, params, follow_redirects)
local resp = http_post_simple(host, port, url.absolute(path, target), nil, params)
if not resp.status then return false end
local status = tonumber(resp.status) or 0
if follow_redirects and ( status > 300 and status < 400 ) then
resp = http_get_simple(host, port, url.absolute(path, resp.header.location))
end
if resp.status and resp.status ~= 404 and not(http.response_contains(resp, failstr)) then
return true
end
return false
end
---
-- Returns authentication realm advertised in an HTTP response
-- @param response HTTP response object, such as a result from http.get()
-- @return realm found in response header WWW-Authenticate
-- (or nil if not present)
---
local function http_auth_realm(response)
local auth = response.header["www-authenticate"] or ""
return auth:match('%srealm%s*=%s*"([^"]*)')
end
---
-- Tests whether an HTTP response sets a named cookie with a given value
-- @param response a standard HTTP response object
-- @param name a case-insensitive cookie name that must be set
-- @param pattern to validate the cookie value
-- @return cookie value if such a cookie is found
---
local function sets_cookie(response, name, pattern)
name = name:lower()
for _, ck in ipairs(response.cookies or {}) do
if ck.name:lower() == name then
return (not pattern or ck.value:find(pattern)) and ck.value
end
end
return false
end
---
-- Generates default scheme, host, and port components for a parsed URL.
--
-- This filter function generates the scheme, host, and port components from
-- the standard 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 = 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;"):format(c:byte()) end)
end
fingerprints = {}
---
--WEB
---
table.insert(fingerprints, {
-- Version 0.8.8a
name = "Cacti",
cpe = "cpe:/a:cacti:cacti",
category = "web",
paths = {
{path = "/"},
{path = "/cacti/"}
},
target_check = function (host, port, path, response)
return response.status == 200
and (sets_cookie(response, "Cacti") or sets_cookie(response, "CactiEZ"))
end,
login_combos = {
{username = "admin", password = "admin"}
},
login_check = function (host, port, path, user, pass)
return try_http_post_login(host, port, path, "index.php",
"%sname%s*=%s*(['\"]?)login_password%1[%s>]",
{action="login", login_username=user, login_password=pass})
end
})
table.insert(fingerprints, {
-- Version 2.0.6
name = "Zabbix",
cpe = "cpe:/a:zabbix:zabbix",
category = "web",
paths = {
{path = "/zabbix/"}
},
target_check = function (host, port, path, response)
return response.status == 200 and sets_cookie(response, "zbx_sessionid")
end,
login_combos = {
{username = "admin", password = "zabbix"}
},
login_check = function (host, port, path, user, pass)
local resp = http_post_simple(host, port, url.absolute(path, "index.php"), nil,
{request="", name=user, password=pass, enter="Sign in"})
return resp.status == 302 and resp.header["location"] == "dashboard.php"
end
})
table.insert(fingerprints, {
-- Version 0.7, 1.0.1
name = "Xplico",
category = "web",
paths = {
{path = "/"}
},
target_check = function (host, port, path, response)
return response.status == 302 and sets_cookie(response, "Xplico")
end,
login_combos = {
{username = "admin", password = "xplico"},
{username = "xplico", password = "xplico"}
},
login_check = function (host, port, path, user, pass)
local lurl = url.absolute(path, "users/login")
-- harvest all hidden fields from the login form
local resp1 = http_get_simple(host, port, lurl)
if resp1.status ~= 200 then return false end
local html = (resp1.body or ""):match('