1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-07 13:11:28 +00:00

Changed sip-enum-users which now uses brute.lua for extensions enumeration and supports iteration over custom lists and numeric ranges.

This commit is contained in:
kroosec
2012-07-18 12:06:33 +00:00
parent b1555d6cce
commit 6023e253dc
2 changed files with 231 additions and 108 deletions

View File

@@ -1,6 +1,11 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o [NSE] Complete change to sip-enum-users script which now uses brute.lua for
enumeration and supports iterating over custom username lists and numeric
ranges. [Hani Benhabiles]
o Added protocol specific payloads for IPv6 hop-by-hop (0x00), routing (0x2b), o Added protocol specific payloads for IPv6 hop-by-hop (0x00), routing (0x2b),
fragment (0x2c), and destination (0x3c). [Sean Rivera] fragment (0x2c), and destination (0x3c). [Sean Rivera]
o [NSE] Added http-slowloris script which performes a slowloris DoS attack o [NSE] Added http-slowloris script which performes a slowloris DoS attack
against a Web server and reports if it's vulnerable or not. [Aleksandar Nikolic] against a Web server and reports if it's vulnerable or not. [Aleksandar Nikolic]

View File

@@ -2,143 +2,261 @@ local shortport = require "shortport"
local sip = require "sip" local sip = require "sip"
local stdnse = require "stdnse" local stdnse = require "stdnse"
local table = require "table" local table = require "table"
local math = require "math"
local brute = require "brute"
local creds = require "creds"
local unpwdb = require "unpwdb" local unpwdb = require "unpwdb"
description = [[ description = [[
Attempts to enumerate valid user account using SIP (Session Initiation Enumerates a SIP server's valid extensions (users).
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 works by sending REGISTER SIP requests to the server with the
- The script enumerates valid accounts by checking the SIP servers response specified extension and checking for the response status code in order
to the REGISTER request. If TRYING is returned, the account does not to know if an extension is valid. If a response status code is 401 or
exist. If REGISTER is returned the account is valid. 407, it means that the extension is valid and requires authentication. If the
response status code is 200, it means that the extension exists and doesn't
require any authentication while a 403 response status code means that
extension exists but access is forbidden. To skip false positives, the script
begins by sending a REGISTER request for a random extension and checking for
response status code.
]] ]]
--- ---
-- @usage --@args sip-enum-users.minext Extension value to start enumeration from.
-- nmap -sU -p 5060 <target> --script=sip-enum-users -- Defaults to <code>0</code>.
-- --
-- PORT STATE SERVICE --@args sip-enum-users.maxext Extension value to end enumeration at.
-- 5060/udp open|filtered sip -- Defaults to <code>999</code>.
--
--@args sip-enum-users.padding Number of digits to pad zeroes up to.
-- Defaults to <code>0</code>. No padding if this is set to zero.
--
--@args sip-enum-users.users If set, will also enumerate users
-- from <code>userslist</code> file.
--
--@args sip-enum-users.userslist Path to list of users.
-- Defaults to <code>nselib/data/usernames.lst</code>.
--
--@usage
-- nmap --script=sip-enum-users -sU -p 5060 <targets>
--
-- nmap --script=sip-enum-users -sU -p 5060 <targets> --script-args
-- 'sip-enum-users.padding=4, sip-enum-users.minext=1000,
-- sip-enum-users.maxext=9999'
--
--@output
-- 5060/udp open sip
-- | sip-enum-users: -- | sip-enum-users:
-- | Valid SIP accounts -- | Accounts
-- | 1000 -- | 101: Auth required
-- |_ 1001 -- | 120: No auth
-- | Statistics
-- |_ Performed 1000 guesses in 50 seconds, average tps: 20
-- Version 0.1
-- Created 04/03/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
author = "Patrik Karlsson" author = "Hani Benhabiles"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive", "auth"}
categories = {"auth", "intrusive"}
portrule = shortport.port_or_service(5060, "sip", {"tcp", "udp"}) portrule = shortport.port_or_service(5060, "sip", {"tcp", "udp"})
-- Send a register request to the server and returned the unparsed response --- Function that sends register sip request with provided extension
-- @param session instance of Session class -- using the specified session.
-- @param username string containing the name of the user -- @arg sess session to use.
-- @param Used protocol, could be "UDP" or "TCP" -- @arg ext Extension to send register request to.
-- @return status true on success false on failure -- @return status true on success, false on failure.
-- @return response instance of sip.Response (on success) -- @return Response instance on success, error string on failure.
-- @return err string containing the error message (on failure) local registerext = function(sess, ext)
local function register(session, username, protocol) -- set session values
local request = sip.Request:new(sip.Method.REGISTER, protocol) local request = sip.Request:new(sip.Method.REGISTER)
session.sessdata:setUsername(username) request:setUri("sip:" .. sess.sessdata:getServer())
request:setUri("sip:" .. session.sessdata:getServer() ) sess.sessdata:setUsername(ext)
request:setSessionData(session.sessdata) request:setSessionData(sess.sessdata)
local status, response = session:exch(request) return sess:exch(request)
if (not(status)) then return false, response end
return true, response
end end
--- Function that returns a number as string with a number of zeroes padded to
-- the left.
-- @arg num Number to be padded.
-- @arg padding number of digits to pad up to.
-- @return string of padded number.
local padnum = function(num, padding)
-- How many zeroes do we need to add
local n = #tostring(num)
if n >= padding then
return tostring(num)
end
n = padding - n
-- Confirm the server is a valid and supported one return string.rep(tostring(0), n) .. tostring(num)
-- @param host table as passed to the action method end
-- @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() --- Iterator function that returns values from a lower value up to a greater
-- value with zeroes padded up to padding argument.
-- @arg minval Start value.
-- @arg maxval End value.
-- @arg padding number of digits to pad up to.
-- @return string current value.
local numiterator = function(minval, maxval, padding)
local i = minval - 1
return function()
i = i + 1
if i <= maxval then return padnum(i, padding), '' end
end
end
--- Iterator function that returns lines from a file
-- @arg userslist Path to file list in data location.
-- @return status false if error.
-- @return string current line.
local useriterator = function(list)
local f = nmap.fetchfile(list)
if not f then
return false, ("\n ERROR: Couldn't find %s"):format(list)
end
f = io.open(f)
if ( not(f) ) then
return false, ("\n ERROR: Failed to open %s"):format(DEFAULT_ACCOUNTS)
end
return function()
for line in f:lines() do
return line
end
end
end
--- function that tests for 404 status code when sending a REGISTER request
-- with a random sip extension.
-- @arg host Target host table.
-- @arg port Target port table.
local test404 = function(host, port)
local session, status, randext, response
-- Random extension
randext = math.random(1234567,987654321)
session = sip.Session:new(host, port)
status = session:connect()
if not status then
return false, "ERROR: Failed to connect to the SIP server."
end
status, response = registerext(session, randext)
if not status then
return false, "ERROR: No response from the SIP server."
end
if response:getErrorCode() ~= 404 then
return false, "Server not returning 404 for random extension."
end
return true
end
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.session = sip.Session:new(self.host, self.port)
local status = self.session:connect()
if ( not(status) ) then if ( not(status) ) then
return "ERROR: Failed to connect to the SIP server" return false, brute.Error:new( "Couldn't connect to host: " .. err )
end end
return true
end,
local response login = function( self, username, password)
status, response = register(session, user, port.protocol:upper()) -- We are using the "password" values instead of the "username" so we
if ( status ) then -- could benifit from brute.lua passonly option and setPasswordIterator
return true, ( -- function, as we are doing usernames enumeration only and not
response:getHeader("User-Agent") or -- credentials brute forcing.
response:getHeader("Server") local status, response, responsecode
) -- Send REGISTER request for each extension
status, response = registerext(self.session, password)
if status then
responsecode = response:getErrorCode()
-- If response status code is 401 or 407, then extension exists but
-- requires authentication
if responsecode == sip.Error.UNAUTHORIZED or
responsecode == sip.Error.PROXY_AUTH_REQUIRED then
return true, brute.Account:new(password, " Auth required", '')
-- If response status code is 200, then extension exists
-- and requires no authentication
elseif responsecode == sip.Error.OK then
return true, brute.Account:new(password, " No auth", '')
-- If response status code is 200, then extension exists
-- but access is forbidden.
elseif responsecode == sip.Error.FORBIDDEN then
return true, brute.Account:new(password, " Forbidden", '')
end end
return false,brute.Error:new( "Not found" )
return false else
end return false,brute.Error:new( "No response" )
-- Asterisk specific function used to check for valid usernames
-- @param session instance of SIP Session
-- @param username string containing the SIP username
-- @param Used protocol, could be "UDP" or "TCP"
-- @return status true on success, false on failure
-- @return err on failure
local function checkAsteriskUsername(session, username, protocol)
local status, response = register(session, username, protocol)
if ( status and response:getErrorCode() == 401 ) then
return true, "SUCCESS"
end end
return false, "FAILURE" end,
end
-- Table containing a server match and corresponding check function disconnect = function(self)
local detectiontbl = { self.session:close()
{ name="^Asterisk PBX", func=checkAsteriskUsername } return true
end,
} }
action = function(host, port) action = function(host, port)
local accounts = {} local result, lthreads = {}, {}
local status, usernames = unpwdb.usernames() local status, err
if ( not(status) ) then return false, "Failed to load usernames" end local minext = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".minext")) or 0
local minext = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".minext")) or 0
local maxext = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".maxext")) or 999
local padding = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".padding")) or 0
local users = stdnse.get_script_args(SCRIPT_NAME .. ".users")
local usersfile = stdnse.get_script_args(SCRIPT_NAME .. ".userslist")
or "nselib/data/usernames.lst"
local server -- min extension should be less than max extension.
status, server = confirmServer( host, port ) if minext > maxext then
if ( not(status) ) then return "ERROR: maxext should be greater or equal than minext."
return "ERROR: Failed to determine server version" end
-- If not set to zero, number of digits to pad up to should have less or
-- equal the number of digits of max extension.
if padding ~= 0 and #tostring(maxext) > padding then
return "ERROR: padding should be greater or equal to number of digits of maxext."
end end
local checkUsername -- We test for false positives by sending a request for a random extension
for _, item in ipairs( detectiontbl ) do -- and checking if it did return a 404.
if ( server and server:match( item.name ) ) then status, err = test404(host, port)
checkUsername = item.func if not status then
break return err
end
end end
if ( not(checkUsername) ) then return ("ERROR: Unsupported server (%s)"):format((server or "")) end local engine = brute.Engine:new(Driver, host, port)
engine.options.script_name = SCRIPT_NAME
for username in usernames do local iterator = numiterator(minext, maxext, padding)
local session = sip.Session:new( host, port ) if users then
usernames, err = useriterator(usersfile)
local status = session:connect() if not usernames then
if ( not(status) ) then return err
return "ERROR: Failed to connect to the SIP server"
end end
-- Concat numbers and users iterators
local status, err = checkUsername( session, username, port.protocol:upper() ) iterator = unpwdb.concat_iterators(iterator, usernames)
if ( status ) then table.insert( accounts, username ) end
session:close()
end end
engine:setPasswordIterator(iterator)
engine.options.passonly = true
status, result = engine:start()
accounts.name = "Valid SIP accounts" return result
return stdnse.format_output(true, { accounts } )
end end