1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-09 14:11:29 +00:00

o [NSE] Added a SIP library and two new scripts sip-brute.nse and

sip-user-enum.nse providing brute and user enumeration support for the SIP
  protocol. [Patrik]
This commit is contained in:
patrik
2011-05-09 18:00:52 +00:00
parent 19da29942c
commit e8c5640dda
5 changed files with 1089 additions and 0 deletions

View File

@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o [NSE] Added a SIP library and two new scripts sip-brute.nse and
sip-user-enum.nse providing brute and user enumeration support for the SIP
protocol. [Patrik]
o [NSE] Added xmpp.nse, which collects XMPP server information [Vasiliy Kulikov] o [NSE] Added xmpp.nse, which collects XMPP server information [Vasiliy Kulikov]
o [NSE] Added broadcast-avahi-dos.nse, which tries to detect if the o [NSE] Added broadcast-avahi-dos.nse, which tries to detect if the

833
nselib/sip.lua Executable file
View File

@@ -0,0 +1,833 @@
--- A SIP library supporting a limited subset of SIP commands and methods
--
-- The library currently supports the following methods:
-- * REGISTER, INVITE & OPTIONS
--
-- Overview
-- --------
-- The library consists of the following classes:
--
-- o SessionData
-- - Holds session data for the SIP session
--
-- o Session
-- - Contains application functionality related to the implemented
-- SIP methods.
--
-- o Connection
-- - A class containing code related to socket communication.
--
-- o Response
-- - A class containing code for handling SIP responses
--
-- o Request
-- - A class containing code for handling SIP requests
--
-- o Util
-- - A class containing static utility functions
--
-- o SIPAuth
-- - A class containing code related to SIP Authentication
--
-- o Helper
-- - A class containing code used as a primary interface by scripts
--
--
-- @author "Patrik Karlsson <patrik@cqure.net>"
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
--
-- @args sip.timeout - specifies the session (socket) timeout in seconds
-- Version 0.1
-- Created 2011/03/30 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
module(... or "sip", package.seeall)
-- Method constants
Method = {
ACK = "ACK",
INVITE = "INVITE",
OPTIONS = "OPTIONS",
REGISTER = "REGISTER",
}
-- Error constants
Error = {
TRYING = 100,
RING = 180,
OK = 200,
UNAUTHORIZED = 401,
FORBIDDEN = 403,
PROXY_AUTH_REQUIRED = 407,
}
-- The SessionData class
SessionData = {
--- Creates a new instance of sessiondata
-- @return o an instance of SessionData
new = function(self, o)
local o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
--- Sets the session username
-- @param user string containing the username
setUsername = function(self, user) self.user = user end,
--- Sets the session password
-- @param pass string containing the password
setPassword = function(self, pass) self.pass = pass end,
--- Sets the SIP domain
-- @param domain string containing the SIP domain
setDomain = function(self, domain) self.domain = domain end,
--- Sets the ip and port of the remote server
-- @param host string containing the ip of the remote server
-- @param port number containing the port of the remote server
setServer = function(self, host, port) self.server = { host = host, port = port } end,
--- Sets the ip and port of the client
-- @param host string containing the ip of the client
-- @param port number containing the port of the client
setClient = function(self, host, port) self.client = { host = host, port = port } end,
--- Sets the SIP users Full Name
-- @param name string containing the full name of the user
setName = function(self, name) self.name = name end,
--- Retrieves the username
-- @return user string containing the sessions username
getUsername = function(self) return self.user end,
--- Retrieves the session password
-- @return pass string containing the session password
getPassword = function(self) return self.pass end,
--- Retrieves the SIP domain
-- @return domain string containing the SIP domain
getDomain = function(self) return self.domain end,
--- Retrieves the client IP and port
-- @return host string containing the client IP
-- @return port number containing the client port
getClient = function(self) return self.client.host, self.client.port end,
--- Retrieves the server IP and port
-- @return host string containing the server IP
-- @return port number containing the server port
getServer = function(self) return self.server.host, self.server.port end,
--- Retrieves the SIP users full name
-- @return name string containing the users full name
getName = function(self) return self.name or "Nmap NSE" end,
}
-- The session class holds the code necessary to register a SIP session
Session = {
--- Creates a new session instance
-- @param host table containing the remote host to connect to
-- @param port table containing the remote port to connect to
-- @param sessdata instance of SessionData
-- @param options table containing zero or more of the following options
-- <code>expires</code> - the expire value in seconds
-- <code>timeout</code> - the socket timeout in seconds
-- @return o containing a new instance of the Session class
new = function(self, host, port, sessdata, options)
local o = {}
setmetatable(o, self)
self.__index = self
o.expires = (options and options.expires) or 300
o.conn = Connection:new(host,port)
o.cseq = (options and options.cseq) or 1234
local timeout = ( ( options and options.timeout ) and
options.timeout * 1000 ) or 5000
o.conn.socket:set_timeout( timeout )
o.sessdata = sessdata or sip.SessionData:new()
return o
end,
--- Connect the session
-- @return true on success, false on failure
-- @return err string containing error message
connect = function(self)
local status, err = self.conn:connect()
if (not(status)) then
return false, "ERROR: Failed to connect to server"
end
local status, lhost, lport, rhost, rport = self.conn.socket:get_info()
if ( not(status) ) then
return false, "Failed to retreive socket information"
end
self.sessdata:setClient(lhost, lport)
self.sessdata:setServer(rhost, rport)
return true
end,
--- Closes the session
-- TODO: We should probably send some "closing" packets here
-- @return true on success, false on failure
close = function(self) return self.conn:close() end,
--- Sends and SIP invite
-- @param uri
invite = function(self, uri)
local request = Request:new(Method.INVITE)
local callid = Util.get_random_string(20)
local lhost, _ = self.sessdata:getClient()
local tm = os.time()
local uri = (uri and uri:match("^sip:.*@.*")) or
("sip:%s@%s"):format(uri, self.sessdata:getDomain())
request:setUri(uri)
request:setSessionData(self.sessdata)
request:setCallId(callid)
local data = {}
table.insert(data, "v=0")
table.insert(data, ("o=- %s %s IN IP4 %s"):format(tm, tm, lhost))
table.insert(data, "s=-")
table.insert(data, ("c=IN IP4 %s"):format(lhost))
table.insert(data, "t=0 0")
table.insert(data, "m=audio 49174 RTP/AVP 0")
table.insert(data, "a=rtpmap:0 PCMU/8000")
request:setContent(stdnse.strjoin("\r\n", data))
request:setContentType("application/sdp")
local status, response = self:exch(request)
if ( not(status) ) then return false, response end
local errcode = response:getErrorCode()
if ( Error.PROXY_AUTH_REQUIRED == errcode or
Error.UNAUTHORIZED == errcode ) then
-- Send an ACK to the server
request:setMethod(Method.ACK)
local status, err = self.conn:send( tostring(request) )
if ( not(status) ) then return status, "ERROR: Failed to send request" end
-- Send an authenticated INVITE to the server
request:setMethod(Method.INVITE)
self.cseq = self.cseq + 1
status, data = self:authenticate(request, response)
if ( not(status) ) then return false, "SIP Authentication failed" end
response = Response:new(data)
-- read a bunch of 180 Ringing and 100 Trying requests, until we get a 200 OK
while ( response:getErrorCode() ~= Error.OK ) do
status, data = self.conn:recv()
if ( not(status) ) then return status, "ERROR: Failed to receive response" end
response = Response:new(data)
end
end
return true
end,
--- Prepares and sends the challenge response authentication to the server
-- @param request instance of the request object requiring authentication
-- @param authdata string containing authentication data
-- @return status true on success false on failure
-- @return err string containing an error message if status is false
authenticate = function(self, request, response)
local rhost, _ = self.sessdata:getServer()
local auth_header, auth_data = response:getAuthData()
local auth = SipAuth:new(auth_data)
auth:setUsername(self.sessdata:getUsername())
auth:setPassword(self.sessdata:getPassword())
auth:setMethod(request.method)
auth:setUri(("sip:%s"):format(rhost))
if ( auth_header == "WWW-Authenticate" ) then
request:setWWWAuth(auth:createResponse())
else
request:setProxyAuth(auth:createResponse())
end
request:setCseq(self.cseq)
local status, err = self.conn:send( tostring(request) )
if ( not(status) ) then return status, "ERROR: Failed to send request" end
local data
status, data = self.conn:recv()
if ( not(status) and data ~= "TIMEOUT" ) then
return status, "ERROR: Failed to receive response"
end
return status, data
end,
--- Sends a SIP Request and receives the Response
-- @param request instance of Request
-- @return status true on success, false on failure
-- @return resp containing a new Response instance
-- err containing error message if status is false
exch = function(self, request)
request:setCseq(self.cseq)
local status, err = self.conn:send( tostring(request) )
if ( not(status) ) then return status, "ERROR: Failed to send request" end
local status, data = self.conn:recv()
if ( not(status) ) then return status, "ERROR: Failed to receive response" end
return true, Response:new(data)
end,
--- Sends a register request to the server
-- @return status true on success, false on failure
-- @return msg string containing the error message (if status is false)
register = function(self)
local request = Request:new(Method.REGISTER)
local callid = Util.get_random_string(20)
request:setUri("sip:" .. self.sessdata:getServer())
request:setSessionData(self.sessdata)
request:setCallId(callid)
request:setExpires(self.expires)
request:setAllow({"PRACK","INVITE","ACK","BYE","CANCEL","UPDATE",
"SUBSCRIBE","NOTIFY","REFER","MESSAGE","OPTIONS"})
request:setContentLength(0)
local status, response = self:exch(request)
if (not(status)) then return false, response end
local errcode = response:getErrorCode()
if ( status and errcode == Error.OK ) then
return true, response
elseif ( Error.PROXY_AUTH_REQUIRED == errcode or Error.UNAUTHORIZED == errcode ) then
local data
self.cseq = self.cseq + 1
status, data = self:authenticate(request, response)
response = Response:new(data)
errcode = response:getErrorCode()
if ( not(status) or ( errcode and errcode ~= Error.OK ) ) then
return false, "ERROR: Failed to authenticate"
end
elseif ( Error.FORBIDDEN == errcode ) then
return false, "Authentication forbidden"
else
return false, ("Unhandled error: %d"):format(errcode)
end
return true
end,
--- Sends an option request to the server and handles the response
-- @return status true on success, false on failure
-- @return msg string containing the error message (if status is false)
options = function(self)
local req = Request:new(Method.OPTIONS)
local callid = Util.get_random_string(20)
req:setUri("sip:" .. self.sessdata:getServer())
req:setSessionData(self.sessdata)
req:setCallId(callid)
req:setExpires(self.expires)
req:setAllow({"PRACK","INVITE","ACK","BYE","CANCEL","UPDATE",
"SUBSCRIBE","NOTIFY","REFER","MESSAGE","OPTIONS"})
req:addHeader("Accept", "application/sdp")
req:setContentLength(0)
local status, response = self:exch(req)
if (not(status)) then return false, response end
local errcode = response:getErrorCode()
local errmsg = response:getErrorMessage()
local msg = ( errcode and ( errcode .. " " .. errmsg ) )
return ( not(errcode) or errcode == 200), msg
end,
}
-- The connection class contains basic communication code
Connection = {
--- Creates a new SIP Connection
-- @param host table containing the host to connect to
-- @param port table containing the port to connect to
-- @return o containing a new Connection instance
new = function(self, host, port)
local o = {}
setmetatable(o, self)
self.__index = self
o.host = host
o.port = port
o.socket = nmap.new_socket()
return o
end,
--- Connects to the server
-- @return status containing true on success and false on failure
-- @return err containing the error message (if status is false)
connect = function(self)
local status, err = self.socket:connect(self.host, self.port)
if ( status ) then
local status, lhost, lport, _, _ = self.socket:get_info()
if ( status ) then
self.lhost = lhost
self.lport = lport
end
end
return status, err
end,
--- Sends the data over the socket
-- @return status true on success, false on failure
send = function(self, data)
return self.socket:send(data)
end,
--- Receives data from the socket
-- @return status true on success, false on failure
recv = function(self)
return self.socket:receive()
end,
--- Closes the communication channel (socket)
-- @return true on success false on failure
close = function(self)
return self.socket:close()
end,
--- Retrieves the client ip and port
-- @return lhost string containing the local ip
-- @return lport number containing the local port
getClient = function(self) return self.lhost, self.lport end,
--- Retrieves the server ip and port
-- @return rhost string containing the server ip
-- @return rport number containing the server port
getServer = function(self) return ( self.host.ip or self.host ), ( self.port.number or self.port ) end,
}
-- The response class holds the necessary methods and parameters to parse a response
Response = {
--- Creates a new Response instance
-- @param str containing the data as received over the socket
-- @return o table containing a new Response instance
new = function(self, str)
local o = {}
setmetatable(o, self)
self.__index = self
o.tbl = stdnse.strsplit("\r\n", str)
return o
end,
--- Retrieves a given header value from the response
-- @param name string containing the name of the header
-- @return value string containing the header value
getHeader = function(self,name)
for _, line in ipairs(self.tbl) do
local header, value = line:match("^(.-): (.*)$")
if ( header == name ) then return value end
end
end,
--- Returns the error code from the SIP response
-- @return err number containing the error code
getErrorCode = function(self)
return tonumber(self.tbl[1]:match("SIP/%d%.%d (%d+)"))
end,
--- Returns the error message returned by the server
-- @return errmsg string containing the error message
getErrorMessage = function(self)
return self.tbl[1]:match("^SIP/%d%.%d %d+ (.+)$")
end,
--- Returns the message method
-- @return method string containing the method
getMethod = function(self)
return self.tbl[1]:match("^(.-)%s.*SIP/2%.0$")
end,
--- Returns the authentication data from the SIP response
-- @return auth string containing the raw authentication data
getAuthData = function(self)
local auth = self:getHeader("WWW-Authenticate") or self:getHeader("Proxy-Authenticate")
if ( auth ) then
return ( self:getHeader("WWW-Authenticate") and
"WWW-Authenticate" or
"Proxy-Authenticate"), auth
end
end,
--- Retrieves the current sequence number
-- @return cseq number containing the current sequence number
getCSeq = function(self)
local cseq = self:getHeader("CSeq")
cseq = (cseq and cseq:match("^(%d+)"))
return (cseq and tonumber(cseq))
end,
}
-- The request class holds the necessary functions and parameters for a basic SIP request
Request = {
--- Creates a new Request instance
-- @param method string containing the request method to use
-- @return o containing a new Request instance
new = function(self, method)
local o = {}
setmetatable(o, self)
self.__index = self
o.ua = "Nmap NSE"
o.protocol = "UDP"
o.maxfwd = 70
o.method = method
return o
end,
--- Sets the sessiondata so that session information may be fetched
-- @param data instance of SessionData
setSessionData = function(self, data) self.sessdata = data end,
--- Adds a custom header to the request
-- @param name string containing the header name
-- @param value string containing the header value
addHeader = function(self, name, value)
self.headers = self.headers or {}
table.insert(self.headers, ("%s: %s"):format(name, value))
end,
--- Sets the SIP uri
-- @param uri string containing the SIP uri
setUri = function(self, uri) self.uri = uri end,
--- Sets an error
-- @param code number containing the error code
-- @param msg string containing the error message
setError = function(self, code, msg) self.error = { code = code, msg = msg } end,
--- Sets the request method
-- @param method string containing a valid SIP method (@see Method constant)
setMethod = function(self, method) self.method = method end,
--- Sets the sequence number
-- @param seq number containing the sequence number to set
setCseq = function(self, seq) self.cseq = seq end,
--- Sets the allow header
-- @param allow string containing all of the allowed SIP methods
setAllow = function(self, allow) self.allow = stdnse.strjoin(", ", allow) end,
--- Sets the request content data
-- @param string containing the content data
setContent = function(self, content) self.content = content end,
--- Sets the requests' content type
-- @param t string containing the content type
setContentType = function(self, t) self.content_type = t end,
--- Sets the supported SIP methods
-- @param supported string containing the supported methods
setSupported = function(self, supported) self.supported = supported end,
--- Sets the content-length of the SIP request
-- @param len number containing the length of the actual request
setContentLength = function(self, len) self.length = len end,
--- Sets the expires header of the SIP request
-- @param expires number containing the expire value
setExpires = function(self, expires) self.expires = expires end,
--- Sets the User Agent being used to connect to the SIP server
-- @param ua string containing the User-Agent name (defaults to Nmap NSE)
setUA = function(self, ua) self.ua = ua end,
--- Sets the caller ID information of the SIP request
-- @param cid string containing the callers id
setCallId = function(self, cid) self.cid = cid end,
--- Sets the maximum forwards allowed of this request
-- @param maxfwd number containing the maximum allowed forwards
setForwards = function(self, maxfwd) self.maxfwd = maxfwd end,
--- Sets the proxy authentication data
-- @param auth string containing properly formatted proxy authentication data
setProxyAuth = function(self, auth) self.proxyauth = auth end,
--- Sets the www authentication data
-- @param auth string containing properly formatted proxy authentication data
setWWWAuth = function(self, auth) self.wwwauth = auth end,
--- Specifies the network protocol being used
-- @param proto should be either "UDP" or "TCP"
setProtocol = function(self, proto)
assert( proto == "UDP" or proto == "TCP", ("Unsupported protocol %s"):format(proto))
self.protocol = proto
end,
--- Converts the request to a String suitable to be sent over the socket
-- @return ret string containing the complete request for sending over the socket
__tostring = function(self)
local data = {}
local branch = "z9hG4bK" .. Util.get_random_string(25)
-- must be at least 32-bit unique
self.from_tag = self.from_tag or Util.get_random_string(20)
local sessdata = self.sessdata
local lhost, lport = sessdata:getClient()
local rhost, rport = sessdata:getServer()
local name, user, domain = sessdata:getName(), sessdata:getUsername(), sessdata:getDomain()
assert(self.method, "No method specified")
assert(self.maxfwd, "Max forward not set")
-- if no domain was specified use the remote host instead
domain = domain or rhost
if ( self.error ) then
table.insert(data, ("SIP/2.0 %s %d"):format(self.error.msg, self.error.code))
else
if ( self.method == Method.ACK ) then
table.insert(data, ("%s %s:%d SIP/2.0"):format(self.method, self.uri, rport))
else
table.insert(data, ("%s %s SIP/2.0"):format(self.method, self.uri))
end
end
table.insert(data, ("Via: SIP/2.0/%s %s:%d;rport;branch=%s"):format(self.protocol, lhost, lport, branch))
table.insert(data, ("Max-Forwards: %d"):format(self.maxfwd))
table.insert(data, ("From: \"%s\" <sip:%s@%s>;tag=%s"):format(name, user, domain, self.from_tag))
if ( self.method == Method.INVITE ) then
table.insert(data, ("To: <sip:%s@%s>"):format(user, domain))
else
table.insert(data, ("To: \"%s\" <sip:%s@%s>"):format(name, user, domain))
end
table.insert(data, ("Call-ID: %s"):format(self.cid))
if ( self.error and self.error.code == Error.OK ) then
table.insert(data, ("CSeq: %d OPTIONS"):format(self.cseq))
else
table.insert(data, ("CSeq: %d %s"):format(self.cseq, self.method))
end
if ( self.method ~= Method.ACK ) then
table.insert(data, ("User-Agent: %s"):format(self.ua))
table.insert(data, ("Contact: \"%s\" <sip:%s@%s:%d>"):format(name, user, lhost, lport))
if ( self.expires ) then
table.insert(data, ("Expires: %d"):format(self.expires))
end
if ( self.allow ) then
table.insert(data, ("Allow: %s"):format(self.allow))
end
if ( self.supported ) then
table.insert(data, ("Supported: %s"):format(self.supported))
end
if ( not(self.error) ) then
if ( self.proxyauth ) then
table.insert(data, ("Proxy-Authorization: %s"):format(self.proxyauth))
end
if ( self.wwwauth ) then
table.insert(data, ("Authorization: %s"):format(self.wwwauth))
end
end
self.length = (self.content and #self.content +2 or 0)
if ( self.headers ) then
for _, val in ipairs(self.headers) do
table.insert(data, val)
end
end
if ( self.content_type ) then
table.insert(data, ("Content-Type: %s"):format(self.content_type))
end
table.insert(data, ("Content-Length: %d"):format(self.length))
table.insert(data, "")
if ( self.content ) then table.insert(data, self.content) end
table.insert(data, "")
else
self.length = (self.content and #self.content +2 or 0)
table.insert(data, ("Content-Length: %d"):format(self.length))
table.insert(data, "")
end
return stdnse.strjoin("\r\n", data)
end,
}
-- A minimal Util class with supporting functions
Util = {
--- Generates a random string of the requested length.
-- @param length (optional) The length of the string to return. Default: 8.
-- @param set (optional) The set of letters to choose from. Default: upper, lower, numbers, and underscore.
-- @return The random string.
get_random_string = function(length, set)
if(length == nil) then
length = 8
end
if(set == nil) then
set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"
end
local str = ""
-- Seed the random number, if we haven't already
if (not(nmap.registry.sip) or not(nmap.registry.sip.seeded)) then
math.randomseed(os.time())
nmap.registry.sip = {}
nmap.registry.sip.seeded = true
end
for i = 1, length, 1 do
local random = math.random(#set)
str = str .. string.sub(set, random, random)
end
return str
end
}
-- The SIP authentication class, supporting MD5 digest authentication
SipAuth = {
--- Creates a new SipAuth instance
-- @param auth string containing the auth data as received from the server
new = function(self, auth)
local o = {}
setmetatable(o, self)
self.__index = self
o.auth = auth
return o
end,
--- Sets the username used for authentication
-- @param username string containing the name of the user
setUsername = function(self, username) self.username = username end,
--- Sets the password used for authentication
-- @param password string containing the password of the user
setPassword = function(self, password) self.password = password end,
--- Sets the method used for authentication
-- @param method string containing the method (Usually REGISTER)
setMethod = function(self, method) self.method = method end,
--- Sets the uri used for authentication
-- @param uri string containing the uri (Usually sip:<ip>)
setUri = function(self, uri) self.uri = uri end,
--- Processes and parses a challenge as received from the server
parseChallenge = function(self)
if ( not(self.auth) ) then return end
self.nonce = self.auth:match("nonce=[\"]([^,]-)[\"]")
self.algorithm = self.auth:match("algorithm=[\"]*(.-)[\"]*,")
self.realm = self.auth:match("realm=[\"]([^,]-)[\"]")
assert(self.algorithm:upper() == "MD5",
("Unsupported algorithm detected in authentication challenge (%s)"):format(self.algorithm:upper()))
end,
--- Calculates the authentication response
-- @return reponse string containing the authentication response
calculateResponse = function(self)
if ( not(self.nonce) or not(self.algorithm) or not(self.realm) ) then
self:parseChallenge()
end
assert(self.username, "SipAuth: No username specified")
assert(self.password, "SipAuth: No password specified")
assert(self.method, "SipAuth: No method specified")
assert(self.uri, "SipAuth: No uri specified")
local result
if ( self.algorithm == "MD5" ) then
local HA1 = select(2, bin.unpack("H16", openssl.md5(self.username .. ":" .. self.realm .. ":" .. self.password)))
local HA2 = select(2, bin.unpack("H16", openssl.md5(self.method .. ":" .. self.uri)))
result = openssl.md5(HA1:lower() .. ":" .. self.nonce ..":" .. HA2:lower())
end
return select(2, bin.unpack("H16", result)):lower()
end,
--- Creates the complete authentication response
-- @return auth string containing the complete authentication digest
createResponse = function(self)
local response = self:calculateResponse()
return ("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\"," ..
" uri=\"%s\", response=\"%s\", algorithm=%s"):format(self.username, self.realm,
self.nonce, self.uri, response, self.algorithm)
end,
}
-- The Helper class used as main script interface
Helper = {
--- Creates a new instance of the Helper class
-- @param host table containing the remote host
-- @param port table containing the remote port
-- @param options table containing any options to pass along to the
-- session (@see Session:new for more details)
-- @return o containing a new instance of the Helper class
new = function(self, host, port, options)
local o = {}
setmetatable(o, self)
self.__index = self
local timeout = stdnse.get_script_args("sip.timeout")
if ( timeout ) then options.timeout = timeout end
o.sessdata = SessionData:new()
o.session = Session:new(host, port, o.sessdata, options)
return o
end,
--- Connects the helper instance
connect = function(self) return self.session:connect() end,
--- Disconnects and closes the helper instance
close = function(self) return self.session:close() end,
--- Sets the credentials used when performing authentication
-- @param username string containing the username to use for authentication
-- @param password string containing the password to use for authentication
setCredentials = function(self, username, password)
self.sessdata:setUsername(username)
self.sessdata:setPassword(password)
end,
--- Sets the SIP domain
-- @param domain string containing the domain name
setDomain = function(self, domain) self.sessdata:setDomain(domain) end,
--- Register the UAC with the server
-- @param options table containing zero or more options
-- (@see Session:register for more details)
-- @return status true on success, false on failure
-- @return msg containing the error message if status is false
register = function(self, options)
local status, response = self.session:register(options)
if ( not(status) ) then return false, response end
return true
end,
options = function(self) return self.session:options() end,
--- Attempts to INVITE the user at uri to a call
-- @param uri string containing the sip uri
-- @return status true on success, false on failure
invite = function(self, uri)
return self.session:invite(uri)
end,
}

View File

@@ -141,6 +141,8 @@ Entry { filename = "resolveall.nse", categories = { "discovery", "safe", } }
Entry { filename = "rmi-dumpregistry.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "rmi-dumpregistry.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "rpcinfo.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "rpcinfo.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "servicetags.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "servicetags.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "sip-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "sip-enum-users.nse", categories = { "auth", "intrusive", } }
Entry { filename = "skypev2-version.nse", categories = { "version", } } Entry { filename = "skypev2-version.nse", categories = { "version", } }
Entry { filename = "smb-brute.nse", categories = { "auth", "intrusive", } } Entry { filename = "smb-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "smb-check-vulns.nse", categories = { "dos", "exploit", "intrusive", "vuln", } } Entry { filename = "smb-check-vulns.nse", categories = { "dos", "exploit", "intrusive", "vuln", } }
@@ -193,3 +195,4 @@ Entry { filename = "wdb-version.nse", categories = { "default", "discovery", "ve
Entry { filename = "whois.nse", categories = { "discovery", "external", "safe", } } Entry { filename = "whois.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "wsdd-discover.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "wsdd-discover.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "x11-access.nse", categories = { "auth", "default", "safe", } } Entry { filename = "x11-access.nse", categories = { "auth", "default", "safe", } }
Entry { filename = "xmpp.nse", categories = { "default", "discovery", "safe", } }

106
scripts/sip-brute.nse Executable file
View File

@@ -0,0 +1,106 @@
description = [[
Attempts to brute-force SIP accounts
]]
---
-- @usage
-- nmap -sU -p 5060 <target> --script=sip-brute
--
-- PORT STATE SERVICE
-- 5060/udp open|filtered sip
-- | sip-brute:
-- | Accounts
-- | 1000:password123 => Login correct
-- | Statistics
-- |_ Performed 5010 guesses in 3 seconds, average tps: 1670
-- Version 0.1
-- Created 04/03/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive", "auth"}
require "shortport"
require "sip"
require "brute"
portrule = shortport.port_or_service(5060, "sip", "udp")
Driver = {
new = function(self, host, port)
local o = {}
setmetatable(o, self)
self.__index = self
o.host = host
o.port = port
return o
end,
connect = function( self )
self.helper = sip.Helper:new(self.host, self.port, { expires = 0 })
local status, err = self.helper:connect()
if ( not(status) ) then
return "ERROR: Failed to connect to SIP server"
end
return true
end,
login = function( self, username, password )
self.helper:setCredentials(username, password)
status, err = self.helper:register()
if ( not(status) ) then
-- The 3CX System has an anti-hacking option that triggers after
-- a certain amount of guesses. This protection basically prevents
-- any connection from the offending IP at an application level.
if ( err:match("^403 Forbidden") ) then
local err = brute.Error:new("The systems seems to have blocked our IP")
err:setAbort( true )
return false, err
end
return false, brute.Error:new( "Incorrect password" )
end
return true, brute.Account:new(username, password, "OPEN")
end,
disconnect = function(self) return self.helper:close() end,
}
-- Function used to check if we can distinguish existing from non-existing
-- accounts. In order to do so we send a semi-random username and password
-- and interpret the response. Some servers will respond as if the login
-- was successful which makes it impossible to tell successfull logins
-- from non-existing accounts apart.
local function checkBadUser(host, port)
math.randomseed( os.time() )
local user = "baduser-" .. math.random(10000)
local pass = "badpass-" .. math.random(10000)
local helper = sip.Helper:new(host, port, { expires = 0 })
print(user, pass)
local status, err = helper:connect()
if ( not(status) ) then return false, "ERROR: Failed to connect" end
helper:setCredentials(user, pass)
local status, err = helper:register()
helper:close()
return status, err
end
action = function(host, port)
local force = stdnse.get_script_args("sip-brute.force")
if ( not(force) ) then
local status = checkBadUser(host, port)
if ( status ) then
return "\nERROR: Cannot detect non-existing user accounts, this will result in:\n" ..
" * Non-exisiting accounts being detected as found\n" ..
" * Passwords for existing accounts being correctly detected\n\n" ..
"Supply the sip-brute.force argument to override"
end
end
local engine = brute.Engine:new(Driver, host, port)
local status, result = engine:start()
return result
end

143
scripts/sip-enum-users.nse Normal file
View File

@@ -0,0 +1,143 @@
description = [[
Attempts to enumerate valid user account using SIP. Currently only the SIP
server Asterisk is supported.
* Asterisk
- The script enumerates valid accounts by checking the SIP servers response
to the REGISTER request. If TRYING is returned, the account does not
exist. If REGISTER is returned the account is valid.
]]
---
-- @usage
-- nmap -sU -p 5060 <target> --script=sip-brute
--
-- PORT STATE SERVICE
-- 5060/udp open|filtered sip
-- | sip-enum-users:
-- | Valid SIP accounts
-- | 1000
-- |_ 1001
-- Version 0.1
-- Created 04/03/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive", "auth"}
require "shortport"
require "sip"
require "unpwdb"
portrule = shortport.port_or_service(5060, "sip", "udp")
-- Send a register request to the server and returned the unparsed response
-- @param session instance of Session class
-- @param username string containing the name of the user
-- @return status true on success false on failure
-- @return response instance of sip.Response (on success)
-- @return err string containing the error message (on failure)
local function register(session, username)
local request = sip.Request:new(sip.Method.REGISTER)
local callid = sip.Util.get_random_string(20)
session.sessdata:setUsername(username)
request:setUri("sip:" .. session.sessdata:getServer() )
request:setSessionData(session.sessdata)
request:setCallId(callid)
request:setExpires(0)
request:setAllow({"PRACK","INVITE","ACK","BYE","CANCEL","UPDATE",
"SUBSCRIBE","NOTIFY","REFER","MESSAGE","OPTIONS"})
request:setContentLength(0)
local status, response = session:exch(request)
if (not(status)) then return false, response end
return true, response
end
-- Confirm the server is a valid and supported one
-- @param host table as passed to the action method
-- @param port table as passed to the action method
-- @return status true on success, false on failure
-- @return header string containing the server name
local function confirmServer(host, port)
local user = "nmap_banner_check"
local session = sip.Session:new( host, port )
local status = session:connect()
if ( not(status) ) then
return "ERROR: Failed to connect to the SIP server"
end
local response
status, response = register(session, user)
if ( status ) then
return true, (
response:getHeader("User-Agent") or
response:getHeader("Server")
)
end
return false
end
-- Asterisk specific function used to check for valid usernames
-- @param session instance of SIP Session
-- @param username string containing the SIP username
-- @return status true on success, false on failure
-- @return err on failure
local function checkAsteriskUsername(session, username)
local status, response = register(session, username)
if ( status and response:getErrorCode() == 401 ) then
return true, "SUCCESS"
end
return false, "FAILURE"
end
-- Table containing a server match and corresponding check function
local detectiontbl = {
{ name="^Asterisk PBX", func=checkAsteriskUsername }
}
action = function(host, port)
local accounts = {}
local status, usernames = unpwdb.usernames()
if ( not(status) ) then return false, "Failed to load usernames" end
local server
status, server = confirmServer( host, port )
if ( not(status) ) then
return "ERROR: Failed to determine server version"
end
local checkUsername
for _, item in ipairs( detectiontbl ) do
if ( server and server:match( item.name ) ) then
checkUsername = item.func
break
end
end
if ( not(checkUsername) ) then return ("ERROR: Unsupported server (%s)"):format((server or "")) end
for username in usernames do
local session = sip.Session:new( host, port )
local status = session:connect()
if ( not(status) ) then
return "ERROR: Failed to connect to the SIP server"
end
local status, err = checkUsername( session, username )
if ( status ) then table.insert( accounts, username ) end
session:close()
end
accounts.name = "Valid SIP accounts"
return stdnse.format_output(true, { accounts } )
end