summaryrefslogtreecommitdiffstats
path: root/scripts/sip-call-spoof.nse
blob: 67524b611b083c08d59dcf3ab9d9f24773e70e50 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
local nmap = require "nmap"
local shortport = require "shortport"
local sip = require "sip"
local stdnse = require "stdnse"

description = [[
Spoofs a call to a SIP phone and detects the action taken by the target (busy, declined, hung up, etc.)

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
-- <code>Ekiga</code>.
--
--@args sip-call-spoof.from Caller user ID. Defaults to <code>Home</code>.
--
--@args sip-call-spoof.extension SIP Extension to send request from. Defaults to
-- <code>100</code>.
--
--@args sip-call-spoof.src Source address to spoof.
--
--@args sip-call-spoof.timeout Time to wait for a response. Defaults to
-- <code>5s</code>
--
-- @usage
-- nmap --script=sip-call-spoof -sU -p 5060 <targets>
-- nmap --script=sip-call-spoof -sU -p 5060 --script-args
-- 'sip-call-spoof.ua=Nmap, sip-call-spoof.from=Boss' <targets>
--
--@output
-- 5060/udp open  sip
-- | sip-call-spoof:
-- |_  Target hung up. (After 10.9 seconds)


author = "Hani Benhabiles"

license = "Same as Nmap--See https://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 = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))

  -- Default timeout value = 5 seconds.
  timeout = (timeout or 5) * 1000

  session = sip.Session:new(host, port)
  status = session:connect()
  if not status then
    return stdnse.format_output(false, "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.debug1("Target phone didn't ring.")
  end
end