description = [[ Attempts to enumerate valid user account using SIP (Session Initiation Protocol - http://en.wikipedia.org/wiki/Session_Initiation_Protocol). This protocol is most commonly associated with VoIP sessions. 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 --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 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