PenetrationTestingScripts/nmap_scripts/infiltrator.nse

1225 lines
38 KiB
Lua

local comm = require "comm"
local string = require "string"
local table = require "table"
local shortport = require "shortport"
local nmap = require "nmap"
local stdnse = require "stdnse"
local U = require "lpeg-utility"
local http = require "http"
local snmp = require "snmp"
local sslcert = require "sslcert"
local tls = require "tls"
local url = require "url"
local json = require "json"
description = [[
Search SD-WAN products from SDWAN NewHope research project database by
- server name
- http titles
- snmp descriptions
- ssl certificates
The search database is based on census.md document with SD-WAN products search queries.
Also this script is based on:
- http-server-header NSE script by Daniel Miller
- http-title NSE script by Diman Todorov
- snmp-sysdescr NSE script by Thomas Buchanan
- ssl-cert NSE script by David Fifield
Installation
$ git clone https://github.com/sdnewhop/sdwan-infiltrator
$ cd /sdwan-infiltrator/
$ sudo cp * /usr/share/nmap/scripts/
$ sudo nmap --script infiltrator --script-args infiltrator.version=true -sS -sU -p U:161,T:80,443,8008,8080,8443 <target> or -iL <targets.txt>
]]
--
-- @usage
-- nmap --script=infiltrator.nse -sS -sU -p U:161,T:80,443,8008,8080,8443 <target> or -iL <targets.txt>
--
-- @output
-- | infiltrator:
-- | status: success
-- | method: server
-- | product: <product name>
-- | host_addr: ...
-- | host_port: 443
-- |_ version: ...
-- ...
-- | infiltrator:
-- | status: success
-- | method: title
-- | product: <product name>
-- | host_addr: ...
-- | host_port: 443
-- |_ version: ...
-- ...
-- | infiltrator:
-- | status: success
-- | method: snmp
-- | product: <product name>
-- | host_addr: ...
-- | host_port: 161
-- |_ version: ...
-- ...
-- | infiltrator:
-- | status: success
-- | method: SSL certificate
-- | product: <product name>
-- | host_addr: ...
-- | host_port: 443
-- |_ version: ...
author = "sdnewhop"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"}
portrule = shortport.portnumber({80, 161, 443, 8008, 8080, 8443}, {"tcp", "udp"}, {"open"})
SDWANS_BY_SSL_TABLE = {
["Cisco SD-WAN"] = {"Viptela Inc"},
["Versa Analytics"] = {"versa%-analytics"},
["Versa Director"] = {"director%-1", "versa%-director"},
["Riverbed SteelHead"] = {"Riverbed Technology"},
["Silver Peak Unity Orchestrator"] = {"Silverpeak GMS"},
["Silver Peak Unity EdgeConnect"] = {"silver%-peak", "Silver Peak Systems Inc"},
["CloudGenix SD-WAN"] = {"CloudGenix Inc."},
["Talari SD-WAN"] = {"Talari", "Talari Networks"},
["InfoVista SALSA"] = {"SALSA Portal"},
["Barracuda CloudGen Firewall"] = {"Barracuda CloudGen Firewall", "Barracuda Networks"},
["Viprinet Virtual VPN Hub"] = {"Viprinet"},
["Citrix Netscaler SD-WAN"] = {"Citrix Systems"},
["Fortinet FortiGate SD-WAN"] = {"FGT%-", "FortiGate"}
}
SDWANS_BY_SNMP_TABLE = {
["Fatpipe SYMPHONY SD-WAN"] = {"Linux Fatpipe"},
["Versa Analytics"] = {"Linux versa%-analytics"},
["Juniper Networks Contrail SD-WAN"] = {"Juniper Networks, Inc. srx"},
["Aryaka Network Access Point"] = {"Aryaka Networks Access Point"},
["Arista Networks EOS"] = {"Arista Networks EOS"},
["Viprinet Virtual VPN Hub"]= {"Viprinet VPN Router"}
}
SDWANS_BY_TITLE_TABLE = {
["VMWare NSX SD-WAN"] = {"VeloCloud", "VeloCloud Orchestrator"},
["TELoIP VINO SD-WAN"] = {"Teloip Orchestrator API"},
["Fatpipe SYMPHONY SD-WAN"] = {"WARP"},
["Cisco SD-WAN"] = {"Viptela vManage", "Cisco vManage"},
["Versa Flex VNF"] = {"Flex VNF"},
["Versa Director"] = {"Versa Director Login"},
["Riverbed SteelConnect"] = {"SteelConnect Manager", "Riverbed AWS Appliance"},
["Riverbed SteelHead"] = {"amnesiac Sign in"},
["Citrix NetScaler SD-WAN VPX"] = {"Citrix NetScaler SD%-WAN %- Login"},
["Citrix NetScaler SD-WAN Center"] = {"SD%-WAN Center | Login"},
["Citrix Netscaler SD-WAN"] = {"DC | Login"},
["Silver Peak Unity Orchestrator"] = {"Welcome to Unity Orchestrator"},
["Silver Peak Unity EdgeConnect"] = {"Silver Peak Appliance Management Console"},
["Ecessa WANworX SD-WAN"] = {"Ecessa"},
["Nuage Networks SD-WAN (VNS)"] = {"SD%-WAN Portal", "Architect", "VNS portal"},
["Juniper Networks Contrail SD-WAN"] = {"Log In %- Juniper Networks Web Management"},
["Talari SD-WAN"] = {"AWS"},
["Aryaka Network Access Point"] = {"Aryaka Networks", "Aryaka, Welcome"},
["InfoVista SALSA"] = {"SALSA Login"},
["Huawei SD-WAN"] = {"Agile Controller"},
["Sonus SBC Management Application"] = {"SBC Management Application"},
["Sonus SBC Edge"] = {"Sonus SBC Edge Web Interface"},
["Arista Networks EOS"] = {"Arista Networks EOS"},
["128 Technology Networking Platform"] = {"128T Networking Platform"},
["Gluware Control"] = {"Gluware Control"},
["Barracuda CloudGen Firewall"] = {"Barracuda CloudGen Firewall"},
["Viprinet Virtual VPN Hub"] = {"Viprinet %- AdminDesk %- Login"},
["Viprinet Traffic Tools"] = {"Viprinet traffic tools"},
["Cradlepoint SD-WAN"] = {"Login :: CR4250%-PoE", "Login :: AER2200%-600M"},
["Brain4Net Orchestrator"] = {"B4N ORC"},
["Fortinet FortiManager"] = {"FortiManager%-VM64"}
}
SDWANS_BY_SERVER_TABLE = {
["Versa Director"] = {"Versa Director"},
["Versa Analytics"] = {"Versa%-Analytics%-Server"},
["Barracuda CloudGen Firewall"] = {"Barracuda CloudGen Firewall"},
["Viprinet Virtual VPN Hub"] = {"ViprinetHubReplacement", "Viprinet"}
}
-------------------------------------------------------------------------------
-- version gathering block
-------------------------------------------------------------------------------
local function vbrain(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/api/version"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
found, matches = http.response_contains(response, '0;url%=(.*)"%/%>')
if found == true then
local urltmp = url.parse(matches[1])
urlp = urltmp.path
response = http.generic_request(host, port, "GET", urlp)
try_counter = 1
end
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, '"build":"(.+)",', false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Brain4Net Orchestrator Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vcradlepoint(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/login/?referer=/admin/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, "([0-9.]+[0-9]) .[a-zA-Z]+.[a-zA-Z]+.[0-9]+.[0-9]+:[0-9]+:[0-9]+", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Cradlepoint App Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vcitrix(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
-- stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 30 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, "css%?v%=([.0-9]+)", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Citrix NetScaler Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vfatpipe(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, "<h5>([r.0-9]+)</h5>", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Fatpipe Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vnuage(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, 'ng%-version="([.0-9]+)"', false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Nuage Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vriverbed(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, "web3 v([.0-9]+)", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Riverbed Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vsilverpeak(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
if response.status == 302 then
found, matches = http.response_contains(response, "http.*/([.0-9]+)/", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "SilverPeak Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vsilverpeak_login(host, port)
local output_info = {}
output_info.login = {}
local monitor_check = "/rest/json/login?user=monitor&password=monitor"
local admin_check = "/rest/json/login?user=admin&password=admin"
local resp_monitor = http.get(host, port, monitor_check)
if not resp_monitor.status then
-- force check on 80 port if empty response from 443 (by default)
resp_monitor = http.get(host, 80, monitor_check)
end
if resp_monitor.status == 200 then
table.insert(output_info.login, "Authentication successful (monitor:monitor)")
end
local resp_admin = http.get(host, port, admin_check)
if not resp_admin.status then
-- force check on 80 port if empty response from 443 (by default)
resp_admin = http.get(host, 80, admin_check)
end
if resp_admin.status == 200 then
table.insert(output_info.login, "Authentication successful (admin:admin)")
end
if next(output_info.login) ~= nil then
return output_info, stdnse.format_output(true, output_info)
end
end
local function vsonus_edge(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/cgi/index.php"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, "/style/([.0-9]+)%-[0-9]+%_rel", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Sonus Edge Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vsonus_mgmt(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and (response.status ~= 503 or response.status ~= 200) do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 503 or response.status == 200 then
found, matches = http.response_contains(response, "EMA ([.0-9]+)", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Sonus Mgmt App Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vtalari(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, 'talari%.css%?([_.0-9A-Za-z]+)"', false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Talari Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vversa_analytics(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/versa/app/js/common/constants.js"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
found, matches = http.response_contains(response, '0;url%=(.*)"%/%>')
if found == true then
local urltmp = url.parse(matches[1])
urlp = urltmp.path
response = http.generic_request(host, port, "GET", urlp)
try_counter = 1
end
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, "%/analytics%/([v.0-9]+)%/", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Versa Analytics Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vversa_flex(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/scripts/main-layout/main-layout-controller.js"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, '"versa%-flexvnf%-([.0-9%-a-zA-Z]+)', false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Versa Flex Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function vvmware_nsx(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, "%/vco%-ui.([0-9.]+).", false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "VMware NSX Version: " .. vsdwan)
end
return output_info, stdnse.format_output(true, output_info)
end
local function fortinet(host, port)
local resp_js_path, js_path, resp_js
local conf_build, conf_model, conf_label
local output_info = {}
local version
-- trigger 401 error to find path to js file with version
resp_js_path = http.get(host, port, "/api")
if not resp_js_path.body then
return nil
end
-- search for js file that contains version
js_path = string.match(resp_js_path.body:lower(), "<script src=\"(/%w+/fweb_all.js)")
if not js_path then
return nil
end
stdnse.print_debug("Found js path: " .. js_path)
-- get founded js and grep for version
resp_js = http.get(host, port, js_path)
if not resp_js_path.body then
return nil
end
stdnse.print_debug("Js - founded")
-- parse versions
conf_build = string.match(resp_js.body, "CONFIG_BUILD_NUMBER:(%d+)")
conf_model = string.match(resp_js.body, "CONFIG_MODEL:\"([%w_]+)\"")
conf_label = string.match(resp_js.body, "CONFIG_BUILD_LABEL:\"([%w_]+)\"")
if (not conf_build) or (not conf_model) or (not conf_label) then
return nil
end
output_info = stdnse.output_table()
output_info.vsdwan_version = {}
version = "build " .. conf_build .. ", model " .. conf_model .. " (" .. conf_label .. ")"
table.insert(output_info.vsdwan_version, "Fortinet FortiGate Version: " .. version)
return output_info, stdnse.format_output(true, output_info)
end
local function vversa_analytics_server(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/versa/analytics/version"
local response
local output_info = {}
local vsdwan = ""
local urlp = path
local auth_credentials = {}
auth_credentials.username = 'vanclient'
auth_credentials.password = '88347b9e8s6$90d9f31te366&d5be77'
local options = {}
options.auth = auth_credentials
response = http.generic_request(host, port, "GET", path)
if response.status == 301 or response.status == 302 then
local url_parse_res = url.parse(response.header.location)
urlp = url_parse_res.path
stdnse.print_debug("Status code: " .. response.status)
response = http.generic_request(host,port,"GET", urlp)
end
output_info = stdnse.output_table()
if response == nil then
return fail("Request failed")
end
local try_counter = 1
while try_counter < 6 and response.status ~= 200 do
response = http.generic_request(host, port, "GET", urlp)
found, matches = http.response_contains(response, '0;url%=(.*)"%/%>')
if found == true then
local urltmp = url.parse(matches[1])
urlp = urltmp.path
response = http.generic_request(host, port, "GET", urlp)
try_counter = 1
end
try_counter = try_counter + 1
end
if response.status == 200 then
found, matches = http.response_contains(response, '"release":"([%w.]+)",', false)
if found == true then vsdwan = matches[1] else return nil end
output_info.vsdwan_version = {}
table.insert(output_info.vsdwan_version, "Versa Analytics Server Version: " .. vsdwan)
end
response = http.generic_request(host, 5000, "GET", "/", options)
if response.status == 200 and response.body ~= nil then
output_info.additional_version = response.body
end
response = http.generic_request(host, 5000, "GET", "/analytics/system/info", options)
if response.status == 200 and response.body ~= nil then
status, json_repr = json.parse(response.body)
if status == true then
output_info.sys_info = json_repr
end
end
response = http.generic_request(host, 5000, "GET", "/analytics/tools/status", options)
if response.status == 200 and response.body ~= nil then
status, json_repr = json.parse(response.body)
if status == true then
output_info.sys_status = json_repr
end
end
return output_info, stdnse.format_output(true, output_info)
end
-------------------------------------------------------------------------------
-- version functions call table
-------------------------------------------------------------------------------
VERSION_CALL_TABLE = {
["Citrix NetScaler SD-WAN VPX"] = {version = vcitrix},
["Citrix NetScaler SD-WAN Center"] = {version = vcitrix},
["Citrix Netscaler SD-WAN"] = {version = vcitrix},
["Fatpipe SYMPHONY SD-WAN"] = {version = vfatpipe},
["Nuage Networks SD-WAN (VNS)"] = {version = vnuage},
["Riverbed SteelHead"] = {version = vriverbed},
["Riverbed SteelConnect"] = {version = vriverbed},
["Silver Peak Unity Orchestrator"] = {version = vsilverpeak},
["Silver Peak Unity EdgeConnect"] = {version = vsilverpeak},
["Silver Peak Unity EdgeConnect"] = {version = vsilverpeak_login},
["Sonus SBC Management Application"] = {version = vsonus_mgmt},
["Sonus SBC Edge"] = {version = vsonus_edge},
["Talari SD-WAN"] = {version = vtalari},
["Versa Analytics"] = {version = vversa_analytics},
["Versa Analytics"] = {version = vversa_analytics_server},
["Versa Flex VNF"] = {version = vversa_flex},
["VMWare NSX SD-WAN"] = {version = vvmware_nsx},
["Cradlepoint SD-WAN"] = {version = vcradlepoint},
["Brain4Net Orchestrator"] = {version = vbrain},
["Fortinet FortiGate SD-WAN"] = {version = fortinet}
}
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
local function get_version(product, host, port)
local version = nil
for version_product, _ in pairs(VERSION_CALL_TABLE) do
-- check if product in version list
if version_product == product then
version = VERSION_CALL_TABLE[product].version(host, port)
if version ~= nil then
return version
end
end
end
return version
end
local function ssl_name_to_table(name)
local output = {}
for k, v in pairs(name) do
if type(k) == "table" then
k = stdnse.strjoin(".", k)
end
output[k] = v
end
return output
end
local function collect_results(status, method, product, addr, port, version)
local output_tab = stdnse.output_table()
output_tab.status = status
output_tab.method = method
output_tab.product = product
output_tab.host_addr = addr
output_tab.host_port = port
if version ~= nil then
if version['vsdwan_version'] ~= nil then
parse = version['vsdwan_version'][1]
output_tab.version = string.match(parse, ': (.*)')
end
if version['additional_version'] ~= nil then
output_tab.additional_version = version['additional_version']
end
if version['sys_info'] ~= nil then
output_tab.system_info = version['sys_info']
end
if version['sys_status'] ~= nil then
output_tab.system_status = version['sys_status']
end
if version['login'] ~= nil then
output_tab.login = version['login']
end
end
return output_tab, stdnse.format_output(true, output_tab)
end
local function check_ssl(host, port, version_arg)
if not (shortport.ssl(host, port) or sslcert.isPortSupported(port) or sslcert.getPrepareTLSWithoutReconnect(port)) then
return nil
end
local cert_status, cert = sslcert.getCertificate(host, port)
if not cert_status then
return nil
end
ssl_subject = ssl_name_to_table(cert.subject)
if not ssl_subject then
return nil
end
for product, titles in pairs(SDWANS_BY_SSL_TABLE) do
for _, sd_wan_title in ipairs(titles) do
for _, ssl_field in pairs(ssl_subject) do
if string.match(ssl_field:lower(), sd_wan_title:lower()) then
stdnse.print_debug("Matched SSL certificates: " .. ssl_field)
local version = nil
if version_arg then
version = get_version(product, host, port)
end
return collect_results("success", "SSL certificate", product, host.ip, port.number, version)
end
end
end
end
end
local function check_snmp(host, port, version_arg)
if not shortport.portnumber(161, "udp", {"open"}) then
return nil
end
local snmpHelper = snmp.Helper:new(host, port)
snmpHelper:connect()
local status, response = snmpHelper:get({reqId=28428}, "1.3.6.1.2.1.1.1.0")
if not status then
return nil
end
nmap.set_port_state(host, port, "open")
local result = response and response[1] and response[1][1]
if not result then
return nil
end
for product, titles in pairs(SDWANS_BY_SNMP_TABLE) do
for _, sd_wan_title in ipairs(titles) do
if string.match(result:lower(), sd_wan_title:lower()) then
stdnse.print_debug("Matched SNMP banners: " .. product)
-- override snmp port
local version = nil
if version_arg then
if product == "Versa Analytics" then
version = get_version(product, host, 8080)
end
if not version then
version = get_version(product, host, 80)
end
end
return collect_results("success", "snmp banner", product, host.ip, port.number, version)
end
end
end
end
local function check_title(host, port, version_arg)
if not shortport.http(host, port) then
return nil
end
local resp = http.get(host, port, "/")
found, matches = http.response_contains(resp, "top.location='(.+)';")
if found == true then
resp = http.get(host, port, matches[1])
end
-- make redirect if needed
if resp.status == 301 or resp.status == 302 then
local url_parsed = url.parse(resp.header.location)
local redirect_path = url_parsed.path or "/"
-- detect right port to redirect (by location parsing or by default scheme port)
local existed_scheme = nil
if url_parsed.scheme then
existed_scheme = url.get_default_port(url_parsed.scheme)
end
local redirect_port = url_parsed.port or existed_scheme
-- port of last hope (in a case when we can't parse port from location or scheme)
if not redirect_port then
redirect_port = 443
end
if url_parsed.host == host.targetname or url_parsed.host == (host.name ~= '' and host.name) or url_parsed.host == host.ip then
stdnse.print_debug("Redirect: " .. host.ip .. " -> " .. url_parsed.scheme.. "://" .. url_parsed.authority .. url_parsed.path)
resp = http.get(host.ip, redirect_port, redirect_path)
-- redirect to the path from top.location in body (for example, Fortinet, etc.)
found, matches = http.response_contains(resp, "top.location='(.+)';")
if found == true then
resp = http.get(host.ip, redirect_port, matches[1])
end
end
end
if not resp.body then
return nil
end
local title = string.match(resp.body, "<[Tt][Ii][Tt][Ll][Ee][^>]*>([^<]*)</[Tt][Ii][Tt][Ll][Ee]>")
if not title then
return nil
end
stdnse.print_debug("Get title: " .. title)
for product, titles in pairs(SDWANS_BY_TITLE_TABLE) do
for _, sd_wan_title in ipairs(titles) do
if string.match(title:lower(), sd_wan_title:lower()) then
stdnse.print_debug("Matched titles: " .. title)
local version = nil
if version_arg then
version = get_version(product, host, port)
end
return collect_results("success", "http-title", product, host.ip, port.number, version)
end
end
end
end
local function check_server(host, port, version_arg)
if not (shortport.http(host, port) and nmap.version_intensity() >= 7) then
return nil
end
local responses = {}
if port.version and port.version.service_fp then
for _, p in ipairs({"GetRequest", "GenericLines", "HTTPOptions",
"FourOhFourRequest", "NULL", "RTSPRequest", "Help", "SIPOptions"}) do
responses[#responses+1] = U.get_response(port.version.service_fp, p)
end
end
if #responses == 0 then
local socket, result = comm.tryssl(host, port, "GET / HTTP/1.0\r\n\r\n")
if not socket then
return nil
end
socket:close()
responses[1] = result
end
-- Also send a probe with host header if we can. IIS reported to send
-- different Server headers depending on presence of Host header.
local socket, result = comm.tryssl(host, port,
("GET / HTTP/1.1\r\nHost: %s\r\n\r\n"):format(stdnse.get_hostname(host)))
if socket then
socket:close()
responses[#responses+1] = result
end
port.version = port.version or {}
local headers = {}
for _, result in ipairs(responses) do
if string.match(result, "^HTTP/1.[01] %d%d%d") then
port.version.service = "http"
local http_server = string.match(result, "\n[Ss][Ee][Rr][Vv][Ee][Rr]:[ \t]*(.-)\r?\n")
-- Avoid setting version info if -sV scan already got a match
if port.version.product == nil and (port.version.name_confidence or 0) <= 3 then
port.version.product = http_server
end
-- Setting "softmatched" allows the service fingerprint to be printed
nmap.set_port_version(host, port, "softmatched")
if http_server then
headers[http_server] = true
end
end
end
for product, servers in pairs(SDWANS_BY_SERVER_TABLE) do
for _, sd_wan_server in ipairs(servers) do
for recv_server, _ in pairs(headers) do
if string.match(recv_server:lower(), sd_wan_server:lower()) then
stdnse.print_debug("Matched servers: " .. recv_server)
local version = nil
if version_arg then
version = get_version(product, host, port)
end
return collect_results("success", "http-server", product, host.ip, port.number, version)
end
end
end
end
end
local function check_fortinet(host, port, version_arg)
if not shortport.http(host, port) then
return nil
end
local resp = http.get(host, port, "/")
local version = nil
-- make redirect if needed
if resp.status == 301 or resp.status == 302 then
local url = url.parse( resp.header.location )
if url.host == host.targetname or url.host == ( host.name ~= '' and host.name ) or url.host == host.ip then
stdnse.print_debug("Redirect: " .. host.ip .. " -> " .. url.scheme.. "://" .. url.authority .. url.path)
-- extract redirect port
redir_port = string.match(url.authority, ":(%d+)")
stdnse.print_debug("Redirect port is: " .. redir_port)
stdnse.print_debug("Trying to get " .. host.ip .. " at " .. redir_port .. " port")
-- get Fortinet login page at custom port
resp = http.get(host.ip, tonumber(redir_port), "/login")
end
end
if not resp.body then
return nil
end
-- check if it Fortinet or not
if not string.match(resp.body:lower(), "fortinet") then
return nil
end
stdnse.print_debug("Found Fortinet SD-WAN")
-- get version
if version_arg then
version = get_version("Fortinet FortiGate SD-WAN", host.ip, tonumber(redir_port))
end
return collect_results("success", "Fortinet Custom Method", "Fortinet FortiGate SD-WAN", host.ip, redir_port, version)
end
-- main function
action = function(host, port)
version_arg = stdnse.get_script_args(SCRIPT_NAME..".version") or "false"
if version_arg == "true" then
version_arg = true
else
version_arg = false
end
-- check fortinet
if (port.number == 8008) then
local results = check_fortinet(host, port, version_arg)
if results then
return results
end
end
-- get title and server from http/https
if (port.number == 443 or port.number == 80 or port.number == 8080 or port.number == 8443) then
local title_tab = check_title(host, port, version_arg)
if title_tab then
return title_tab
end
local server_tab = check_server(host, port, version_arg)
if server_tab then
return server_tab
end
-- check ssl cert from https
if port.number == 443 then
local ssl_tab = check_ssl(host, port, version_arg)
if ssl_tab then
return ssl_tab
end
end
-- get snmp banner by 161 udp
elseif port.number == 161 then
local snmp_tab = check_snmp(host, port, version_arg)
if snmp_tab then
return snmp_tab
end
end
end