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