diff --git a/CHANGELOG b/CHANGELOG index 1ca69fa47..f90e999b3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added sip-call-spoof script which spoofs a call to a SIP phone and + detects the action taken by the target. [Hani Benhabiles] + o [NSOCK] Modified multiple scripts that operated against HTTP based services so as to remove false positives that were generated when the target service answers with a 200 response to all requests. [Tom Sellers] diff --git a/scripts/script.db b/scripts/script.db index d1fd87b9f..5ab79f605 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -317,6 +317,7 @@ Entry { filename = "rtsp-url-brute.nse", categories = { "brute", "intrusive", } Entry { filename = "samba-vuln-cve-2012-1182.nse", categories = { "intrusive", "vuln", } } Entry { filename = "servicetags.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "sip-brute.nse", categories = { "brute", "intrusive", } } +Entry { filename = "sip-call-spoof.nse", categories = { "discovery", "intrusive", } } Entry { filename = "sip-enum-users.nse", categories = { "auth", "intrusive", } } Entry { filename = "skypev2-version.nse", categories = { "version", } } Entry { filename = "smb-brute.nse", categories = { "brute", "intrusive", } } diff --git a/scripts/sip-call-spoof.nse b/scripts/sip-call-spoof.nse new file mode 100644 index 000000000..6470cb42f --- /dev/null +++ b/scripts/sip-call-spoof.nse @@ -0,0 +1,174 @@ +local shortport = require "shortport" +local sip = require "sip" +local stdnse = require "stdnse" +local table = require "table" + +description = [[ +Spoofs a call to a SIP phone and detects the action taken by the target. + +This works by sending a fake sip invite request to the target phone and checking +the responses. A response with status code 180 means that the phone is ringing. +The script waits for the next responses until timeout is reached or a special +response is received. Special responses include: Busy (486), Decline (603), +Timeout (408) or Hang up (200). +]] + +--- +--@args sip-call-spoof.ua Source application's user agent. Defaults to +-- Ekiga. +-- +--@args sip-call-spoof.from Caller user ID. Defaults to Home. +-- +--@args sip-call-spoof.extension SIP Extension to send request from. Defaults to +-- 100. +-- +--@args sip-call-spoof.src Source address to spoof. +-- +--@args sip-call-spoof.timeout Time to wait for a response. Defaults to +-- 5 seconds. +-- +-- @usage +-- nmap --script=sip-call-spoof -sU -p 5060 +-- nmap --script=sip-call-spoof -sU -p 5060 --script-args +-- 'sip-call-spoof.ua=Nmap, sip-call-spoof.from=Boss' +-- +--@output +-- 5060/udp open sip +-- | sip-call-spoof: +-- |_ Target hung up. (After 10.9 seconds) + + +author = "Hani Benhabiles" + +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" + +categories = {"discovery", "intrusive"} + + +portrule = shortport.port_or_service(5060, "sip", {"tcp", "udp"}) + + +--- Function that sends an invite request with given parameters. +-- @arg session SIP Session to use. +-- @arg ua User Agent to use. +-- @arg from SIP From field. +-- @arg src Request source address to spoof. +-- @arg extension Request SIP extension. +-- @return status True if we got a response, false else. +-- @return resp Response table if status is true, error string else. +local sendinvite = function(session, ua, from, src, extension) + local request = sip.Request:new(sip.Method.INVITE) + + request:setUri("sip:" .. session.sessdata:getServer()) + request:setUA(ua) + if src then + session.sessdata:setDomain(src) + end + session.sessdata:setUsername(extension) + session.sessdata:setName(from) + request:setSessionData(session.sessdata) + + return session:exch(request) +end + +--- Function that waits for certain responses for an amount of time. +-- @arg session SIP Session to use. +-- @arg timeout Max time to wait for responses other than ringing. +-- @return ringing True if we got a ringing response, false else. +-- @return responsecode Code for the latest meaningful response. +-- could be 180, 200, 486, 408 or 603 +local waitresponses = function(session,timeout) + local response, status, data, responsecode, ringing, waittime + local start = nmap.clock_ms() + + while (nmap.clock_ms() - start) < timeout do + status, data = session.conn:recv() + if status then + response = sip.Response:new(data) + responsecode = response:getErrorCode() + waittime = nmap.clock_ms() - start + if responsecode == sip.Error.RING then + ringing = true + elseif responsecode == sip.Error.BUSY then + return ringing, sip.Error.BUSY + elseif responsecode == sip.Error.DECLINE then + return ringing, sip.Error.DECLINE, waittime + elseif responsecode == sip.Error.OK then + return ringing, sip.Error.OK, waittime + elseif responsecode == sip.Error.TIMEOUT then + return ringing, sip.Error.OK + end + end + end + if ringing then + return ringing, sip.Error.RING + end +end + +--- Function that spoofs an invite request and listens for responses. +-- @arg session SIP Session to use. +-- @arg ua User Agent to use. +-- @arg from SIP From field. +-- @arg src Request source address to spoof. +-- @arg extension Request SIP extension. +-- @arg timeout Max time to wait for responses other than ringing. +-- @return ringing True if we got a ringing response, false else. +-- @return responsecode Code for the latest meaningful response. +-- could be 180, 200, 486, 408 or 603 +local invitespoof = function(session, ua, from, src, extension, timeout) + + local status, response = sendinvite(session, ua, from, src, extension) + -- check if we got a 100 Trying response. + if status and response:getErrorCode() == 100 then + -- wait for responses + return waitresponses(session, timeout) + end +end + +action = function(host, port) + local status, session + + local ua = stdnse.get_script_args(SCRIPT_NAME .. ".ua") or "Ekiga" + local from = stdnse.get_script_args(SCRIPT_NAME .. ".from") or "Home" + local src = stdnse.get_script_args(SCRIPT_NAME .. ".src") + local extension = stdnse.get_script_args(SCRIPT_NAME .. ".extension") or 100 + local timeout = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) + + -- Default timeout value = 5 seconds. + if timeout then + timeout = timeout * 1000 + else + timeout = 5000 + end + + session = sip.Session:new(host, port) + status = session:connect() + if not status then + return "ERROR: Failed to connect to the SIP server." + end + + local ringing, result, waittime = invitespoof(session, ua, from, src, extension, timeout) + -- If we get a response, we set the port to open. + if result then + if nmap.get_port_state(host, port) ~= "open" then + nmap.set_port_state(host, port, "open") + end + end + + -- We check for ringing to skip false positives. + if ringing then + if result == sip.Error.BUSY then + return stdnse.format_output(true, "Target line is busy.") + elseif result == sip.Error.DECLINE then + return stdnse.format_output(true, ("Target declined the call. (After %.1f seconds)"):format(waittime / 1000)) + elseif result == sip.Error.OK then + return stdnse.format_output(true, ("Target hung up. (After %.1f seconds)"):format(waittime / 1000)) + elseif result == sip.Error.TIMEOUT then + return stdnse.format_output(true, "Ringing, no answer.") + elseif result == sip.Error.RING then + return stdnse.format_output(true, "Ringing, got no answer. (script timeout)") + end + else + stdnse.print_debug(SCRIPT_NAME .. "Target phone didn't ring.") + end +end