diff --git a/scripts/address-info.nse b/scripts/address-info.nse index ce29dbe04..9c237692d 100644 --- a/scripts/address-info.nse +++ b/scripts/address-info.nse @@ -118,60 +118,60 @@ categories = {"default", "safe"} hostrule = function(host) - return true + return true end -- Match an address (array of bytes) against a hex-encoded pattern. "XX" in the -- pattern is a wildcard. local function matches(addr, pattern) - local octet_patterns + local octet_patterns - octet_patterns = {} - for op in pattern:gmatch("([%xX][%xX])") do - octet_patterns[#octet_patterns + 1] = op - end + octet_patterns = {} + for op in pattern:gmatch("([%xX][%xX])") do + octet_patterns[#octet_patterns + 1] = op + end - if #addr ~= #octet_patterns then - return false - end + if #addr ~= #octet_patterns then + return false + end - for i = 1, #addr do - local a, op + for i = 1, #addr do + local a, op - a = addr[i] - op = octet_patterns[i] - if not (op == "XX" or a == tonumber(op, 16)) then - return false - end - end + a = addr[i] + op = octet_patterns[i] + if not (op == "XX" or a == tonumber(op, 16)) then + return false + end + end - return true + return true end local function get_manuf(mac) - local catch = function() return "Unknown" end - local try = nmap.new_try(catch) - local mac_prefixes = try(datafiles.parse_mac_prefixes()) - local prefix = string.upper(string.format("%02x%02x%02x", mac[1], mac[2], mac[3])) - return mac_prefixes[prefix] or "Unknown" + local catch = function() return "Unknown" end + local try = nmap.new_try(catch) + local mac_prefixes = try(datafiles.parse_mac_prefixes()) + local prefix = string.upper(string.format("%02x%02x%02x", mac[1], mac[2], mac[3])) + return mac_prefixes[prefix] or "Unknown" end local function format_mac(mac) - local out = stdnse.output_table() - out.address = stdnse.format_mac(string.char(table.unpack(mac))) - out.manuf = get_manuf(mac) - return out + local out = stdnse.output_table() + out.address = stdnse.format_mac(string.char(table.unpack(mac))) + out.manuf = get_manuf(mac) + return out end local function format_ipv4(ipv4) - local octets + local octets - octets = {} - for _, v in ipairs(ipv4) do - octets[#octets + 1] = string.format("%d", v) - end + octets = {} + for _, v in ipairs(ipv4) do + octets[#octets + 1] = string.format("%d", v) + end - return stdnse.strjoin(".", octets) + return stdnse.strjoin(".", octets) end local function do_ipv4(addr) @@ -179,120 +179,120 @@ end -- EUI-64 from MAC, RFC 4291. local function decode_eui_64(eui_64) - if eui_64[4] == 0xff and eui_64[5] == 0xfe then - return { bit.bxor(eui_64[1], 0x02), - eui_64[2], eui_64[3], eui_64[6], eui_64[7], eui_64[8] } - end + if eui_64[4] == 0xff and eui_64[5] == 0xfe then + return { bit.bxor(eui_64[1], 0x02), + eui_64[2], eui_64[3], eui_64[6], eui_64[7], eui_64[8] } + end end local function do_ipv6(addr) - local label - local output + local label + local output - output = stdnse.output_table() + output = stdnse.output_table() - if matches(addr, "0000:0000:0000:0000:0000:0000:0000:0001") then - -- ::1 is localhost. Not much to report. - return nil - elseif matches(addr, "0000:0000:0000:0000:0000:0000:XXXX:XXXX") then - -- RFC 4291 2.5.5.1. - local ipv4 = { addr[13], addr[14], addr[15], addr[16] } - return {["IPv4-compatible"]= { ["IPv4 address"] = format_ipv4(ipv4) } } - elseif matches(addr, "0000:0000:0000:0000:0000:ffff:XXXX:XXXX") then - -- RFC 4291 2.5.5.2. - local ipv4 = { addr[13], addr[14], addr[15], addr[16] } - return {["IPv4-mapped"]= { ["IPv4 address"] = format_ipv4(ipv4) } } - elseif matches(addr, "2001:0000:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then - -- Teredo, RFC 4380. - local server_ipv4 = { addr[5], addr[6], addr[7], addr[8] } - -- RFC 5991 makes the flags mostly meaningless. - local flags = addr[9] * 256 + addr[10] - local obs_port = addr[11] * 256 + addr[12] - local obs_client_ipv4 = { addr[13], addr[14], addr[15], addr[16] } - local port, client_ipv4 + if matches(addr, "0000:0000:0000:0000:0000:0000:0000:0001") then + -- ::1 is localhost. Not much to report. + return nil + elseif matches(addr, "0000:0000:0000:0000:0000:0000:XXXX:XXXX") then + -- RFC 4291 2.5.5.1. + local ipv4 = { addr[13], addr[14], addr[15], addr[16] } + return {["IPv4-compatible"]= { ["IPv4 address"] = format_ipv4(ipv4) } } + elseif matches(addr, "0000:0000:0000:0000:0000:ffff:XXXX:XXXX") then + -- RFC 4291 2.5.5.2. + local ipv4 = { addr[13], addr[14], addr[15], addr[16] } + return {["IPv4-mapped"]= { ["IPv4 address"] = format_ipv4(ipv4) } } + elseif matches(addr, "2001:0000:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then + -- Teredo, RFC 4380. + local server_ipv4 = { addr[5], addr[6], addr[7], addr[8] } + -- RFC 5991 makes the flags mostly meaningless. + local flags = addr[9] * 256 + addr[10] + local obs_port = addr[11] * 256 + addr[12] + local obs_client_ipv4 = { addr[13], addr[14], addr[15], addr[16] } + local port, client_ipv4 - -- Invert obs_port. - port = bit.bxor(obs_port, 0xffff) + -- Invert obs_port. + port = bit.bxor(obs_port, 0xffff) - -- Invert obs_client_ipv4. - client_ipv4 = {} - for _, octet in ipairs(obs_client_ipv4) do - client_ipv4[#client_ipv4 + 1] = bit.bxor(octet, 0xff) - end + -- Invert obs_client_ipv4. + client_ipv4 = {} + for _, octet in ipairs(obs_client_ipv4) do + client_ipv4[#client_ipv4 + 1] = bit.bxor(octet, 0xff) + end - output["Server IPv4 address"] = format_ipv4(server_ipv4) - output["Client IPv4 address"] = format_ipv4(client_ipv4) - output["UDP port"] = tostring(port) + output["Server IPv4 address"] = format_ipv4(server_ipv4) + output["Client IPv4 address"] = format_ipv4(client_ipv4) + output["UDP port"] = tostring(port) - return {["Teredo"] = output} - elseif matches(addr, "0064:ff9b:XXXX:XXXX:00XX:XXXX:XXXX:XXXX") then - --IPv4-embedded IPv6 addresses. RFC 6052, Section 2 + return {["Teredo"] = output} + elseif matches(addr, "0064:ff9b:XXXX:XXXX:00XX:XXXX:XXXX:XXXX") then + --IPv4-embedded IPv6 addresses. RFC 6052, Section 2 - --skip addr[9] - if matches(addr,"0064:ff9b:0000:0000:0000:0000:XXXX:XXXX") then - local ipv4 = {addr[13], addr[14], addr[15], addr[16]} - return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} - elseif addr[5] ~= 0x01 then - local ipv4 = {addr[5], addr[6], addr[7], addr[8]} - return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} - elseif addr[6] ~= 0x22 then - local ipv4 = {addr[6], addr[7], addr[8], addr[10]} - return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} - elseif addr[7] ~= 0x03 then - local ipv4 = {addr[7], addr[8], addr[10], addr[11]} - return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} - elseif addr[8] ~= 0x44 then - local ipv4 = {addr[8], addr[10], addr[11], addr[12]} - return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} - elseif addr[10] == 0x00 and addr[11] == 0x00 and addr[12] == 0x00 then - local ipv4 = {addr[13], addr[14], addr[15], addr[16]} - return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} - end - elseif matches(addr, "0000:0000:0000:0000:ffff:0000:XXXX:XXXX") then - -- IPv4-translated IPv6 addresses. RFC 2765, Section 2.1 - return {["IPv4-translated IPv6 address"]= - {["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}} - elseif matches(addr, "XXXX:XXXX:XXXX:XX00:0000:5efe:XXXX:XXXX") then - -- ISATAP. RFC 5214, Appendix A - -- XXXX:XXXX:XXXX:XX00:0000:5EFE:a.b.c.d - return {["ISATAP Modified EUI-64 IPv6 Address"]= - {["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}} - end + --skip addr[9] + if matches(addr,"0064:ff9b:0000:0000:0000:0000:XXXX:XXXX") then + local ipv4 = {addr[13], addr[14], addr[15], addr[16]} + return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} + elseif addr[5] ~= 0x01 then + local ipv4 = {addr[5], addr[6], addr[7], addr[8]} + return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} + elseif addr[6] ~= 0x22 then + local ipv4 = {addr[6], addr[7], addr[8], addr[10]} + return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} + elseif addr[7] ~= 0x03 then + local ipv4 = {addr[7], addr[8], addr[10], addr[11]} + return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} + elseif addr[8] ~= 0x44 then + local ipv4 = {addr[8], addr[10], addr[11], addr[12]} + return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} + elseif addr[10] == 0x00 and addr[11] == 0x00 and addr[12] == 0x00 then + local ipv4 = {addr[13], addr[14], addr[15], addr[16]} + return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}} + end + elseif matches(addr, "0000:0000:0000:0000:ffff:0000:XXXX:XXXX") then + -- IPv4-translated IPv6 addresses. RFC 2765, Section 2.1 + return {["IPv4-translated IPv6 address"]= + {["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}} + elseif matches(addr, "XXXX:XXXX:XXXX:XX00:0000:5efe:XXXX:XXXX") then + -- ISATAP. RFC 5214, Appendix A + -- XXXX:XXXX:XXXX:XX00:0000:5EFE:a.b.c.d + return {["ISATAP Modified EUI-64 IPv6 Address"]= + {["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}} + end - -- These following use common handling for the Interface ID part - -- (last 64 bits). + -- These following use common handling for the Interface ID part + -- (last 64 bits). - if matches(addr, "2002:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then - -- 6to4, RFC 3056. - local ipv4 = { addr[3], addr[4], addr[5], addr[6] } - local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12], - addr[13], addr[14], addr[15], addr[16] }) + if matches(addr, "2002:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then + -- 6to4, RFC 3056. + local ipv4 = { addr[3], addr[4], addr[5], addr[6] } + local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12], + addr[13], addr[14], addr[15], addr[16] }) - label = "6to4" - output["IPv4 address"] = format_ipv4(ipv4) - end + label = "6to4" + output["IPv4 address"] = format_ipv4(ipv4) + end - local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12], - addr[13], addr[14], addr[15], addr[16] }) - if mac then - output["MAC address"] = format_mac(mac) - if not label then - label = "IPv6 EUI-64" - end - end + local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12], + addr[13], addr[14], addr[15], addr[16] }) + if mac then + output["MAC address"] = format_mac(mac) + if not label then + label = "IPv6 EUI-64" + end + end - return {[label]= output} + return {[label]= output} end action = function(host) - local addr_s, addr_t + local addr_s, addr_t - addr_s = host.bin_ip - addr_t = { string.byte(addr_s, 1, #addr_s) } + addr_s = host.bin_ip + addr_t = { string.byte(addr_s, 1, #addr_s) } - if #addr_t == 4 then - return do_ipv4(addr_t) - elseif #addr_t == 16 then - return do_ipv6(addr_t) - end + if #addr_t == 4 then + return do_ipv4(addr_t) + elseif #addr_t == 16 then + return do_ipv6(addr_t) + end end diff --git a/scripts/backorifice-brute.nse b/scripts/backorifice-brute.nse index 0a3c2fd05..44efd105d 100644 --- a/scripts/backorifice-brute.nse +++ b/scripts/backorifice-brute.nse @@ -50,249 +50,249 @@ categories = {"intrusive", "brute"} -- This portrule succeeds only when the open|filtered port is in the port range -- which is specified by the ports script argument portrule = function(host, port) - 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) - return false - end + 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) + return false + 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 - 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") - return false - end + --print out a debug message if port 31337/udp is open + 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") + return false + end - return port.protocol == "udp" and stdnse.in_port_range(port, ports:gsub(",",",") ) and - not(shortport.port_is_excluded(port.number,port.protocol)) + return port.protocol == "udp" and stdnse.in_port_range(port, ports:gsub(",",",") ) and + not(shortport.port_is_excluded(port.number,port.protocol)) end local backorifice = { - new = function(self, host, port) - local o = {} - setmetatable(o, self) - self.__index = self - o.host = host - o.port = port - return o - end, + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + return o + end, - --- Initializes the backorifice object - -- - initialize = function(self) - --create socket - self.socket = nmap.new_socket("udp") - self.socket:set_timeout(self.host.times.timeout * 1000) - return true - end, + --- Initializes the backorifice object + -- + initialize = function(self) + --create socket + self.socket = nmap.new_socket("udp") + self.socket:set_timeout(self.host.times.timeout * 1000) + return true + end, - --- Attempts to send an encrypted PING packet to BackOrifice service - -- - -- @param password string containing password for encryption - -- @param initial_seed number containing initial encryption seed - -- @return status, true on success, false on failure - -- @return err string containing error message on failure - try_password = function(self, password, initial_seed) - --initialize BackOrifice PING packet: |MAGICSTRING|size|packetID|TYPE_PING|arg1|arg_separat|arg2|CRC/disregarded| - local PING_PACKET = bin.pack("A error, singular, partial, continued + --receive info + local output, response, p_type, multi_flag + output = {} + output.name = cmds[i].cmd_name + multi_flag = false + while true do + response = try(socket:receive()) + response = BOcrypt(response,password,initial_seed) + response, p_type = BOunpack(response) -- p_type -> error, singular, partial, continued - if p_type ~= TYPE.ERROR then - local tmp_str = cmds[i].filter(response) - if tmp_str ~= nil then - if cmds[i].p_code==TYPE.PING then - --invalid chars for hostname are allowed on old windows boxes - local BOversion, BOhostname = string.match(tmp_str,"!PONG!(1%.20)!(.*)!") - if BOversion==nil then - --in case of bad PING reply return "" - return - else - --fill up version information - insert_version_info(host,port,BOversion,BOhostname,initial_seed,password) - end - end + if p_type ~= TYPE.ERROR then + local tmp_str = cmds[i].filter(response) + if tmp_str ~= nil then + if cmds[i].p_code==TYPE.PING then + --invalid chars for hostname are allowed on old windows boxes + local BOversion, BOhostname = string.match(tmp_str,"!PONG!(1%.20)!(.*)!") + if BOversion==nil then + --in case of bad PING reply return "" + return + else + --fill up version information + insert_version_info(host,port,BOversion,BOhostname,initial_seed,password) + end + end - table.insert(output,tmp_str) - end + table.insert(output,tmp_str) + end - --singular - if bit.band(p_type,TYPE.PARTIAL_PACKET)==0x00 - and bit.band(p_type,TYPE.CONTINUED_PACKET)==0x00 then break end + --singular + if bit.band(p_type,TYPE.PARTIAL_PACKET)==0x00 + and bit.band(p_type,TYPE.CONTINUED_PACKET)==0x00 then break end - --first - if bit.band(p_type,TYPE.CONTINUED_PACKET)==0x00 then - multi_flag = true - end + --first + if bit.band(p_type,TYPE.CONTINUED_PACKET)==0x00 then + multi_flag = true + end - --last - if bit.band(p_type,TYPE.PARTIAL_PACKET)==0x00 then break end - end + --last + if bit.band(p_type,TYPE.PARTIAL_PACKET)==0x00 then break end + end - end - --gather all responses in table - table.insert(output_all,output) - end + end + --gather all responses in table + table.insert(output_all,output) + end - socket:close() - return stdnse.format_output(true,output_all) + socket:close() + return stdnse.format_output(true,output_all) end diff --git a/scripts/broadcast-eigrp-discovery.nse b/scripts/broadcast-eigrp-discovery.nse index ecc7979f6..03cd9fc1e 100644 --- a/scripts/broadcast-eigrp-discovery.nse +++ b/scripts/broadcast-eigrp-discovery.nse @@ -76,15 +76,15 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "broadcast", "safe"} prerule = function() - if nmap.address_family() ~= 'inet' then - stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) - return false - end - if not nmap.is_privileged() then - stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) - return false - end - return true + if nmap.address_family() ~= 'inet' then + stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) + return false + end + if not nmap.is_privileged() then + stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) + return false + end + return true end @@ -92,22 +92,22 @@ end --@param interface Network interface to use. --@param eigrp_raw Raw eigrp packet. local eigrpSend = function(interface, eigrp_raw) - local srcip = interface.address - local dstip = "224.0.0.10" + local srcip = interface.address + local dstip = "224.0.0.10" - local ip_raw = bin.pack("H", "45c00040ed780000015818bc0a00c8750a00c86b") .. eigrp_raw - 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_dst(ipOps.ip_to_str(dstip)) - eigrp_packet:ip_set_len(#eigrp_packet.buf) - eigrp_packet:ip_count_checksum() + local ip_raw = bin.pack("H", "45c00040ed780000015818bc0a00c8750a00c86b") .. eigrp_raw + 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_dst(ipOps.ip_to_str(dstip)) + eigrp_packet:ip_set_len(#eigrp_packet.buf) + eigrp_packet:ip_count_checksum() - local sock = nmap.new_dnet() - sock:ethernet_open(interface.device) - -- 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") - sock:ethernet_send(eth_hdr .. eigrp_packet.buf) - sock:ethernet_close() + local sock = nmap.new_dnet() + sock:ethernet_open(interface.device) + -- 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") + sock:ethernet_send(eth_hdr .. eigrp_packet.buf) + sock:ethernet_close() end @@ -116,48 +116,48 @@ end --@param timeout Ammount of time to listen for. --@param responses Table to put valid responses into. local eigrpListener = function(interface, timeout, responses) - local condvar = nmap.condvar(responses) - local routers = {} - local status, l3data, response, p, eigrp_raw, _ - local start = nmap.clock_ms() - -- 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 listener = nmap.new_socket() - listener:set_timeout(500) - listener:pcap_open(interface.device, 1024, true, filter) + local condvar = nmap.condvar(responses) + local routers = {} + local status, l3data, response, p, eigrp_raw, _ + local start = nmap.clock_ms() + -- 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 listener = nmap.new_socket() + listener:set_timeout(500) + listener:pcap_open(interface.device, 1024, true, filter) - -- For each EIGRP packet captured until timeout - while (nmap.clock_ms() - start) < timeout do - response = {} - response.tlvs = {} - status, _, _, l3data = listener:pcap_receive() - if status then - p = packet.Packet:new(l3data, #l3data) - eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1) - -- Check if it is an EIGRPv2 Update - if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x01 then - -- Skip if did get the info from this router before - if not routers[p.ip_src] then - -- Parse header - response = eigrp.EIGRP.parse(eigrp_raw) - response.src = p.ip_src - response.interface = interface.shortname - end - if response then - -- See, if it has routing information - for _,tlv in pairs(response.tlvs) do - if eigrp.EIGRP.isRoutingTLV(tlv.type) then - routers[p.ip_src] = true - table.insert(responses, response) - break - end - end - end - end - end + -- For each EIGRP packet captured until timeout + while (nmap.clock_ms() - start) < timeout do + response = {} + response.tlvs = {} + status, _, _, l3data = listener:pcap_receive() + if status then + p = packet.Packet:new(l3data, #l3data) + eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1) + -- Check if it is an EIGRPv2 Update + if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x01 then + -- Skip if did get the info from this router before + if not routers[p.ip_src] then + -- Parse header + response = eigrp.EIGRP.parse(eigrp_raw) + response.src = p.ip_src + response.interface = interface.shortname + end + if response then + -- See, if it has routing information + for _,tlv in pairs(response.tlvs) do + if eigrp.EIGRP.isRoutingTLV(tlv.type) then + routers[p.ip_src] = true + table.insert(responses, response) + break + end + end + end + end end - condvar("signal") - return + end + condvar("signal") + return end -- 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 astab Table to put result into. local asListener = function(interface, timeout, astab) - local condvar = nmap.condvar(astab) - local status, l3data, p, eigrp_raw, eigrp_hello, _ - local start = nmap.clock_ms() - local filter = "ip proto 88 and ip dst host 224.0.0.10" - local listener = nmap.new_socket() - listener:set_timeout(500) - listener:pcap_open(interface.device, 1024, true, filter) - while (nmap.clock_ms() - start) < timeout do - -- Check if another listener already found an A.S value. - if #astab > 0 then break end + local condvar = nmap.condvar(astab) + local status, l3data, p, eigrp_raw, eigrp_hello, _ + local start = nmap.clock_ms() + local filter = "ip proto 88 and ip dst host 224.0.0.10" + local listener = nmap.new_socket() + listener:set_timeout(500) + listener:pcap_open(interface.device, 1024, true, filter) + while (nmap.clock_ms() - start) < timeout do + -- Check if another listener already found an A.S value. + if #astab > 0 then break end - status, _, _, l3data = listener:pcap_receive() - if status then - p = packet.Packet:new(l3data, #l3data) - eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1) - -- Listen for EIGRPv2 Hello packets - if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x05 then - eigrp_hello = eigrp.EIGRP.parse(eigrp_raw) - if eigrp_hello and eigrp_hello.as then - table.insert(astab, eigrp_hello.as) - break - end - end - end + status, _, _, l3data = listener:pcap_receive() + if status then + p = packet.Packet:new(l3data, #l3data) + eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1) + -- Listen for EIGRPv2 Hello packets + if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x05 then + eigrp_hello = eigrp.EIGRP.parse(eigrp_raw) + if eigrp_hello and eigrp_hello.as then + table.insert(astab, eigrp_hello.as) + break + end + end end - condvar("signal") + end + condvar("signal") end action = function() - -- Get script arguments - local as = stdnse.get_script_args(SCRIPT_NAME .. ".as") - local kparams = stdnse.get_script_args(SCRIPT_NAME .. ".kparams") or "101000" - local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) - local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface") - local output, responses, interfaces, lthreads = {}, {}, {}, {} - local result, response, route, eigrp_hello, k - local timeout = (timeout or 10) * 1000 + -- Get script arguments + local as = stdnse.get_script_args(SCRIPT_NAME .. ".as") + local kparams = stdnse.get_script_args(SCRIPT_NAME .. ".kparams") or "101000" + local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) + local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface") + local output, responses, interfaces, lthreads = {}, {}, {}, {} + local result, response, route, eigrp_hello, k + local timeout = (timeout or 10) * 1000 - -- K params should be of length 6 - -- Cisco routers ignore eigrp packets that don't have matching K parameters - if #kparams < 6 or #kparams > 6 then - return "\n ERROR: kparams should be of size 6." - else - k = {} - k[1] = string.sub(kparams, 1,1) - k[2] = string.sub(kparams, 2,2) - k[3] = string.sub(kparams, 3,3) - k[4] = string.sub(kparams, 4,4) - k[5] = string.sub(kparams, 5,5) - k[6] = string.sub(kparams, 6) + -- K params should be of length 6 + -- Cisco routers ignore eigrp packets that don't have matching K parameters + if #kparams < 6 or #kparams > 6 then + return "\n ERROR: kparams should be of size 6." + else + k = {} + k[1] = string.sub(kparams, 1,1) + k[2] = string.sub(kparams, 2,2) + k[3] = string.sub(kparams, 3,3) + k[4] = string.sub(kparams, 4,4) + k[5] = string.sub(kparams, 5,5) + 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 + 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() - 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 - 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 + stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, iface.shortname) + table.insert(interfaces, iface) + end end + end - -- If user didn't provide an Autonomous System value, we listen fro multicast - -- HELLO router announcements to get one. - if not as then - -- We use a table for condvar - local astab = {} - stdnse.print_debug("%s: No A.S value provided, will sniff for one.", SCRIPT_NAME) - -- 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 = {} + -- If user didn't provide an Autonomous System value, we listen fro multicast + -- HELLO router announcements to get one. + if not as then + -- We use a table for condvar + local astab = {} + stdnse.print_debug("%s: No A.S value provided, will sniff for one.", SCRIPT_NAME) + -- We should iterate over interfaces 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)) + local co = stdnse.new_thread(asListener, interface, timeout, astab) + lthreads[co] = true end - - local condvar = nmap.condvar(responses) + local condvar = nmap.condvar(astab) -- 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 + 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 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) + 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 + 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 diff --git a/scripts/broadcast-igmp-discovery.nse b/scripts/broadcast-igmp-discovery.nse index 7c3fd47c0..febb3f5ff 100644 --- a/scripts/broadcast-igmp-discovery.nse +++ b/scripts/broadcast-igmp-discovery.nse @@ -88,15 +88,15 @@ interfaces. prerule = function() - if nmap.address_family() ~= 'inet' then - stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) - return false - end - if ( not(nmap.is_privileged()) ) then - stdnse.print_verbose("%s not running due to lack of privileges.", SCRIPT_NAME) - return false - end - return true + if nmap.address_family() ~= 'inet' then + stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) + return false + end + if ( not(nmap.is_privileged()) ) then + stdnse.print_verbose("%s not running due to lack of privileges.", SCRIPT_NAME) + return false + end + return true end author = "Hani Benhabiles" @@ -109,54 +109,54 @@ categories = {"discovery", "safe", "broadcast"} -- @param data string IGMP Raw packet. -- @return response table Structured igmp packet. local igmpParse = function(data) - local index - local response = {} - local group, source - -- Report type (0x12 == v1, 0x16 == v2, 0x22 == v3) - index, response.type = bin.unpack(">C", data, index) - if response.type == 0x12 or response.type == 0x16 then - -- Max response time - index, response.maxrt = bin.unpack(">C", data, index) - -- Checksum - index, response.checksum = bin.unpack(">S", data, index) - -- Multicast group - index, response.group = bin.unpack("= 12 then - -- Skip reserved byte - index = index + 1 - -- Checksum - index, response.checksum = bin.unpack(">S", data, index) - -- Skip reserved byte - index = index + 2 - -- Number of groups - index, response.ngroups = bin.unpack(">S", data, index) - response.groups = {} - for i=1,response.ngroups do - group = {} - -- Mode is either INCLUDE or EXCLUDE - index, group.mode = bin.unpack(">C", data, index) - -- Auxiliary data length in the group record (in 32bits units) - index, group.auxdlen = bin.unpack(">C", data, index) - -- Number of source addresses - index, group.nsrc = bin.unpack(">S", data, index) - index, group.address = bin.unpack(" 0 then - for i=1,group.nsrc do - index, source = bin.unpack("C", data, index) + if response.type == 0x12 or response.type == 0x16 then + -- Max response time + index, response.maxrt = bin.unpack(">C", data, index) + -- Checksum + index, response.checksum = bin.unpack(">S", data, index) + -- Multicast group + index, response.group = bin.unpack("= 12 then + -- Skip reserved byte + index = index + 1 + -- Checksum + index, response.checksum = bin.unpack(">S", data, index) + -- Skip reserved byte + index = index + 2 + -- Number of groups + index, response.ngroups = bin.unpack(">S", data, index) + response.groups = {} + for i=1,response.ngroups do + group = {} + -- Mode is either INCLUDE or EXCLUDE + index, group.mode = bin.unpack(">C", data, index) + -- Auxiliary data length in the group record (in 32bits units) + index, group.auxdlen = bin.unpack(">C", data, index) + -- Number of source addresses + index, group.nsrc = bin.unpack(">S", data, index) + index, group.address = bin.unpack(" 0 then + for i=1,group.nsrc do + index, source = bin.unpack("C", 0x11) -- Membership Query, same for all versions - if version == 1 then - igmp_raw = igmp_raw .. bin.pack(">C", 0x00) -- Unused, 0x00 for version 1 only - else - igmp_raw = igmp_raw .. bin.pack(">C", 0x16) -- Max response time: 10 Seconds, for version 2 and 3 - end + -- Let's craft an IGMP Membership Query + local igmp_raw = bin.pack(">C", 0x11) -- Membership Query, same for all versions + if version == 1 then + igmp_raw = igmp_raw .. bin.pack(">C", 0x00) -- Unused, 0x00 for version 1 only + else + igmp_raw = igmp_raw .. bin.pack(">C", 0x16) -- Max response time: 10 Seconds, for version 2 and 3 + end - 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(">S", 0x00) -- Checksum, calculated later + igmp_raw = igmp_raw .. bin.pack(">I", 0x00) -- Multicast Address: 0.0.0.0 - if version == 3 then - -- Reserved = 4 bits (Should be zeroed) - -- Supress Flag = 1 bit - -- QRV (Querier's Robustness Variable) = 3 bits - -- all are set to 0 - igmp_raw = igmp_raw .. bin.pack(">C", 0x00) - -- QQIC (Querier's Query Interval Code) in seconds = Set to 0 to get insta replies. - igmp_raw = igmp_raw .. bin.pack(">C", 0x10) - -- Number of sources (in the next arrays) = 1 ( Our IP only) - igmp_raw = igmp_raw .. bin.pack(">S", 0x01) - -- Source = Our IP address - igmp_raw = igmp_raw .. bin.pack(">I", ipOps.todword(interface.address)) - end + if version == 3 then + -- Reserved = 4 bits (Should be zeroed) + -- Supress Flag = 1 bit + -- QRV (Querier's Robustness Variable) = 3 bits + -- all are set to 0 + igmp_raw = igmp_raw .. bin.pack(">C", 0x00) + -- QQIC (Querier's Query Interval Code) in seconds = Set to 0 to get insta replies. + igmp_raw = igmp_raw .. bin.pack(">C", 0x10) + -- Number of sources (in the next arrays) = 1 ( Our IP only) + igmp_raw = igmp_raw .. bin.pack(">S", 0x01) + -- Source = Our IP address + igmp_raw = igmp_raw .. bin.pack(">I", ipOps.todword(interface.address)) + 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 @@ -249,181 +249,181 @@ local igmpQuery; -- @param interface Network interface to send on. -- @param vesion IGMP version. Could be 1, 2, 3 or all. igmpQuery = function(interface, version) - local srcip = interface.address - local dstip = "224.0.0.1" + local srcip = interface.address + local dstip = "224.0.0.1" - if version == 'all' then - -- Small pause to let listener begin and not miss reports. - stdnse.sleep(0.5) - igmpQuery(interface, 3) - igmpQuery(interface, 2) - igmpQuery(interface, 1) - else - local igmp_raw = igmpRaw(interface, version) + if version == 'all' then + -- Small pause to let listener begin and not miss reports. + stdnse.sleep(0.5) + igmpQuery(interface, 3) + igmpQuery(interface, 2) + igmpQuery(interface, 1) + else + local igmp_raw = igmpRaw(interface, version) - local ip_raw = bin.pack("H", "45c00040ed780000010218bc0a00c8750a00c86b") .. igmp_raw - 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_dst(ipOps.ip_to_str(dstip)) - igmp_packet:ip_set_len(#igmp_packet.buf) - igmp_packet:ip_count_checksum() + local ip_raw = bin.pack("H", "45c00040ed780000010218bc0a00c8750a00c86b") .. igmp_raw + 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_dst(ipOps.ip_to_str(dstip)) + igmp_packet:ip_set_len(#igmp_packet.buf) + igmp_packet:ip_count_checksum() - local sock = nmap.new_dnet() - sock:ethernet_open(interface.device) + local sock = nmap.new_dnet() + sock:ethernet_open(interface.device) - -- 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") - sock:ethernet_send(eth_hdr .. igmp_packet.buf) - sock:ethernet_close() - end + -- 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") + sock:ethernet_send(eth_hdr .. igmp_packet.buf) + sock:ethernet_close() + end end -- Function to compare wieght of an IGMP response message. -- Used to sort elements in responses table. local respCompare = function(a,b) - 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)) + 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)) end local mgroup_names_fetch = function(filename) - local groupnames_db = {} + local groupnames_db = {} - local file = io.open(filename, "r") - if not file then - return false - end + local file = io.open(filename, "r") + if not file then + return false + end - for l in file:lines() do - groupnames_db[#groupnames_db + 1] = stdnse.strsplit("\t", l) - end + for l in file:lines() do + groupnames_db[#groupnames_db + 1] = stdnse.strsplit("\t", l) + end - file:close() - return groupnames_db + file:close() + return groupnames_db end local mgroup_name_identify = function(db, ip) - --stdnse.print_debug("%s: '%s'", SCRIPT_NAME, ip) - for _, mg in ipairs(db) do - local ip1 = mg[1] - local ip2 = mg[2] - local desc = mg[3] - --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 - --stdnse.print_debug("%s: found! %s <= %s <= %s (%s)", SCRIPT_NAME, ip1, ip, ip2, desc) - return desc - end + --stdnse.print_debug("%s: '%s'", SCRIPT_NAME, ip) + for _, mg in ipairs(db) do + local ip1 = mg[1] + local ip2 = mg[2] + local desc = mg[3] + --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 + --stdnse.print_debug("%s: found! %s <= %s <= %s (%s)", SCRIPT_NAME, ip1, ip, ip2, desc) + return desc end - return false + end + return false end action = function(host, port) - local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) - local version = stdnse.get_script_args(SCRIPT_NAME .. ".version") or 2 - local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface") - timeout = (timeout or 7) * 1000 - if version ~= 'all' then - version = tonumber(version) + local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) + local version = stdnse.get_script_args(SCRIPT_NAME .. ".version") or 2 + local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface") + timeout = (timeout or 7) * 1000 + if version ~= 'all' then + 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 + 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 = {}, {}, {}, {} - 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 - 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 + stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, iface.shortname) + table.insert(interfaces, iface) + end end + end - -- We should iterate over interfaces - for _, interface in pairs(interfaces) do - local co = stdnse.new_thread(igmpListener, interface, timeout, responses) - igmpQuery(interface, version) - lthreads[co] = true + -- We should iterate over interfaces + for _, interface in pairs(interfaces) do + local co = stdnse.new_thread(igmpListener, interface, timeout, responses) + igmpQuery(interface, version) + 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 - - 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 - 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) + 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 diff --git a/scripts/broadcast-listener.nse b/scripts/broadcast-listener.nse index 71cbd9cf5..68a60daa3 100644 --- a/scripts/broadcast-listener.nse +++ b/scripts/broadcast-listener.nse @@ -70,11 +70,11 @@ unless a specific interface was given using the -e argument to Nmap. -- Version 0.1 -- Created 07/02/2011 - v0.1 - created by Patrik Karlsson -- Revised 07/25/2011 - v0.2 - --- * added more documentation --- * added getInterfaces code to detect available --- interfaces. --- * corrected bug that would fail to load --- decoders if not in a relative directory. +-- * added more documentation +-- * added getInterfaces code to detect available +-- interfaces. +-- * corrected bug that would fail to load +-- decoders if not in a relative directory. @@ -86,11 +86,11 @@ categories = {"broadcast", "safe"} prerule = function() - if not nmap.is_privileged() then - stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) - return false - end - return true + if not nmap.is_privileged() then + stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) + return false + end + return true end --- @@ -101,26 +101,26 @@ end -- @return decoders table of decoder functions on success -- @return err string containing the error message on failure loadDecoders = function(fname) - -- resolve the full, absolute, path - local abs_fname = nmap.fetchfile(fname) + -- resolve the full, absolute, path + local abs_fname = nmap.fetchfile(fname) - if ( not(abs_fname) ) then - return false, ("ERROR: Failed to load decoder definition (%s)"):format(fname) - end + if ( not(abs_fname) ) then + return false, ("ERROR: Failed to load decoder definition (%s)"):format(fname) + end - local env = setmetatable({Decoders = {}}, {__index = _G}); - local file = loadfile(abs_fname, "t", env) - if(not(file)) then - stdnse.print_debug("%s: Couldn't load decoder file: %s", SCRIPT_NAME, fname) - return false, "ERROR: Couldn't load decoder file: " .. fname - end + local env = setmetatable({Decoders = {}}, {__index = _G}); + local file = loadfile(abs_fname, "t", env) + if(not(file)) then + stdnse.print_debug("%s: Couldn't load decoder file: %s", SCRIPT_NAME, fname) + return false, "ERROR: Couldn't load decoder file: " .. fname + end - file() + file() - local d = env.Decoders + local d = env.Decoders - if ( d ) then return true, d end - return false, "ERROR: Failed to load decoders" + if ( d ) then return true, d end + return false, "ERROR: Failed to load decoders" end --- @@ -130,66 +130,66 @@ end -- @param iface table containing name and address -- @param Decoders the decoders class loaded externally -- @param decodertab the "result" table to which all discovered items are --- reported +-- reported sniffInterface = function(iface, Decoders, decodertab) - local condvar = nmap.condvar(decodertab) - local sock = nmap.new_socket() - local timeout = stdnse.parse_timespec(stdnse.get_script_args("broadcast-listener.timeout")) + local condvar = nmap.condvar(decodertab) + local sock = nmap.new_socket() + local timeout = stdnse.parse_timespec(stdnse.get_script_args("broadcast-listener.timeout")) - -- default to 30 seconds, if nothing else was set - timeout = (timeout or 30) * 1000 + -- default to 30 seconds, if nothing else was set + timeout = (timeout or 30) * 1000 - -- We want all packets that aren't explicitly for us - sock:pcap_open(iface.name, 1500, true, ("!host %s"):format(iface.address)) + -- We want all packets that aren't explicitly for us + 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 - sock:set_timeout(100) + -- Set a short timeout so that we can timeout in time if needed + sock:set_timeout(100) - local start_time = nmap.clock_ms() - while( nmap.clock_ms() - start_time < timeout ) do - local status, _, _, data = sock:pcap_receive() + local start_time = nmap.clock_ms() + while( nmap.clock_ms() - start_time < timeout ) do + local status, _, _, data = sock:pcap_receive() - if ( status ) then - local p = packet.Packet:new( data, #data ) + if ( status ) then + local p = packet.Packet:new( data, #data ) - -- 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 ( not(decodertab.udp[p.udp_dport]) ) then - decodertab.udp[p.udp_dport] = Decoders.udp[p.udp_dport]:new() - end - decodertab.udp[p.udp_dport]:process(data) - -- The packet was decoded successfully but we don't have a valid decoder - -- Report this - elseif ( p and p.udp_dport ) then - 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 - -- in that case, check the ether Decoder table for pattern matches - else - -- attempt to find a match for a pattern - local pos, hex = bin.unpack("H" .. #data, data) - local decoded = false - for match, _ in pairs(Decoders.ether) do - -- attempts to match the "raw" packet against a filter - -- supplied in each ethernet packet decoder - if ( hex:match(match) ) then - if ( not(decodertab.ether[match]) ) then - decodertab.ether[match] = Decoders.ether[match]:new() - end - -- start a new decoding thread. This way, if something gets foobared - -- the whole script doesn't break, only the packet decoding for that - -- specific packet. - stdnse.new_thread( decodertab.ether[match].process, decodertab.ether[match], data ) - decoded = true - end - end - -- no decoder was found for this layer2 packet - if ( not(decoded) and #data > 10 ) then - stdnse.print_debug(1, "No decoder for packet hex: %s", select(2, bin.unpack("H10", data) ) ) - end - end - end - end - condvar "signal" + -- 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 ( not(decodertab.udp[p.udp_dport]) ) then + decodertab.udp[p.udp_dport] = Decoders.udp[p.udp_dport]:new() + end + decodertab.udp[p.udp_dport]:process(data) + -- The packet was decoded successfully but we don't have a valid decoder + -- Report this + elseif ( p and p.udp_dport ) then + 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 + -- in that case, check the ether Decoder table for pattern matches + else + -- attempt to find a match for a pattern + local pos, hex = bin.unpack("H" .. #data, data) + local decoded = false + for match, _ in pairs(Decoders.ether) do + -- attempts to match the "raw" packet against a filter + -- supplied in each ethernet packet decoder + if ( hex:match(match) ) then + if ( not(decodertab.ether[match]) ) then + decodertab.ether[match] = Decoders.ether[match]:new() + end + -- start a new decoding thread. This way, if something gets foobared + -- the whole script doesn't break, only the packet decoding for that + -- specific packet. + stdnse.new_thread( decodertab.ether[match].process, decodertab.ether[match], data ) + decoded = true + end + end + -- no decoder was found for this layer2 packet + if ( not(decoded) and #data > 10 ) then + stdnse.print_debug(1, "No decoder for packet hex: %s", select(2, bin.unpack("H10", data) ) ) + end + end + end + end + condvar "signal" end --- @@ -199,96 +199,96 @@ end -- @param link string containing the link type to filter -- @param up string containing the interface status to filter -- @return result table containing tables of interfaces --- each interface table has the following fields: --- name containing the device name --- address containing the device address +-- each interface table has the following fields: +-- name containing the device name +-- address containing the device address getInterfaces = function(link, up) - if( not(nmap.list_interfaces) ) then return end - local interfaces, err = nmap.list_interfaces() - local result = {} - if ( not(err) ) then - for _, iface in ipairs(interfaces) do - if ( iface.link == link and - iface.up == up and - iface.address ) then + if( not(nmap.list_interfaces) ) then return end + local interfaces, err = nmap.list_interfaces() + local result = {} + if ( not(err) ) then + for _, iface in ipairs(interfaces) do + if ( iface.link == link and + iface.up == up and + iface.address ) then - -- exclude ipv6 addresses for now - if ( not(iface.address:match(":")) ) then - table.insert(result, { name = iface.device, - address = iface.address } ) - end - end - end - end - return result + -- exclude ipv6 addresses for now + if ( not(iface.address:match(":")) ) then + table.insert(result, { name = iface.device, + address = iface.address } ) + end + end + end + end + return result end action = function() - local DECODERFILE = "nselib/data/packetdecoders.lua" - local iface = nmap.get_interface() - local interfaces = {} + local DECODERFILE = "nselib/data/packetdecoders.lua" + local iface = nmap.get_interface() + local interfaces = {} - -- was an interface supplied using the -e argument? - if ( iface ) then - local iinfo, err = nmap.get_interface_info(iface) + -- was an interface supplied using the -e argument? + if ( iface ) then + local iinfo, err = nmap.get_interface_info(iface) - if ( not(iinfo.address) ) then - return "\n ERROR: The IP address of the interface could not be determined ..." - end + if ( not(iinfo.address) ) then + return "\n ERROR: The IP address of the interface could not be determined ..." + end - interfaces = { { name = iface, address = iinfo.address } } - else - -- no interface was supplied, attempt autodiscovery - interfaces = getInterfaces("ethernet", "up") - end + interfaces = { { name = iface, address = iinfo.address } } + else + -- no interface was supplied, attempt autodiscovery + interfaces = getInterfaces("ethernet", "up") + end - -- make sure we have at least one interface to start sniffing - if ( #interfaces == 0 ) then - return "\n ERROR: Could not determine any valid interfaces" - end + -- make sure we have at least one interface to start sniffing + if ( #interfaces == 0 ) then + return "\n ERROR: Could not determine any valid interfaces" + end - -- load the decoders from file - local status, Decoders = loadDecoders(DECODERFILE) - if ( not(status) ) then return "\n " .. Decoders end + -- load the decoders from file + local status, Decoders = loadDecoders(DECODERFILE) + if ( not(status) ) then return "\n " .. Decoders end - -- create a local table to handle instantiated decoders - local decodertab = { udp = {}, ether = {} } - local condvar = nmap.condvar(decodertab) - local threads = {} + -- create a local table to handle instantiated decoders + local decodertab = { udp = {}, ether = {} } + local condvar = nmap.condvar(decodertab) + local threads = {} - -- start a thread for each interface to sniff - for _, iface in ipairs(interfaces) do - local co = stdnse.new_thread(sniffInterface, iface, Decoders, decodertab) - threads[co] = true - end + -- start a thread for each interface to sniff + for _, iface in ipairs(interfaces) do + local co = stdnse.new_thread(sniffInterface, iface, Decoders, decodertab) + threads[co] = true + end - -- wait for all threads to finish sniffing - repeat - for thread in pairs(threads) do - if coroutine.status(thread) == "dead" then - threads[thread] = nil - end - end - if ( next(threads) ) then - condvar "wait" - end - until next(threads) == nil + -- wait for all threads to finish sniffing + repeat + for thread in pairs(threads) do + if coroutine.status(thread) == "dead" then + threads[thread] = nil + end + end + if ( next(threads) ) then + condvar "wait" + end + until next(threads) == nil - local out_outer = {} + local out_outer = {} - -- create the results table - for proto, _ in pairs(decodertab) do - local out_inner = {} - for key, decoder in pairs(decodertab[proto]) do - table.insert( out_inner, decodertab[proto][key]:getResults() ) - end - if ( #out_inner > 0 ) then - table.insert( out_outer, { name = proto, out_inner } ) - end - end + -- create the results table + for proto, _ in pairs(decodertab) do + local out_inner = {} + for key, decoder in pairs(decodertab[proto]) do + table.insert( out_inner, decodertab[proto][key]:getResults() ) + end + if ( #out_inner > 0 ) then + table.insert( out_outer, { name = proto, out_inner } ) + end + end - table.sort(out_outer, function(a, b) return a.name < b.name end) - return stdnse.format_output(true, out_outer) + table.sort(out_outer, function(a, b) return a.name < b.name end) + return stdnse.format_output(true, out_outer) end diff --git a/scripts/daap-get-library.nse b/scripts/daap-get-library.nse index feebb5266..9bf4ec837 100644 --- a/scripts/daap-get-library.nse +++ b/scripts/daap-get-library.nse @@ -57,24 +57,24 @@ portrule = shortport.port_or_service(3689, "daap") -- @param port table containing number and protocol fields. -- @return string containing the name of the library function getLibraryName( host, port ) - local _, libname, pos - local url = "daap://" .. host.ip .. "/server-info" - local response = http.get( host, port, url, nil, nil, nil) + local _, libname, pos + local url = "daap://" .. host.ip .. "/server-info" + local response = http.get( host, port, url, nil, nil, nil) - if response == nil or response.body == nil or response.body=="" then - return - end + if response == nil or response.body == nil or response.body=="" then + return + end - pos = string.find(response.body, "minm") + pos = string.find(response.body, "minm") - if pos > 0 then - local len - pos = pos + 4 - pos, len = bin.unpack( ">I", response.body, pos ) - pos, libname = bin.unpack( "A" .. len, response.body, pos ) - end + if pos > 0 then + local len + pos = pos + 4 + pos, len = bin.unpack( ">I", response.body, pos ) + pos, libname = bin.unpack( "A" .. len, response.body, pos ) + end - return libname + return libname end --- Reads the first item value specified by name @@ -84,23 +84,23 @@ end -- @return number local function getAttributeAsInt( data, name ) - local pos = string.find(data, name) - local attrib + local pos = string.find(data, name) + local attrib - if pos and pos > 0 then - pos = pos + 4 - local len - pos, len = bin.unpack( ">I", data, pos ) + if pos and pos > 0 then + pos = pos + 4 + local len + pos, len = bin.unpack( ">I", data, pos ) - if ( len ~= 4 ) then - stdnse.print_debug( string.format("Unexpected length returned: %d", len ) ) - return - end + if ( len ~= 4 ) then + stdnse.print_debug( string.format("Unexpected length returned: %d", len ) ) + return + end - pos, attrib = bin.unpack( ">I", data, pos ) - end + pos, attrib = bin.unpack( ">I", data, pos ) + end - return attrib + return attrib end @@ -111,14 +111,14 @@ end -- @return number containing the session identity received from the server function getSessionId( host, port ) - local _, sessionid - local response = http.get( host, port, "/login", nil, nil, nil ) + local _, sessionid + local response = http.get( host, port, "/login", nil, nil, nil ) - if response ~= nil then - sessionid = getAttributeAsInt( response.body, "mlid") - end + if response ~= nil then + sessionid = getAttributeAsInt( response.body, "mlid") + end - return sessionid + return sessionid end --- Gets the revision number for the library @@ -128,15 +128,15 @@ end -- @param sessionid number containing session identifier from getSessionId -- @return number containing the revision number for the library function getRevisionNumber( host, port, sessionid ) - local url = "/update?session-id=" .. sessionid .. "&revision-number=1" - local _, revision - local response = http.get( host, port, url, nil, nil, nil ) + local url = "/update?session-id=" .. sessionid .. "&revision-number=1" + local _, revision + local response = http.get( host, port, url, nil, nil, nil ) - if response ~= nil then - revision = getAttributeAsInt( response.body, "musr") - end + if response ~= nil then + revision = getAttributeAsInt( response.body, "musr") + end - return revision + return revision end --- Gets the database identitity for the library @@ -146,15 +146,15 @@ end -- @param sessionid number containing session identifier from getSessionId -- @param revid number containing the revision id as retrieved from getRevisionNumber function getDatabaseId( host, port, sessionid, revid ) - local url = "/databases?session-id=" .. sessionid .. "&revision-number=" .. revid - local response = http.get( host, port, url, nil, nil, nil ) - local miid + local url = "/databases?session-id=" .. sessionid .. "&revision-number=" .. revid + local response = http.get( host, port, url, nil, nil, nil ) + local miid - if response ~= nil then - miid = getAttributeAsInt( response.body, "miid") - end + if response ~= nil then + miid = getAttributeAsInt( response.body, "miid") + end - return miid + return miid end --- Gets a string item type from data @@ -164,19 +164,19 @@ end -- @return pos number containing new position after reading string -- @return value string containing the string item that was read 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 - return bin.unpack( "A"..len, data, pos ) - end + if ( len > 0 ) then + return bin.unpack( "A"..len, data, pos ) + end end 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["minm"] = itemFetcher["mikd"] itemFetcher["asal"] = itemFetcher["mikd"] @@ -190,22 +190,22 @@ itemFetcher["asar"] = itemFetcher["mikd"] -- asal and asar when available parseItem = function( data, len ) - local pos, name, value = 1, nil, nil - local item = {} + local pos, name, value = 1, nil, nil + local item = {} - while( len - pos > 0 ) do - pos, name = bin.unpack( "A4", data, pos ) + while( len - pos > 0 ) do + pos, name = bin.unpack( "A4", data, pos ) - if itemFetcher[name] then - pos, item[name] = itemFetcher[name](data, pos ) - else - stdnse.print_debug( string.format("No itemfetcher for: %s", name) ) - break - end + if itemFetcher[name] then + pos, item[name] = itemFetcher[name](data, pos ) + else + stdnse.print_debug( string.format("No itemfetcher for: %s", name) ) + break + end - end + end - return item + return item end @@ -218,124 +218,124 @@ end -- @param limit number containing the maximum amount of songs to return -- @return table containing the following structure [artist][album][songs] function getItems( host, port, sessionid, revid, dbid, limit ) - 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 response = http.get( host, port, url, nil, nil, nil ) - local item, data, pos, len - local items = {} - local limit = limit or -1 + 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 response = http.get( host, port, url, nil, nil, nil ) + local item, data, pos, len + local items = {} + local limit = limit or -1 - if response == nil then - return - end + if response == nil then + return + end - -- get our position to the list of items - pos = string.find(response.body, "mlcl") - pos = pos + 4 + -- get our position to the list of items + pos = string.find(response.body, "mlcl") + 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 - pos = string.find(response.body, "mlit", pos) - pos = pos + 4 + -- find the next single item + pos = string.find(response.body, "mlit", pos) + 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 - pos, data = bin.unpack( "A" .. len, response.body, pos ) - else - break - end + if ( pos < response.body:len() and pos + len < response.body:len() ) then + pos, data = bin.unpack( "A" .. len, response.body, pos ) + else + break + end - -- parse a single item - item = parseItem( data, len ) + -- parse a single item + item = parseItem( data, len ) - local album = item.asal or "unknown" - local artist= item.asar or "unknown" - local song = item.minm or "" + local album = item.asal or "unknown" + local artist= item.asar or "unknown" + local song = item.minm or "" - if items[artist] == nil then - items[artist] = {} - end + if items[artist] == nil then + items[artist] = {} + end - if items[artist][album] == nil then - items[artist][album] = {} - end + if items[artist][album] == nil then + items[artist][album] = {} + end - if limit == 0 then - break - elseif limit > 0 then - limit = limit - 1 - end + if limit == 0 then + break + elseif limit > 0 then + limit = limit - 1 + end - table.insert( items[artist][album], song ) + table.insert( items[artist][album], song ) - end + end - return items + return items end action = function(host, port) - local limit = tonumber(nmap.registry.args.daap_item_limit) or 100 - local libname = getLibraryName( host, port ) + local limit = tonumber(nmap.registry.args.daap_item_limit) or 100 + local libname = getLibraryName( host, port ) - if libname == nil then - return - end + if libname == nil then + return + end - local sessionid = getSessionId( host, port ) + local sessionid = getSessionId( host, port ) - if sessionid == nil then - return stdnse.format_output(true, "Libname: " .. libname) - end + if sessionid == nil then + return stdnse.format_output(true, "Libname: " .. libname) + end - local revid = getRevisionNumber( host, port, sessionid ) + local revid = getRevisionNumber( host, port, sessionid ) - if revid == nil then - return stdnse.format_output(true, "Libname: " .. libname) - end + if revid == nil then + return stdnse.format_output(true, "Libname: " .. libname) + end - local dbid = getDatabaseId( host, port, sessionid, revid ) + local dbid = getDatabaseId( host, port, sessionid, revid ) - if dbid == nil then - return - end + if dbid == nil then + return + end - local items = getItems( host, port, sessionid, revid, dbid, limit ) + local items = getItems( host, port, sessionid, revid, dbid, limit ) - if items == nil then - return - end + if items == nil then + return + 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 - albums = {} - for album, v2 in pairs(v) do - songs = {} - for _, song in pairs( v2 ) do - table.insert( songs, song ) - end - table.insert( albums, album ) - table.insert( albums, songs ) - end - table.insert( artists, artist ) - table.insert( artists, albums ) - end + for artist, v in pairs(items) do + albums = {} + for album, v2 in pairs(v) do + songs = {} + for _, song in pairs( v2 ) do + table.insert( songs, song ) + end + table.insert( albums, album ) + table.insert( albums, songs ) + end + table.insert( artists, artist ) + table.insert( artists, albums ) + end - table.insert( results, artists ) - local output = stdnse.format_output( true, results ) + table.insert( results, artists ) + local output = stdnse.format_output( true, results ) - if limit > 0 then - output = output .. string.format("\n\nOutput limited to %d items", limit ) - end + if limit > 0 then + output = output .. string.format("\n\nOutput limited to %d items", limit ) + end - return output + return output end diff --git a/scripts/db2-das-info.nse b/scripts/db2-das-info.nse index 42ef15b5e..98440d474 100644 --- a/scripts/db2-das-info.nse +++ b/scripts/db2-das-info.nse @@ -103,13 +103,13 @@ portrule = shortport.version_port_or_service({523}, nil, -- @return string containing the complete server profile function extract_server_profile(data) - local server_profile_offset = 37 + local server_profile_offset = 37 - if server_profile_offset > data:len() then - return - end + if server_profile_offset > data:len() then + return + end - return data:sub(server_profile_offset) + return data:sub(server_profile_offset) end @@ -124,28 +124,28 @@ end -- @return table with parsed data function parse_db2_packet(packet) - local info_length_offset = 158 - local info_offset = 160 - local version_offset = 97 - local response = {} + local info_length_offset = 158 + local info_offset = 160 + local version_offset = 97 + local response = {} - if packet.header.data_len < info_length_offset then - stdnse.print_debug( "db2-das-info: packet too short to be DB2 response...") - return - end + if packet.header.data_len < info_length_offset then + stdnse.print_debug( "db2-das-info: packet too short to be DB2 response...") + return + end - 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.info_length = len - 4 - response.info = packet.data:sub(info_offset, info_offset + response.info_length - (info_offset-info_length_offset)) + 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.info_length = len - 4 + response.info = packet.data:sub(info_offset, info_offset + response.info_length - (info_offset-info_length_offset)) - 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: info_length: %d", response.info_length) ) - stdnse.print_debug( string.format("db2-das-info: response.info:len(): %d", response.info:len())) - end + 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: info_length: %d", response.info_length) ) + stdnse.print_debug( string.format("db2-das-info: response.info:len(): %d", response.info:len())) + end - return response + return response end @@ -163,67 +163,67 @@ end -- @return table with header and data function read_db2_packet(socket) - local packet = {} - local header_len = 41 - local total_len = 0 - local buf + local packet = {} + local header_len = 41 + local total_len = 0 + local buf - local DATA_LENGTH_OFFSET = 38 - local ENDIANESS_OFFSET = 23 + local DATA_LENGTH_OFFSET = 38 + local ENDIANESS_OFFSET = 23 - local catch = function() - stdnse.print_debug("%s", "db2-das-info: ERROR communicating with DB2 server") - socket:close() - end + local catch = function() + stdnse.print_debug("%s", "db2-das-info: ERROR communicating with DB2 server") + socket:close() + end - local try = nmap.new_try(catch) - packet.header = {} + local try = nmap.new_try(catch) + 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 - _, packet.header.data_len = bin.unpack("I", packet.header.raw, DATA_LENGTH_OFFSET ) - else - _, packet.header.data_len = bin.unpack(">I", packet.header.raw, DATA_LENGTH_OFFSET ) - end + if endian == "9z" then + _, packet.header.data_len = bin.unpack("I", packet.header.raw, DATA_LENGTH_OFFSET ) + else + _, packet.header.data_len = bin.unpack(">I", packet.header.raw, DATA_LENGTH_OFFSET ) + end - total_len = header_len + packet.header.data_len + total_len = header_len + packet.header.data_len - 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: buf_len: %d", buf:len())) - stdnse.print_debug( string.format("db2-das-info: total_len: %d", total_len)) - end + 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: buf_len: %d", buf:len())) + stdnse.print_debug( string.format("db2-das-info: total_len: %d", total_len)) + end - -- do we have all data as specified by data_len? - while total_len > buf:len() do - -- if not read additional bytes - if(nmap.debugging() > 3) then - stdnse.print_debug( string.format("db2-das-info: Reading %d additional bytes", total_len - buf:len())) - end - local tmp = try( socket:receive_bytes( total_len - buf:len() ) ) - if(nmap.debugging() > 3) then - stdnse.print_debug( string.format("db2-das-info: Read %d bytes", tmp:len())) - end - buf = buf .. tmp - end + -- do we have all data as specified by data_len? + while total_len > buf:len() do + -- if not read additional bytes + if(nmap.debugging() > 3) then + stdnse.print_debug( string.format("db2-das-info: Reading %d additional bytes", total_len - buf:len())) + end + local tmp = try( socket:receive_bytes( total_len - buf:len() ) ) + if(nmap.debugging() > 3) then + stdnse.print_debug( string.format("db2-das-info: Read %d bytes", tmp:len())) + end + buf = buf .. tmp + end - packet.data = buf:sub(header_len + 1) + packet.data = buf:sub(header_len + 1) - else - stdnse.print_debug("db2-das-info: Unknown packet, aborting ...") - return - end + else + stdnse.print_debug("db2-das-info: Unknown packet, aborting ...") + return + end - return packet + return packet end @@ -234,16 +234,16 @@ end -- function send_db2_packet( socket, packet ) - local catch = function() - stdnse.print_debug("%s", "db2-das-info: ERROR communicating with DB2 server") - socket:close() - end + local catch = function() + stdnse.print_debug("%s", "db2-das-info: ERROR communicating with DB2 server") + socket:close() + 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 @@ -261,172 +261,172 @@ end -- function create_das_packet( magic, data ) - local packet = {} - local data_len = data:len() + local packet = {} + 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 = 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 .. bin.pack("C", magic) - packet.header.raw = packet.header.raw .. bin.pack("S", data_len) - packet.header.raw = packet.header.raw .. string.char(0x00, 0x00) + 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(0x00, 0x00, 0x00, 0x00 ) + 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 .. string.char(0x00, 0x00) - packet.header.data_len = data_len - packet.data = data + packet.header.data_len = data_len + packet.data = data - return packet + return packet end action = function(host, port) - -- create the socket used for our connection - local socket = nmap.new_socket() + -- create the socket used for our connection + local socket = nmap.new_socket() - -- set a reasonable timeout value - socket:set_timeout(10000) + -- set a reasonable timeout value + socket:set_timeout(10000) - -- do some exception handling / cleanup - local catch = function() - stdnse.print_debug("%s", "db2-das-info: ERROR communicating with " .. host.ip .. " on port " .. port.number) - socket:close() - end + -- do some exception handling / cleanup + local catch = function() + stdnse.print_debug("%s", "db2-das-info: ERROR communicating with " .. host.ip .. " on port " .. port.number) + socket:close() + 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 - -- ************************************************************************************ - local data = string.char(0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4a, 0x00) + -- ************************************************************************************ + -- Transaction block 1 + -- ************************************************************************************ + local data = string.char(0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4a, 0x00) - --try(socket:send(query)) - local db2packet = create_das_packet(0x02, data) + --try(socket:send(query)) + local db2packet = create_das_packet(0x02, data) - send_db2_packet( socket, db2packet ) - read_db2_packet( socket ) + send_db2_packet( socket, db2packet ) + read_db2_packet( socket ) - -- ************************************************************************************ - -- Transaction block 2 - -- ************************************************************************************ - 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(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) + -- ************************************************************************************ + -- Transaction block 2 + -- ************************************************************************************ + 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(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) - db2packet = create_das_packet(0x05, data) + db2packet = create_das_packet(0x05, data) - send_db2_packet( socket, db2packet ) - read_db2_packet( socket ) + send_db2_packet( socket, db2packet ) + read_db2_packet( socket ) - -- ************************************************************************************ - -- Transaction block 3 - -- ************************************************************************************ - 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(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(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) + -- ************************************************************************************ + -- Transaction block 3 + -- ************************************************************************************ + 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(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(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) - db2packet = create_das_packet(0x0a, data) - send_db2_packet( socket, db2packet ) - read_db2_packet( socket ) + db2packet = create_das_packet(0x0a, data) + send_db2_packet( socket, db2packet ) + read_db2_packet( socket ) - -- ************************************************************************************ - -- Transaction block 4 - -- ************************************************************************************ - 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(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(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(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(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, 0x11, 0x00, 0x00, 0x00) - data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x00) + -- ************************************************************************************ + -- Transaction block 4 + -- ************************************************************************************ + 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(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(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(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(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, 0x11, 0x00, 0x00, 0x00) + data = data .. string.char(0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0xb8, 0x00) - db2packet = create_das_packet(0x06, data) - send_db2_packet( socket, db2packet ) + db2packet = create_das_packet(0x06, data) + send_db2_packet( socket, db2packet ) - 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(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(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, 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, 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, 0x0c, 0x00, 0x00, 0x00, 0x18) + 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(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(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, 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, 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, 0x0c, 0x00, 0x00, 0x00, 0x18) - db2packet = create_das_packet(0x06, data) - send_db2_packet( socket, db2packet ) + db2packet = create_das_packet(0x06, data) + send_db2_packet( socket, db2packet ) - local packet = read_db2_packet( socket ) - local db2response = parse_db2_packet(packet) + local packet = read_db2_packet( socket ) + 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 - local server_version - if string.sub(db2response.version,1,3) == "SQL" then - local major_version = string.sub(db2response.version,4,5) + -- The next block of code is essentially the version extraction code from db2-info.nse + local server_version + if string.sub(db2response.version,1,3) == "SQL" then + local major_version = string.sub(db2response.version,4,5) - -- strip the leading 0 from the major version, for consistency with - -- nmap-service-probes results - if string.sub(major_version,1,1) == "0" then - major_version = string.sub(major_version,2) - end - local minor_version = string.sub(db2response.version,6,7) - local hotfix = string.sub(db2response.version,8) - server_version = major_version .. "." .. minor_version .. "." .. hotfix - end + -- strip the leading 0 from the major version, for consistency with + -- nmap-service-probes results + if string.sub(major_version,1,1) == "0" then + major_version = string.sub(major_version,2) + end + local minor_version = string.sub(db2response.version,6,7) + local hotfix = string.sub(db2response.version,8) + server_version = major_version .. "." .. minor_version .. "." .. hotfix + end - -- 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) - local _ - local current_count = 0 - if port.version.version ~= nil then - _, current_count = string.gsub(port.version.version, "%.", ".") - end + -- 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) + local _ + local current_count = 0 + if port.version.version ~= nil then + _, current_count = string.gsub(port.version.version, "%.", ".") + end - local new_count = 0 - if server_version ~= nil then - _, new_count = string.gsub(server_version, "%.", ".") - end + local new_count = 0 + if server_version ~= nil then + _, new_count = string.gsub(server_version, "%.", ".") + end - if current_count < new_count then - port.version.version = server_version - end + if current_count < new_count then + port.version.version = server_version + end local result = false local db2profile = extract_server_profile( db2response.info ) if (db2profile ~= nil ) then - result = "DB2 Administration Server Settings\r\n" - result = result .. extract_server_profile( db2response.info ) + result = "DB2 Administration Server Settings\r\n" + result = result .. extract_server_profile( db2response.info ) - -- Set port information - port.version.name = "ibm-db2" - port.version.product = "IBM DB2 Database Server" - port.version.name_confidence = 10 - nmap.set_port_version(host, port) - nmap.set_port_state(host, port, "open") - end + -- Set port information + port.version.name = "ibm-db2" + port.version.product = "IBM DB2 Database Server" + port.version.name_confidence = 10 + nmap.set_port_version(host, port) + nmap.set_port_state(host, port, "open") + end - return result + return result end diff --git a/scripts/dns-brute.nse b/scripts/dns-brute.nse index 23efcc7bc..4d398302f 100644 --- a/scripts/dns-brute.nse +++ b/scripts/dns-brute.nse @@ -67,170 +67,170 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"intrusive", "discovery"} prerule = function() - if not stdnse.get_script_args("dns-brute.domain") then - stdnse.print_debug(1, - "Skipping '%s' %s, 'dns-brute.domain' argument is missing.", - SCRIPT_NAME, SCRIPT_TYPE) - return false - end - return true + if not stdnse.get_script_args("dns-brute.domain") then + stdnse.print_debug(1, + "Skipping '%s' %s, 'dns-brute.domain' argument is missing.", + SCRIPT_NAME, SCRIPT_TYPE) + return false + end + return true end hostrule = function(host) - return true + return true end local function guess_domain(host) - local name + local name - name = stdnse.get_hostname(host) - if name and name ~= host.ip then - return string.match(name, "%.([^.]+%..+)%.?$") or string.match(name, "^([^.]+%.[^.]+)%.?$") - else - return nil - end + name = stdnse.get_hostname(host) + if name and name ~= host.ip then + return string.match(name, "%.([^.]+%..+)%.?$") or string.match(name, "^([^.]+%.[^.]+)%.?$") + else + return nil + end end -- Single DNS lookup, returning all results. dtype should be e.g. "A", "AAAA". local function resolve(host, dtype) - local status, result = dns.query(host, {dtype=dtype,retAll=true}) - return status and result or false + local status, result = dns.query(host, {dtype=dtype,retAll=true}) + return status and result or false end local function array_iter(array, i, j) - return coroutine.wrap(function () - while i <= j do - coroutine.yield(array[i]) - i = i + 1 - end - end) + return coroutine.wrap(function () + while i <= j do + coroutine.yield(array[i]) + i = i + 1 + end + end) end local function thread_main(domainname, results, name_iter) - local condvar = nmap.condvar( results ) - for name in name_iter do - for _, dtype in ipairs({"A", "AAAA"}) do - local res = resolve(name..'.'..domainname, dtype) - if(res) then - for _,addr in ipairs(res) do - local hostn = name..'.'..domainname - if target.ALLOW_NEW_TARGETS then - stdnse.print_debug("Added target: "..hostn) - local status,err = target.add(hostn) - end - stdnse.print_debug("Hostname: "..hostn.." IP: "..addr) - local record = { hostname=hostn, address=addr } - setmetatable(record, { - __tostring = function(t) - return string.format("%s - %s", t.hostname, t.address) - end - }) - results[#results+1] = record - end - end - end - end - condvar("signal") + local condvar = nmap.condvar( results ) + for name in name_iter do + for _, dtype in ipairs({"A", "AAAA"}) do + local res = resolve(name..'.'..domainname, dtype) + if(res) then + for _,addr in ipairs(res) do + local hostn = name..'.'..domainname + if target.ALLOW_NEW_TARGETS then + stdnse.print_debug("Added target: "..hostn) + local status,err = target.add(hostn) + end + stdnse.print_debug("Hostname: "..hostn.." IP: "..addr) + local record = { hostname=hostn, address=addr } + setmetatable(record, { + __tostring = function(t) + return string.format("%s - %s", t.hostname, t.address) + end + }) + results[#results+1] = record + end + end + end + end + condvar("signal") end local function srv_main(domainname, srvresults, srv_iter) - local condvar = nmap.condvar( srvresults ) - for name in srv_iter do - local res = resolve(name..'.'..domainname, "SRV") - if(res) then - for _,addr in ipairs(res) do - local hostn = name..'.'..domainname - addr = stdnse.strsplit(":",addr) - for _, dtype in ipairs({"A", "AAAA"}) do - local srvres = resolve(addr[4], dtype) - if(srvres) then - for srvhost,srvip in ipairs(srvres) do - if target.ALLOW_NEW_TARGETS then - stdnse.print_debug("Added target: "..srvip) - local status,err = target.add(srvip) - end - stdnse.print_debug("Hostname: "..hostn.." IP: "..srvip) - local record = { hostname=hostn, address=srvip } - setmetatable(record, { - __tostring = function(t) - return string.format("%s - %s", t.hostname, t.address) - end - }) - srvresults[#srvresults+1] = record - end - end - end - end - end - end - condvar("signal") + local condvar = nmap.condvar( srvresults ) + for name in srv_iter do + local res = resolve(name..'.'..domainname, "SRV") + if(res) then + for _,addr in ipairs(res) do + local hostn = name..'.'..domainname + addr = stdnse.strsplit(":",addr) + for _, dtype in ipairs({"A", "AAAA"}) do + local srvres = resolve(addr[4], dtype) + if(srvres) then + for srvhost,srvip in ipairs(srvres) do + if target.ALLOW_NEW_TARGETS then + stdnse.print_debug("Added target: "..srvip) + local status,err = target.add(srvip) + end + stdnse.print_debug("Hostname: "..hostn.." IP: "..srvip) + local record = { hostname=hostn, address=srvip } + setmetatable(record, { + __tostring = function(t) + return string.format("%s - %s", t.hostname, t.address) + end + }) + srvresults[#srvresults+1] = record + end + end + end + end + end + end + condvar("signal") end action = function(host) - local domainname = stdnse.get_script_args('dns-brute.domain') - if not domainname then - domainname = guess_domain(host) - end - if not domainname then - return string.format("Can't guess domain of \"%s\"; use %s.domain script argument.", stdnse.get_hostname(host), SCRIPT_NAME) - end + local domainname = stdnse.get_script_args('dns-brute.domain') + if not domainname then + domainname = guess_domain(host) + end + if not domainname then + return string.format("Can't guess domain of \"%s\"; use %s.domain script argument.", stdnse.get_hostname(host), SCRIPT_NAME) + end - if not nmap.registry.bruteddomains then - nmap.registry.bruteddomains = {} - end + if not nmap.registry.bruteddomains then + nmap.registry.bruteddomains = {} + end - if nmap.registry.bruteddomains[domainname] then - stdnse.print_debug("Skipping already-bruted domain %s", domainname) - return nil - end + if nmap.registry.bruteddomains[domainname] then + stdnse.print_debug("Skipping already-bruted domain %s", domainname) + return nil + end - nmap.registry.bruteddomains[domainname] = true - 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 dosrv = stdnse.get_script_args("dns-brute.srv") or false - stdnse.print_debug("THREADS: "..max_threads) - -- First look for dns-brute.hostlist - local fileName = stdnse.get_script_args('dns-brute.hostlist') - -- Check fetchfile locations, then relative paths - local commFile = (fileName and nmap.fetchfile(fileName)) or fileName - -- Finally, fall back to vhosts-default.lst - commFile = commFile or nmap.fetchfile("nselib/data/vhosts-default.lst") - local hostlist = {} - if commFile then - for l in io.lines(commFile) do - if not l:match("#!comment:") then - table.insert(hostlist, l) - end - end - else - stdnse.print_debug(1, "%s: Cannot find hostlist file, quitting", SCRIPT_NAME) - return - end + nmap.registry.bruteddomains[domainname] = true + 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 dosrv = stdnse.get_script_args("dns-brute.srv") or false + stdnse.print_debug("THREADS: "..max_threads) + -- First look for dns-brute.hostlist + local fileName = stdnse.get_script_args('dns-brute.hostlist') + -- Check fetchfile locations, then relative paths + local commFile = (fileName and nmap.fetchfile(fileName)) or fileName + -- Finally, fall back to vhosts-default.lst + commFile = commFile or nmap.fetchfile("nselib/data/vhosts-default.lst") + local hostlist = {} + if commFile then + for l in io.lines(commFile) do + if not l:match("#!comment:") then + table.insert(hostlist, l) + end + end + else + stdnse.print_debug(1, "%s: Cannot find hostlist file, quitting", SCRIPT_NAME) + return + end - local threads, results, srvresults = {}, {}, {} - local condvar = nmap.condvar( results ) - local i = 1 - local howmany = math.floor(#hostlist/max_threads)+1 - stdnse.print_debug("Hosts per thread: "..howmany) - repeat - local j = math.min(i+howmany, #hostlist) - local name_iter = array_iter(hostlist, i, j) - threads[stdnse.new_thread(thread_main, domainname, results, name_iter)] = true - i = j+1 - until i > #hostlist - local done - -- wait for all threads to finish - while( not(done) ) do - done = true - for thread in pairs(threads) do - if (coroutine.status(thread) ~= "dead") then done = false end - end - if ( not(done) ) then - condvar("wait") - end - end + local threads, results, srvresults = {}, {}, {} + local condvar = nmap.condvar( results ) + local i = 1 + local howmany = math.floor(#hostlist/max_threads)+1 + stdnse.print_debug("Hosts per thread: "..howmany) + repeat + local j = math.min(i+howmany, #hostlist) + local name_iter = array_iter(hostlist, i, j) + threads[stdnse.new_thread(thread_main, domainname, results, name_iter)] = true + i = j+1 + until i > #hostlist + local done + -- wait for all threads to finish + while( not(done) ) do + done = true + for thread in pairs(threads) do + if (coroutine.status(thread) ~= "dead") then done = false end + end + if ( not(done) ) then + condvar("wait") + end + end - if(dosrv) then + if(dosrv) then -- First look for dns-brute.srvlist fileName = stdnse.get_script_args('dns-brute.srvlist') -- Check fetchfile locations, then relative paths @@ -245,44 +245,44 @@ action = function(host) end end - i = 1 - threads = {} - howmany = math.floor(#srvlist/max_threads)+1 - condvar = nmap.condvar( srvresults ) - stdnse.print_debug("SRV's per thread: "..howmany) - repeat - local j = math.min(i+howmany, #srvlist) - local name_iter = array_iter(srvlist, i, j) - threads[stdnse.new_thread(srv_main, domainname, srvresults, name_iter)] = true - i = j+1 - until i > #srvlist - local done - -- wait for all threads to finish - while( not(done) ) do - done = true - for thread in pairs(threads) do - if (coroutine.status(thread) ~= "dead") then done = false end - end - if ( not(done) ) then - condvar("wait") - end - end + i = 1 + threads = {} + howmany = math.floor(#srvlist/max_threads)+1 + condvar = nmap.condvar( srvresults ) + stdnse.print_debug("SRV's per thread: "..howmany) + repeat + local j = math.min(i+howmany, #srvlist) + local name_iter = array_iter(srvlist, i, j) + threads[stdnse.new_thread(srv_main, domainname, srvresults, name_iter)] = true + i = j+1 + until i > #srvlist + local done + -- wait for all threads to finish + while( not(done) ) do + done = true + for thread in pairs(threads) do + if (coroutine.status(thread) ~= "dead") then done = false end + end + if ( not(done) ) then + condvar("wait") + end + end else stdnse.print_debug(1, "%s: Cannot find srvlist file, skipping", SCRIPT_NAME) end - end + end - local response = stdnse.output_table() - if(#results==0) then - setmetatable(results, { __tostring = function(t) return "No results." end }) - end - response["DNS Brute-force hostnames"] = results - if(dosrv) then - if(#srvresults==0) then - setmetatable(srvresults, { __tostring = function(t) return "No results." end }) - end - response["SRV results"] = srvresults - end - return response + local response = stdnse.output_table() + if(#results==0) then + setmetatable(results, { __tostring = function(t) return "No results." end }) + end + response["DNS Brute-force hostnames"] = results + if(dosrv) then + if(#srvresults==0) then + setmetatable(srvresults, { __tostring = function(t) return "No results." end }) + end + response["SRV results"] = srvresults + end + return response end diff --git a/scripts/dns-check-zone.nse b/scripts/dns-check-zone.nse index 8afcedcff..19ac4a341 100644 --- a/scripts/dns-check-zone.nse +++ b/scripts/dns-check-zone.nse @@ -59,392 +59,392 @@ hostrule = function(host) return ( arg_domain ~= nil ) end local PROBE_HOST = "scanme.nmap.org" local Status = { - PASS = "PASS", - FAIL = "FAIL", + PASS = "PASS", + FAIL = "FAIL", } local function isValidSOA(res) - if ( not(res) or type(res.answers) ~= "table" or type(res.answers[1].SOA) ~= "table" ) then - return false - end - return true + if ( not(res) or type(res.answers) ~= "table" or type(res.answers[1].SOA) ~= "table" ) then + return false + end + return true end local dns_checks = { - ["NS"] = { - { - desc = "Recursive queries", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) - local result = {} + ["NS"] = { + { + desc = "Recursive queries", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) + local result = {} - if ( not(status) ) then - return false, "Failed to retrieve list of DNS servers" - end - for _, srv in ipairs(res or {}) do - local status, res = dns.query(PROBE_HOST, { host = srv, dtype='A' }) - if ( status ) then - table.insert(result, res) - end - end + if ( not(status) ) then + return false, "Failed to retrieve list of DNS servers" + end + for _, srv in ipairs(res or {}) do + local status, res = dns.query(PROBE_HOST, { host = srv, dtype='A' }) + if ( status ) then + table.insert(result, res) + end + end - local output = "None of the servers allow recursive queries." - if ( 0 < #result ) then - output = ("The following servers allow recursive queries: %s"):format(stdnse.strjoin(", ", result)) - return true, { status = Status.FAIL, output = output } - end - return true, { status = Status.PASS, output = output } - end - }, + local output = "None of the servers allow recursive queries." + if ( 0 < #result ) then + output = ("The following servers allow recursive queries: %s"):format(stdnse.strjoin(", ", result)) + return true, { status = Status.FAIL, output = output } + end + return true, { status = Status.PASS, output = output } + end + }, - { - desc = "Multiple name servers", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) + { + desc = "Multiple name servers", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) - if ( not(status) ) then - return false, "Failed to retrieve list of DNS servers" - end + if ( not(status) ) then + return false, "Failed to retrieve list of DNS servers" + end - local status = Status.FAIL - if ( 1 < #res ) then - status = Status.PASS - end - return true, { status = status, output = ("Server has %d name servers"):format(#res) } - end - }, + local status = Status.FAIL + if ( 1 < #res ) then + status = Status.PASS + end + return true, { status = status, output = ("Server has %d name servers"):format(#res) } + end + }, - { - desc = "DNS name server IPs are public", - func = function(domain, server) + { + desc = "DNS name server IPs are public", + func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) - if ( not(status) ) then - return false, "Failed to retrieve list of DNS servers" - end + local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) + if ( not(status) ) then + return false, "Failed to retrieve list of DNS servers" + end - local result = {} - for _, srv in ipairs(res or {}) do - local status, res = dns.query(srv, { dtype='A', retAll = true }) - if ( not(status) ) then - return false, ("Failed to retrieve IP for DNS: %s"):format(srv) - end - for _, ip in ipairs(res) do - if ( ipOps.isPrivate(ip) ) then - table.insert(result, ip) - end - end - end + local result = {} + for _, srv in ipairs(res or {}) do + local status, res = dns.query(srv, { dtype='A', retAll = true }) + if ( not(status) ) then + return false, ("Failed to retrieve IP for DNS: %s"):format(srv) + end + for _, ip in ipairs(res) do + if ( ipOps.isPrivate(ip) ) then + table.insert(result, ip) + end + end + end - local output = "All DNS IPs were public" - if ( 0 < #result ) then - output = ("The following private IPs were detected: %s"):format(stdnse.strjoin(", ", result)) - status = Status.FAIL - else - status = Status.PASS - end + local output = "All DNS IPs were public" + if ( 0 < #result ) then + output = ("The following private IPs were detected: %s"):format(stdnse.strjoin(", ", result)) + status = Status.FAIL + else + status = Status.PASS + end - return true, { status = status, output = output } - end - }, + return true, { status = status, output = output } + end + }, - { - desc = "DNS server response", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) - if ( not(status) ) then - return false, "Failed to retrieve list of DNS servers" - end + { + desc = "DNS server response", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) + if ( not(status) ) then + return false, "Failed to retrieve list of DNS servers" + end - local result = {} - for _, srv in ipairs(res or {}) do - local status, res = dns.query(domain, { host = srv, dtype='SOA', retPkt = true }) - if ( not(status) ) then - table.insert(result, res) - end - end + local result = {} + for _, srv in ipairs(res or {}) do + local status, res = dns.query(domain, { host = srv, dtype='SOA', retPkt = true }) + if ( not(status) ) then + table.insert(result, res) + end + end - local output = "All servers respond to DNS queries" - if ( 0 < #result ) then - output = ("The following servers did not respond to DNS queries: %s"):format(stdnse.strjoin(", ", result)) - return true, { status = Status.FAIL, output = output } - end - return true, { status = Status.PASS, output = output } - end - }, + local output = "All servers respond to DNS queries" + if ( 0 < #result ) then + output = ("The following servers did not respond to DNS queries: %s"):format(stdnse.strjoin(", ", result)) + return true, { status = Status.FAIL, output = output } + end + return true, { status = Status.PASS, output = output } + end + }, - { - desc = "Missing nameservers reported by parent", - func = function(domain, server) - local tld = domain:match("%.(.*)$") - local status, res = dns.query(tld, { dtype = "NS", retAll = true }) - if ( not(status) ) then - return false, "Failed to retrieve list of TLD DNS servers" - end + { + desc = "Missing nameservers reported by parent", + func = function(domain, server) + local tld = domain:match("%.(.*)$") + local status, res = dns.query(tld, { dtype = "NS", retAll = true }) + if ( not(status) ) then + return false, "Failed to retrieve list of TLD DNS servers" + end - local status, parent_res = dns.query(domain, { host = res, dtype = "NS", retAll = true, retPkt = true, noauth = true } ) - if ( not(status) ) then - return false, "Failed to retrieve a list of parent DNS servers" - end + local status, parent_res = dns.query(domain, { host = res, dtype = "NS", retAll = true, retPkt = true, noauth = true } ) + if ( not(status) ) then + return false, "Failed to retrieve a list of parent DNS servers" + end - 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" - end + 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" + end - local parent_dns = {} - for _, auth in ipairs(parent_res.auth) do - parent_dns[auth.domain] = true - end + local parent_dns = {} + for _, auth in ipairs(parent_res.auth) do + parent_dns[auth.domain] = true + end - status, res = dns.query(domain, { host = server, dtype = "NS", retAll = true } ) - if ( not(status) ) then - return false, "Failed to retrieve a list of DNS servers" - end + status, res = dns.query(domain, { host = server, dtype = "NS", retAll = true } ) + if ( not(status) ) then + return false, "Failed to retrieve a list of DNS servers" + end - local domain_dns = {} - for _,srv in ipairs(res) do domain_dns[srv] = true end + local domain_dns = {} + for _,srv in ipairs(res) do domain_dns[srv] = true end - local result = {} - for srv in pairs(domain_dns) do - if ( not(parent_dns[srv]) ) then - table.insert(result, srv) - end - end + local result = {} + for srv in pairs(domain_dns) do + if ( not(parent_dns[srv]) ) then + table.insert(result, srv) + end + end - if ( 0 < #result ) then - 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 } - end + if ( 0 < #result ) then + 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 } + end - return true, { status = Status.PASS, output = "All DNS servers match" } - end, - }, + return true, { status = Status.PASS, output = "All DNS servers match" } + end, + }, - { - desc = "Missing nameservers reported by your nameservers", - func = function(domain, server) - local tld = domain:match("%.(.*)$") - local status, res = dns.query(tld, { dtype = "NS", retAll = true }) - if ( not(status) ) then - return false, "Failed to retrieve list of TLD DNS servers" - end + { + desc = "Missing nameservers reported by your nameservers", + func = function(domain, server) + local tld = domain:match("%.(.*)$") + local status, res = dns.query(tld, { dtype = "NS", retAll = true }) + if ( not(status) ) then + return false, "Failed to retrieve list of TLD DNS servers" + end - local status, parent_res = dns.query(domain, { host = res, dtype = "NS", retAll = true, retPkt = true, noauth = true } ) - if ( not(status) ) then - return false, "Failed to retrieve a list of parent DNS servers" - end + local status, parent_res = dns.query(domain, { host = res, dtype = "NS", retAll = true, retPkt = true, noauth = true } ) + if ( not(status) ) then + return false, "Failed to retrieve a list of parent DNS servers" + end - 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" - end + 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" + end - local parent_dns = {} - for _, auth in ipairs(parent_res.auth) do - parent_dns[auth.domain] = true - end + local parent_dns = {} + for _, auth in ipairs(parent_res.auth) do + parent_dns[auth.domain] = true + end - status, res = dns.query(domain, { host = server, dtype = "NS", retAll = true } ) - if ( not(status) ) then - return false, "Failed to retrieve a list of DNS servers" - end + status, res = dns.query(domain, { host = server, dtype = "NS", retAll = true } ) + if ( not(status) ) then + return false, "Failed to retrieve a list of DNS servers" + end - local domain_dns = {} - for _,srv in ipairs(res) do domain_dns[srv] = true end + local domain_dns = {} + for _,srv in ipairs(res) do domain_dns[srv] = true end - local result = {} - for srv in pairs(parent_dns) do - if ( not(domain_dns[srv]) ) then - table.insert(result, srv) - end - end + local result = {} + for srv in pairs(parent_dns) do + if ( not(domain_dns[srv]) ) then + table.insert(result, srv) + end + end - if ( 0 < #result ) then - 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 } - end + if ( 0 < #result ) then + 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 } + end - return true, { status = Status.PASS, output = "All DNS servers match" } - end, - }, + return true, { status = Status.PASS, output = "All DNS servers match" } + end, + }, - }, + }, - ["SOA"] = - { - { - desc = "SOA REFRESH", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) - if ( not(status) or not(isValidSOA(res)) ) then - return false, "Failed to retrieve SOA record" - end + ["SOA"] = + { + { + desc = "SOA REFRESH", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) + if ( not(status) or not(isValidSOA(res)) ) then + return false, "Failed to retrieve SOA record" + end - local refresh = tonumber(res.answers[1].SOA.refresh) - if ( not(refresh) ) then - return false, "Failed to retrieve SOA REFRESH" - end + local refresh = tonumber(res.answers[1].SOA.refresh) + if ( not(refresh) ) then + return false, "Failed to retrieve SOA REFRESH" + end - if ( refresh < 1200 or refresh > 43200 ) then - return true, { status = Status.FAIL, output = ("SOA REFRESH was NOT within recommended range (%ss)"):format(refresh) } - else - return true, { status = Status.PASS, output = ("SOA REFRESH was within recommended range (%ss)"):format(refresh) } - end - end - }, + if ( refresh < 1200 or refresh > 43200 ) then + return true, { status = Status.FAIL, output = ("SOA REFRESH was NOT within recommended range (%ss)"):format(refresh) } + else + return true, { status = Status.PASS, output = ("SOA REFRESH was within recommended range (%ss)"):format(refresh) } + end + end + }, - { - desc = "SOA RETRY", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) - if ( not(status) or not(isValidSOA(res)) ) then - return false, "Failed to retrieve SOA record" - end + { + desc = "SOA RETRY", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) + if ( not(status) or not(isValidSOA(res)) ) then + return false, "Failed to retrieve SOA record" + end - local retry = tonumber(res.answers[1].SOA.retry) - if ( not(retry) ) then - return false, "Failed to retrieve SOA RETRY" - end + local retry = tonumber(res.answers[1].SOA.retry) + if ( not(retry) ) then + return false, "Failed to retrieve SOA RETRY" + end - if ( retry < 180 ) then - return true, { status = Status.FAIL, output = ("SOA RETRY was NOT within recommended range (%ss)"):format(retry) } - else - return true, { status = Status.PASS, output = ("SOA RETRY was within recommended range (%ss)"):format(retry) } - end - end - }, + if ( retry < 180 ) then + return true, { status = Status.FAIL, output = ("SOA RETRY was NOT within recommended range (%ss)"):format(retry) } + else + return true, { status = Status.PASS, output = ("SOA RETRY was within recommended range (%ss)"):format(retry) } + end + end + }, - { - desc = "SOA EXPIRE", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) - if ( not(status) or not(isValidSOA(res)) ) then - return false, "Failed to retrieve SOA record" - end + { + desc = "SOA EXPIRE", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) + if ( not(status) or not(isValidSOA(res)) ) then + return false, "Failed to retrieve SOA record" + end - local expire = tonumber(res.answers[1].SOA.expire) - if ( not(expire) ) then - return false, "Failed to retrieve SOA EXPIRE" - end + local expire = tonumber(res.answers[1].SOA.expire) + if ( not(expire) ) then + return false, "Failed to retrieve SOA EXPIRE" + end - if ( expire < 1209600 or expire > 2419200 ) then - return true, { status = Status.FAIL, output = ("SOA EXPIRE was NOT within recommended range (%ss)"):format(expire) } - else - return true, { status = Status.PASS, output = ("SOA EXPIRE was within recommended range (%ss)"):format(expire) } - end - end - }, + if ( expire < 1209600 or expire > 2419200 ) then + return true, { status = Status.FAIL, output = ("SOA EXPIRE was NOT within recommended range (%ss)"):format(expire) } + else + return true, { status = Status.PASS, output = ("SOA EXPIRE was within recommended range (%ss)"):format(expire) } + end + end + }, - { - desc = "SOA MNAME entry check", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) - if ( not(status) or not(isValidSOA(res)) ) then - return false, "Failed to retrieve SOA record" - end - local mname = res.answers[1].SOA.mname + { + desc = "SOA MNAME entry check", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='SOA', retPkt=true }) + if ( not(status) or not(isValidSOA(res)) ) then + return false, "Failed to retrieve SOA record" + end + local mname = res.answers[1].SOA.mname - status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) - if ( not(status) ) then - return false, "Failed to retrieve list of DNS servers" - end + status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) + if ( not(status) ) then + return false, "Failed to retrieve list of DNS servers" + end - for _, srv in ipairs(res or {}) do - if ( srv == mname ) then - return true, { status = Status.PASS, output = "SOA MNAME record is listed as DNS server" } - end - end - return true, { status = Status.FAIL, output = "SOA MNAME record is NOT listed as DNS server" } - end - }, + for _, srv in ipairs(res or {}) do + if ( srv == mname ) then + return true, { status = Status.PASS, output = "SOA MNAME record is listed as DNS server" } + end + end + return true, { status = Status.FAIL, output = "SOA MNAME record is NOT listed as DNS server" } + end + }, - { - desc = "Zone serial numbers", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) - if ( not(status) ) then - return false, "Failed to retrieve list of DNS servers" - end + { + desc = "Zone serial numbers", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='NS', retAll = true }) + if ( not(status) ) then + return false, "Failed to retrieve list of DNS servers" + end - local result = {} - local serial + local result = {} + local serial - for _, srv in ipairs(res or {}) do - local status, res = dns.query(domain, { host = srv, dtype='SOA', retPkt = true }) - if ( not(status) or not(isValidSOA(res)) ) then - return false, "Failed to retrieve SOA record" - end + for _, srv in ipairs(res or {}) do + local status, res = dns.query(domain, { host = srv, dtype='SOA', retPkt = true }) + if ( not(status) or not(isValidSOA(res)) ) then + return false, "Failed to retrieve SOA record" + end - local s = res.answers[1].SOA.serial - if ( not(serial) ) then - serial = s - elseif( serial ~= s ) then - return true, { status = Status.FAIL, output = "Different zone serials were detected" } - end - end + local s = res.answers[1].SOA.serial + if ( not(serial) ) then + serial = s + elseif( serial ~= s ) then + return true, { status = Status.FAIL, output = "Different zone serials were detected" } + end + end - return true, { status = Status.PASS, output = "Zone serials match" } - end, - }, - }, + return true, { status = Status.PASS, output = "Zone serials match" } + end, + }, + }, - ["MX"] = { + ["MX"] = { - { - desc = "Reverse MX A records", - func = function(domain, server) - local status, res = dns.query(domain, { host = server, dtype='MX', retAll = true }) - if ( not(status) ) then - return false, "Failed to retrieve list of mail servers" - end + { + desc = "Reverse MX A records", + func = function(domain, server) + local status, res = dns.query(domain, { host = server, dtype='MX', retAll = true }) + if ( not(status) ) then + return false, "Failed to retrieve list of mail servers" + end - local result = {} - for _, record in ipairs(res or {}) do - local prio, mx = record:match("^(%d*):([^:]*)") - local ips - status, ips = dns.query(mx, { dtype='A', retAll=true }) - if ( not(status) ) then - return false, "Failed to retrieve A records for MX" - end + local result = {} + for _, record in ipairs(res or {}) do + local prio, mx = record:match("^(%d*):([^:]*)") + local ips + status, ips = dns.query(mx, { dtype='A', retAll=true }) + if ( not(status) ) then + return false, "Failed to retrieve A records for MX" + end - for _, ip in ipairs(ips) do - local status, res = dns.query(dns.reverse(ip), { dtype='PTR' }) - if ( not(status) ) then - table.insert(result, ip) - end - end - end + for _, ip in ipairs(ips) do + local status, res = dns.query(dns.reverse(ip), { dtype='PTR' }) + if ( not(status) ) then + table.insert(result, ip) + end + end + end - local output = "All MX records have PTR records" - if ( 0 < #result ) then - output = ("The following IPs do not have PTR records: %s"):format(stdnse.strjoin(", ", result)) - return true, { status = Status.FAIL, output = output } - end - return true, { status = Status.PASS, output = output } - end - }, + local output = "All MX records have PTR records" + if ( 0 < #result ) then + output = ("The following IPs do not have PTR records: %s"):format(stdnse.strjoin(", ", result)) + return true, { status = Status.FAIL, output = output } + end + return true, { status = Status.PASS, output = output } + end + }, - } + } } action = function(host, port) - local server = host.ip - local output = { name = ("DNS check results for domain: %s"):format(arg_domain) } + local server = host.ip + local output = { name = ("DNS check results for domain: %s"):format(arg_domain) } - for group in pairs(dns_checks) do - local group_output = { name = group } - for _, check in ipairs(dns_checks[group]) do - local status, res = check.func(arg_domain, server) - if ( status ) then - local test_res = ("%s - %s"):format(res.status, check.desc) - table.insert(group_output, { name = test_res, res.output }) - else - local test_res = ("ERROR - %s"):format(check.desc) - table.insert(group_output, { name = test_res, res }) - end - end - table.insert(output, group_output) - end - return stdnse.format_output(true, output) + for group in pairs(dns_checks) do + local group_output = { name = group } + for _, check in ipairs(dns_checks[group]) do + local status, res = check.func(arg_domain, server) + if ( status ) then + local test_res = ("%s - %s"):format(res.status, check.desc) + table.insert(group_output, { name = test_res, res.output }) + else + local test_res = ("ERROR - %s"):format(check.desc) + table.insert(group_output, { name = test_res, res }) + end + end + table.insert(output, group_output) + end + return stdnse.format_output(true, output) end diff --git a/scripts/dns-client-subnet-scan.nse b/scripts/dns-client-subnet-scan.nse index 109bb2c5d..d0f080db9 100644 --- a/scripts/dns-client-subnet-scan.nse +++ b/scripts/dns-client-subnet-scan.nse @@ -58,300 +58,300 @@ local argMask = stdnse.get_script_args(SCRIPT_NAME .. '.mask') or 24 local argAddr = stdnse.get_script_args(SCRIPT_NAME .. '.address') prerule = function() - if ( not(argDomain) or nmap.address_family() ~= "inet" ) then - return false - end - return true + if ( not(argDomain) or nmap.address_family() ~= "inet" ) then + return false + end + return true end portrule = function(host, port) - if ( nmap.address_family() ~= "inet" ) then - return false - else - return shortport.port_or_service(53, "domain", {"tcp", "udp"})(host, port) - end + if ( nmap.address_family() ~= "inet" ) then + return false + else + return shortport.port_or_service(53, "domain", {"tcp", "udp"})(host, port) + end end local areaIPs = { - A4 = {ip=47763456, desc="GB,A4,Bath"}, - A5 = {ip=1043402336, desc="GB,A5,Biggleswade"}, - A6 = {ip=1364222182, desc="FR,A6,Chèvremont"}, - A7 = {ip=35357952, desc="GB,A7,Birmingham"}, - A8 = {ip=1050694009, desc="FR,A8,Romainville"}, - A9 = {ip=534257152, desc="FR,A9,Montpellier"}, - AB = {ip=2156920832, desc="CA,AB,Edmonton"}, - AK = {ip=202125312, desc="US,AK,Anchorage"}, - B1 = {ip=1041724648, desc="FR,B1,Robert"}, - B2 = {ip=35138048, desc="GB,B2,Bournemouth"}, - B3 = {ip=33949696, desc="FR,B3,Toulouse"}, - B4 = {ip=1050704998, desc="FR,B4,Lomme"}, - B5 = {ip=35213312, desc="GB,B5,Wembley"}, - B6 = {ip=773106752, desc="FR,B6,Amiens"}, - B7 = {ip=35148800, desc="GB,B7,Bristol"}, - B8 = {ip=786088496, desc="FR,B8,Valbonne"}, - B9 = {ip=33753088, desc="FR,B9,Lyon"}, - BC = {ip=201674096, desc="CA,BC,Victoria"}, - C1 = {ip=522223616, desc="FR,C1,Strasbourg"}, - C2 = {ip=41598976, desc="GB,C2,Halifax"}, - C3 = {ip=534676272, desc="GB,C3,Cambridge"}, - C5 = {ip=1043410032, desc="GB,C5,Runcorn"}, - C6 = {ip=773987544, desc="GB,C6,Saltash"}, - C7 = {ip=35165184, desc="GB,C7,Coventry"}, - C8 = {ip=35248128, desc="GB,C8,Croydon"}, - C9 = {ip=1892301824, desc="PH,C9,Iloilo"}, - D1 = {ip=35414016, desc="GB,D1,Darlington"}, - D2 = {ip=35164672, desc="GB,D2,Derby"}, - D3 = {ip=35301376, desc="GB,D3,Chesterfield"}, - D4 = {ip=1043450424, desc="GB,D4,Barnstaple"}, - D5 = {ip=2036385792, desc="PH,D5,Legaspi"}, - D7 = {ip=41451520, desc="GB,D7,Dudley"}, - D8 = {ip=35279104, desc="GB,D8,Durham"}, - D9 = {ip=460228608, desc="PH,D9,Manila"}, - DC = {ip=68514448, desc="US,DC,Washington"}, - E1 = {ip=1040645056, desc="GB,E1,Beverley"}, - E2 = {ip=35206912, desc="GB,E2,Brighton"}, - E3 = {ip=47822848, desc="GB,E3,Enfield"}, - E4 = {ip=39874560, desc="GB,E4,Colchester"}, - E5 = {ip=35270656, desc="GB,E5,Gateshead"}, - E6 = {ip=1368606720, desc="GB,E6,Coleford"}, - E7 = {ip=1051376056, desc="GB,E7,Woolwich"}, - E8 = {ip=1044737528, desc="GB,E8,Hackney"}, - F1 = {ip=1043451648, desc="GB,F1,Hammersmith"}, - F2 = {ip=35176448, desc="GB,F2,Basingstoke"}, - F4 = {ip=47998976, desc="GB,F4,Harrow"}, - F5 = {ip=1040622704, desc="GB,F5,Hart"}, - F6 = {ip=35230720, desc="GB,F6,Romford"}, - F8 = {ip=35214848, desc="GB,F8,Watford"}, - F9 = {ip=41693184, desc="GB,F9,Uxbridge"}, - G1 = {ip=41437184, desc="GB,G1,Hounslow"}, - G2 = {ip=35188224, desc="GB,G2,Ryde"}, - G3 = {ip=41861120, desc="GB,G3,Islington"}, - G4 = {ip=1040704992, desc="GB,G4,Kensington"}, - G5 = {ip=41506816, desc="GB,G5,Ashford"}, - G6 = {ip=786894336, desc="GB,G6,Hull"}, - G8 = {ip=40112128, desc="GB,G8,Huddersfield"}, - G9 = {ip=1380217968, desc="GB,G9,Knowsley"}, - H1 = {ip=1044731464, desc="GB,H1,Lambeth"}, - H2 = {ip=3512017264, desc="GB,H2,Earby"}, - H3 = {ip=35221504, desc="GB,H3,Leeds"}, - H4 = {ip=35158016, desc="GB,H4,Leicester"}, - H5 = {ip=1043402716, desc="GB,H5,Loughborough"}, - H6 = {ip=41732608, desc="GB,H6,Catford"}, - H7 = {ip=41863168, desc="GB,H7,Lincoln"}, - H8 = {ip=35294976, desc="GB,H8,Liverpool"}, - H9 = {ip=35196928, desc="GB,H9,London"}, - I1 = {ip=35253760, desc="GB,I1,Luton"}, - I2 = {ip=35263488, desc="GB,I2,Manchester"}, - I3 = {ip=47714304, desc="GB,I3,Rochester"}, - I4 = {ip=1298651136, desc="GB,I4,Morden"}, - I5 = {ip=1382961968, desc="GB,I5,Middlesborough"}, - I8 = {ip=1371219061, desc="GB,I8,Stepney"}, - I9 = {ip=35282944, desc="GB,I9,Norwich"}, - IA = {ip=201438272, desc="US,IA,Urbandale"}, - J1 = {ip=523578880, desc="GB,J1,Daventry"}, - J2 = {ip=788492344, desc="GB,J2,Grimsby"}, - J3 = {ip=3282790208, desc="GB,J3,Flixborough"}, - J5 = {ip=41759232, desc="GB,J5,Wallsend"}, - J6 = {ip=1043412268, desc="GB,J6,Alnwick"}, - J7 = {ip=41783296, desc="GB,J7,Harrogate"}, - J8 = {ip=35160064, desc="GB,J8,Nottingham"}, - J9 = {ip=47742976, desc="GB,J9,Newark"}, - JA = {ip=1476096512, desc="RU,JA,Kurilsk"}, - K1 = {ip=48015360, desc="GB,K1,Oldham"}, - K2 = {ip=1043402360, desc="GB,K2,Kidlington"}, - K3 = {ip=39956480, desc="GB,K3,Peterborough"}, - K4 = {ip=41735168, desc="GB,K4,Plymouth"}, - K5 = {ip=775747568, desc="GB,K5,Poole"}, - K6 = {ip=774162844, desc="GB,K6,Portsmouth"}, - K7 = {ip=41746432, desc="GB,K7,Reading"}, - K8 = {ip=35229696, desc="GB,K8,Ilford"}, - L1 = {ip=47773696, desc="GB,L1,Twickenham"}, - L2 = {ip=48103424, desc="GB,L2,Rochdale"}, - L3 = {ip=35304192, desc="GB,L3,Rotherham"}, - L4 = {ip=1043416984, desc="GB,L4,Oakham"}, - L5 = {ip=772988024, desc="GB,L5,Salford"}, - L6 = {ip=35336192, desc="GB,L6,Shrewsbury"}, - L7 = {ip=1043419464, desc="GB,L7,Oldbury"}, - L8 = {ip=39936000, desc="GB,L8,Lytham"}, - L9 = {ip=35304448, desc="GB,L9,Sheffield"}, - M1 = {ip=35384320, desc="GB,M1,Slough"}, - M2 = {ip=41470976, desc="GB,M2,Solihull"}, - M4 = {ip=35139584, desc="GB,M4,Southampton"}, - M5 = {ip=1043402176, desc="GB,M5,Southend-on-sea"}, - M6 = {ip=773986248, desc="GB,M6,Hill"}, - M8 = {ip=1443330688, desc="GB,M8,Camberwell"}, - M9 = {ip=35322880, desc="GB,M9,Stafford"}, - MB = {ip=1076550400, desc="CA,MB,Winnipeg"}, - MI = {ip=201393888, desc="US,MI,Saginaw"}, - N1 = {ip=1318741928, desc="GB,N1,Haydock"}, - N2 = {ip=35266560, desc="GB,N2,Stockport"}, - N3 = {ip=41832448, desc="GB,N3,Stockton-on-tees"}, - N4 = {ip=3231559680, desc="GB,N4,Longport"}, - N5 = {ip=1043424608, desc="GB,N5,Beccles"}, - N6 = {ip=35276800, desc="GB,N6,Sunderland"}, - N7 = {ip=41551872, desc="GB,N7,Tadworth"}, - N8 = {ip=41697280, desc="GB,N8,Sutton"}, - N9 = {ip=35252736, desc="GB,N9,Swindon"}, - NB = {ip=2211053568, desc="CA,NB,Fredericton"}, - ND = {ip=201473536, desc="US,ND,Bismarck"}, - NH = {ip=201772808, desc="US,NH,Laconia"}, - NJ = {ip=201352704, desc="US,NJ,Piscataway"}, - NS = {ip=3226164992, desc="CA,NS,Halifax"}, - NT = {ip=3332472320, desc="CA,NT,Yellowknife"}, - NV = {ip=202261184, desc="US,NV,Henderson"}, - O2 = {ip=40251392, desc="GB,O2,Telford"}, - O3 = {ip=35230208, desc="GB,O3,Grays"}, - O4 = {ip=35318784, desc="GB,O4,Torquay"}, - O5 = {ip=1368498352, desc="GB,O5,Poplar"}, - O6 = {ip=1546138112, desc="GB,O6,Stretford"}, - O7 = {ip=35219456, desc="GB,O7,Wakefield"}, - O8 = {ip=35321856, desc="GB,O8,Walsall"}, - O9 = {ip=1359108248, desc="GB,O9,Walthamstow"}, - ON = {ip=201620304, desc="CA,ON,Ottawa"}, - P1 = {ip=1043431736, desc="GB,P1,Wandsworth"}, - P2 = {ip=35260416, desc="GB,P2,Warrington"}, - P3 = {ip=41766912, desc="GB,P3,Nuneaton"}, - P4 = {ip=41893888, desc="GB,P4,Newbury"}, - P5 = {ip=772987648, desc="GB,P5,Westminster"}, - P7 = {ip=41466624, desc="GB,P7,Wigan"}, - P8 = {ip=48087808, desc="GB,P8,Salisbury"}, - P9 = {ip=41793536, desc="GB,P9,Maidenhead"}, - Q1 = {ip=41457664, desc="GB,Q1,Wallasey"}, - Q2 = {ip=1040739840, desc="GB,Q2,Wokingham"}, - Q3 = {ip=35323392, desc="GB,Q3,Wolverhampton"}, - Q4 = {ip=539624744, desc="GB,Q4,Redditch"}, - Q5 = {ip=1043415688, desc="GB,Q5,Wetherby"}, - Q6 = {ip=1043439984, desc="GB,Q6,Antrim"}, - Q7 = {ip=41811456, desc="GB,Q7,Newtownards"}, - Q8 = {ip=1347208672, desc="GB,Q8,Armagh"}, - Q9 = {ip=1044726432, desc="GB,Q9,Connor"}, - QC = {ip=2210594816, desc="CA,QC,Varennes"}, - R1 = {ip=1482707288, desc="GB,R1,Ballymoney"}, - R3 = {ip=47828992, desc="GB,R3,Belfast"}, - R4 = {ip=1051352576, desc="GB,R4,Eden"}, - R5 = {ip=1056827328, desc="GB,R5,Castlereagh"}, - R6 = {ip=47895040, desc="GB,R6,Coleraine"}, - R7 = {ip=3270400320, desc="GB,R7,Dunmore"}, - R8 = {ip=1367996672, desc="GB,R8,Portadown"}, - R9 = {ip=773985608, desc="GB,R9,Square"}, - RI = {ip=67285760, desc="US,RI,Providence"}, - S1 = {ip=1040409048, desc="GB,S1,Drummond"}, - S2 = {ip=1353842208, desc="GB,S2,Enniskillen"}, - S3 = {ip=1368133632, desc="GB,S3,Larne"}, - S4 = {ip=1446384520, desc="GB,S4,Ardmore"}, - S5 = {ip=1043419184, desc="GB,S5,Lisburn"}, - S6 = {ip=1056826304, desc="GB,S6,Londonderry"}, - S7 = {ip=1359111383, desc="GB,S7,Curran"}, - S8 = {ip=1369435392, desc="GB,S8,Waterfoot"}, - S9 = {ip=1043434592, desc="GB,S9,Newry"}, - T1 = {ip=3242033152, desc="GB,T1,Jordanstown"}, - T2 = {ip=1043402000, desc="GB,T2,Bangor"}, - T3 = {ip=1043429728, desc="GB,T3,Omagh"}, - T4 = {ip=1043429520, desc="GB,T4,Strabane"}, - T5 = {ip=39849984, desc="GB,T5,Aberdeen"}, - T6 = {ip=1043407024, desc="GB,T6,Inverurie"}, - T7 = {ip=47917056, desc="GB,T7,Forfar"}, - T8 = {ip=1051457600, desc="GB,T8,Sandbank"}, - T9 = {ip=1043429424, desc="GB,T9,Melrose"}, - TX = {ip=201673024, desc="US,TX,Mckinney"}, - U1 = {ip=1043400976, desc="GB,U1,Alloa"}, - U2 = {ip=1353815544, desc="GB,U2,Langholm"}, - U3 = {ip=1042190336, desc="GB,U3,Dundee"}, - U4 = {ip=1043428036, desc="GB,U4,Newmilns"}, - U5 = {ip=1051334704, desc="GB,U5,Bishopbriggs"}, - U6 = {ip=1040628912, desc="GB,U6,Musselburgh"}, - U7 = {ip=1056881248, desc="GB,U7,Barrhead"}, - U8 = {ip=35188736, desc="GB,U8,Edinburgh"}, - U9 = {ip=1318744616, desc="GB,U9,Blackstone"}, - V1 = {ip=47947776, desc="GB,V1,Kirkcaldy"}, - V2 = {ip=35190784, desc="GB,V2,Glasgow"}, - V4 = {ip=1043417560, desc="GB,V4,Greenock"}, - V5 = {ip=3570359128, desc="GB,V5,Borthwick"}, - V6 = {ip=1398983520, desc="GB,V6,Findhorn"}, - V7 = {ip=1043452928, desc="GB,V7,Saltcoats"}, - V8 = {ip=523564544, desc="GB,V8,Bothwell"}, - V9 = {ip=1353706504, desc="GB,V9,Redland"}, - VT = {ip=201355264, desc="US,VT,Brattleboro"}, - W1 = {ip=1042195200, desc="GB,W1,Perth"}, - W2 = {ip=1043412560, desc="GB,W2,Paisley"}, - W4 = {ip=1056825616, desc="GB,W4,Dundonald"}, - W5 = {ip=1040411544, desc="GB,W5,Douglas"}, - W6 = {ip=41547776, desc="GB,W6,Stirling"}, - W7 = {ip=1443523584, desc="GB,W7,Bearsden"}, - W8 = {ip=534572928, desc="GB,W8,Cross"}, - W9 = {ip=1042221056, desc="GB,W9,Livingston"}, - WA = {ip=201806720, desc="US,WA,Issaquah"}, - WY = {ip=135495936, desc="US,WY,Casper"}, - X1 = {ip=1043425760, desc="GB,X1,Valley"}, - X2 = {ip=773988152, desc="GB,X2,Victoria"}, - X3 = {ip=35149824, desc="GB,X3,Bridgend"}, - X4 = {ip=1043402272, desc="GB,X4,Blackwood"}, - X5 = {ip=39946240, desc="GB,X5,Cardiff"}, - X6 = {ip=1043435700, desc="GB,X6,Aberystwyth"}, - X7 = {ip=1043408760, desc="GB,X7,Llanelli"}, - X8 = {ip=1368926208, desc="GB,X8,Abergele"}, - X9 = {ip=1043411032, desc="GB,X9,Rhyl"}, - Y1 = {ip=1043407256, desc="GB,Y1,Holywell"}, - Y2 = {ip=1043401576, desc="GB,Y2,Caernarfon"}, - Y4 = {ip=1043428692, desc="GB,Y4,Cwmbran"}, - Y5 = {ip=3265794544, desc="GB,Y5,Cwmafan"}, - Y6 = {ip=35153920, desc="GB,Y6,Newport"}, - Y7 = {ip=1353763984, desc="GB,Y7,Haverfordwest"}, - Y8 = {ip=1043430344, desc="GB,Y8,Welshpool"}, - Z1 = {ip=40116224, desc="GB,Z1,Swansea"}, - Z2 = {ip=40189952, desc="GB,Z2,Pontypool"}, - Z3 = {ip=35147776, desc="GB,Z3,Barry"}, - Z4 = {ip=40321024, desc="GB,Z4,Wrexham"} + A4 = {ip=47763456, desc="GB,A4,Bath"}, + A5 = {ip=1043402336, desc="GB,A5,Biggleswade"}, + A6 = {ip=1364222182, desc="FR,A6,Chèvremont"}, + A7 = {ip=35357952, desc="GB,A7,Birmingham"}, + A8 = {ip=1050694009, desc="FR,A8,Romainville"}, + A9 = {ip=534257152, desc="FR,A9,Montpellier"}, + AB = {ip=2156920832, desc="CA,AB,Edmonton"}, + AK = {ip=202125312, desc="US,AK,Anchorage"}, + B1 = {ip=1041724648, desc="FR,B1,Robert"}, + B2 = {ip=35138048, desc="GB,B2,Bournemouth"}, + B3 = {ip=33949696, desc="FR,B3,Toulouse"}, + B4 = {ip=1050704998, desc="FR,B4,Lomme"}, + B5 = {ip=35213312, desc="GB,B5,Wembley"}, + B6 = {ip=773106752, desc="FR,B6,Amiens"}, + B7 = {ip=35148800, desc="GB,B7,Bristol"}, + B8 = {ip=786088496, desc="FR,B8,Valbonne"}, + B9 = {ip=33753088, desc="FR,B9,Lyon"}, + BC = {ip=201674096, desc="CA,BC,Victoria"}, + C1 = {ip=522223616, desc="FR,C1,Strasbourg"}, + C2 = {ip=41598976, desc="GB,C2,Halifax"}, + C3 = {ip=534676272, desc="GB,C3,Cambridge"}, + C5 = {ip=1043410032, desc="GB,C5,Runcorn"}, + C6 = {ip=773987544, desc="GB,C6,Saltash"}, + C7 = {ip=35165184, desc="GB,C7,Coventry"}, + C8 = {ip=35248128, desc="GB,C8,Croydon"}, + C9 = {ip=1892301824, desc="PH,C9,Iloilo"}, + D1 = {ip=35414016, desc="GB,D1,Darlington"}, + D2 = {ip=35164672, desc="GB,D2,Derby"}, + D3 = {ip=35301376, desc="GB,D3,Chesterfield"}, + D4 = {ip=1043450424, desc="GB,D4,Barnstaple"}, + D5 = {ip=2036385792, desc="PH,D5,Legaspi"}, + D7 = {ip=41451520, desc="GB,D7,Dudley"}, + D8 = {ip=35279104, desc="GB,D8,Durham"}, + D9 = {ip=460228608, desc="PH,D9,Manila"}, + DC = {ip=68514448, desc="US,DC,Washington"}, + E1 = {ip=1040645056, desc="GB,E1,Beverley"}, + E2 = {ip=35206912, desc="GB,E2,Brighton"}, + E3 = {ip=47822848, desc="GB,E3,Enfield"}, + E4 = {ip=39874560, desc="GB,E4,Colchester"}, + E5 = {ip=35270656, desc="GB,E5,Gateshead"}, + E6 = {ip=1368606720, desc="GB,E6,Coleford"}, + E7 = {ip=1051376056, desc="GB,E7,Woolwich"}, + E8 = {ip=1044737528, desc="GB,E8,Hackney"}, + F1 = {ip=1043451648, desc="GB,F1,Hammersmith"}, + F2 = {ip=35176448, desc="GB,F2,Basingstoke"}, + F4 = {ip=47998976, desc="GB,F4,Harrow"}, + F5 = {ip=1040622704, desc="GB,F5,Hart"}, + F6 = {ip=35230720, desc="GB,F6,Romford"}, + F8 = {ip=35214848, desc="GB,F8,Watford"}, + F9 = {ip=41693184, desc="GB,F9,Uxbridge"}, + G1 = {ip=41437184, desc="GB,G1,Hounslow"}, + G2 = {ip=35188224, desc="GB,G2,Ryde"}, + G3 = {ip=41861120, desc="GB,G3,Islington"}, + G4 = {ip=1040704992, desc="GB,G4,Kensington"}, + G5 = {ip=41506816, desc="GB,G5,Ashford"}, + G6 = {ip=786894336, desc="GB,G6,Hull"}, + G8 = {ip=40112128, desc="GB,G8,Huddersfield"}, + G9 = {ip=1380217968, desc="GB,G9,Knowsley"}, + H1 = {ip=1044731464, desc="GB,H1,Lambeth"}, + H2 = {ip=3512017264, desc="GB,H2,Earby"}, + H3 = {ip=35221504, desc="GB,H3,Leeds"}, + H4 = {ip=35158016, desc="GB,H4,Leicester"}, + H5 = {ip=1043402716, desc="GB,H5,Loughborough"}, + H6 = {ip=41732608, desc="GB,H6,Catford"}, + H7 = {ip=41863168, desc="GB,H7,Lincoln"}, + H8 = {ip=35294976, desc="GB,H8,Liverpool"}, + H9 = {ip=35196928, desc="GB,H9,London"}, + I1 = {ip=35253760, desc="GB,I1,Luton"}, + I2 = {ip=35263488, desc="GB,I2,Manchester"}, + I3 = {ip=47714304, desc="GB,I3,Rochester"}, + I4 = {ip=1298651136, desc="GB,I4,Morden"}, + I5 = {ip=1382961968, desc="GB,I5,Middlesborough"}, + I8 = {ip=1371219061, desc="GB,I8,Stepney"}, + I9 = {ip=35282944, desc="GB,I9,Norwich"}, + IA = {ip=201438272, desc="US,IA,Urbandale"}, + J1 = {ip=523578880, desc="GB,J1,Daventry"}, + J2 = {ip=788492344, desc="GB,J2,Grimsby"}, + J3 = {ip=3282790208, desc="GB,J3,Flixborough"}, + J5 = {ip=41759232, desc="GB,J5,Wallsend"}, + J6 = {ip=1043412268, desc="GB,J6,Alnwick"}, + J7 = {ip=41783296, desc="GB,J7,Harrogate"}, + J8 = {ip=35160064, desc="GB,J8,Nottingham"}, + J9 = {ip=47742976, desc="GB,J9,Newark"}, + JA = {ip=1476096512, desc="RU,JA,Kurilsk"}, + K1 = {ip=48015360, desc="GB,K1,Oldham"}, + K2 = {ip=1043402360, desc="GB,K2,Kidlington"}, + K3 = {ip=39956480, desc="GB,K3,Peterborough"}, + K4 = {ip=41735168, desc="GB,K4,Plymouth"}, + K5 = {ip=775747568, desc="GB,K5,Poole"}, + K6 = {ip=774162844, desc="GB,K6,Portsmouth"}, + K7 = {ip=41746432, desc="GB,K7,Reading"}, + K8 = {ip=35229696, desc="GB,K8,Ilford"}, + L1 = {ip=47773696, desc="GB,L1,Twickenham"}, + L2 = {ip=48103424, desc="GB,L2,Rochdale"}, + L3 = {ip=35304192, desc="GB,L3,Rotherham"}, + L4 = {ip=1043416984, desc="GB,L4,Oakham"}, + L5 = {ip=772988024, desc="GB,L5,Salford"}, + L6 = {ip=35336192, desc="GB,L6,Shrewsbury"}, + L7 = {ip=1043419464, desc="GB,L7,Oldbury"}, + L8 = {ip=39936000, desc="GB,L8,Lytham"}, + L9 = {ip=35304448, desc="GB,L9,Sheffield"}, + M1 = {ip=35384320, desc="GB,M1,Slough"}, + M2 = {ip=41470976, desc="GB,M2,Solihull"}, + M4 = {ip=35139584, desc="GB,M4,Southampton"}, + M5 = {ip=1043402176, desc="GB,M5,Southend-on-sea"}, + M6 = {ip=773986248, desc="GB,M6,Hill"}, + M8 = {ip=1443330688, desc="GB,M8,Camberwell"}, + M9 = {ip=35322880, desc="GB,M9,Stafford"}, + MB = {ip=1076550400, desc="CA,MB,Winnipeg"}, + MI = {ip=201393888, desc="US,MI,Saginaw"}, + N1 = {ip=1318741928, desc="GB,N1,Haydock"}, + N2 = {ip=35266560, desc="GB,N2,Stockport"}, + N3 = {ip=41832448, desc="GB,N3,Stockton-on-tees"}, + N4 = {ip=3231559680, desc="GB,N4,Longport"}, + N5 = {ip=1043424608, desc="GB,N5,Beccles"}, + N6 = {ip=35276800, desc="GB,N6,Sunderland"}, + N7 = {ip=41551872, desc="GB,N7,Tadworth"}, + N8 = {ip=41697280, desc="GB,N8,Sutton"}, + N9 = {ip=35252736, desc="GB,N9,Swindon"}, + NB = {ip=2211053568, desc="CA,NB,Fredericton"}, + ND = {ip=201473536, desc="US,ND,Bismarck"}, + NH = {ip=201772808, desc="US,NH,Laconia"}, + NJ = {ip=201352704, desc="US,NJ,Piscataway"}, + NS = {ip=3226164992, desc="CA,NS,Halifax"}, + NT = {ip=3332472320, desc="CA,NT,Yellowknife"}, + NV = {ip=202261184, desc="US,NV,Henderson"}, + O2 = {ip=40251392, desc="GB,O2,Telford"}, + O3 = {ip=35230208, desc="GB,O3,Grays"}, + O4 = {ip=35318784, desc="GB,O4,Torquay"}, + O5 = {ip=1368498352, desc="GB,O5,Poplar"}, + O6 = {ip=1546138112, desc="GB,O6,Stretford"}, + O7 = {ip=35219456, desc="GB,O7,Wakefield"}, + O8 = {ip=35321856, desc="GB,O8,Walsall"}, + O9 = {ip=1359108248, desc="GB,O9,Walthamstow"}, + ON = {ip=201620304, desc="CA,ON,Ottawa"}, + P1 = {ip=1043431736, desc="GB,P1,Wandsworth"}, + P2 = {ip=35260416, desc="GB,P2,Warrington"}, + P3 = {ip=41766912, desc="GB,P3,Nuneaton"}, + P4 = {ip=41893888, desc="GB,P4,Newbury"}, + P5 = {ip=772987648, desc="GB,P5,Westminster"}, + P7 = {ip=41466624, desc="GB,P7,Wigan"}, + P8 = {ip=48087808, desc="GB,P8,Salisbury"}, + P9 = {ip=41793536, desc="GB,P9,Maidenhead"}, + Q1 = {ip=41457664, desc="GB,Q1,Wallasey"}, + Q2 = {ip=1040739840, desc="GB,Q2,Wokingham"}, + Q3 = {ip=35323392, desc="GB,Q3,Wolverhampton"}, + Q4 = {ip=539624744, desc="GB,Q4,Redditch"}, + Q5 = {ip=1043415688, desc="GB,Q5,Wetherby"}, + Q6 = {ip=1043439984, desc="GB,Q6,Antrim"}, + Q7 = {ip=41811456, desc="GB,Q7,Newtownards"}, + Q8 = {ip=1347208672, desc="GB,Q8,Armagh"}, + Q9 = {ip=1044726432, desc="GB,Q9,Connor"}, + QC = {ip=2210594816, desc="CA,QC,Varennes"}, + R1 = {ip=1482707288, desc="GB,R1,Ballymoney"}, + R3 = {ip=47828992, desc="GB,R3,Belfast"}, + R4 = {ip=1051352576, desc="GB,R4,Eden"}, + R5 = {ip=1056827328, desc="GB,R5,Castlereagh"}, + R6 = {ip=47895040, desc="GB,R6,Coleraine"}, + R7 = {ip=3270400320, desc="GB,R7,Dunmore"}, + R8 = {ip=1367996672, desc="GB,R8,Portadown"}, + R9 = {ip=773985608, desc="GB,R9,Square"}, + RI = {ip=67285760, desc="US,RI,Providence"}, + S1 = {ip=1040409048, desc="GB,S1,Drummond"}, + S2 = {ip=1353842208, desc="GB,S2,Enniskillen"}, + S3 = {ip=1368133632, desc="GB,S3,Larne"}, + S4 = {ip=1446384520, desc="GB,S4,Ardmore"}, + S5 = {ip=1043419184, desc="GB,S5,Lisburn"}, + S6 = {ip=1056826304, desc="GB,S6,Londonderry"}, + S7 = {ip=1359111383, desc="GB,S7,Curran"}, + S8 = {ip=1369435392, desc="GB,S8,Waterfoot"}, + S9 = {ip=1043434592, desc="GB,S9,Newry"}, + T1 = {ip=3242033152, desc="GB,T1,Jordanstown"}, + T2 = {ip=1043402000, desc="GB,T2,Bangor"}, + T3 = {ip=1043429728, desc="GB,T3,Omagh"}, + T4 = {ip=1043429520, desc="GB,T4,Strabane"}, + T5 = {ip=39849984, desc="GB,T5,Aberdeen"}, + T6 = {ip=1043407024, desc="GB,T6,Inverurie"}, + T7 = {ip=47917056, desc="GB,T7,Forfar"}, + T8 = {ip=1051457600, desc="GB,T8,Sandbank"}, + T9 = {ip=1043429424, desc="GB,T9,Melrose"}, + TX = {ip=201673024, desc="US,TX,Mckinney"}, + U1 = {ip=1043400976, desc="GB,U1,Alloa"}, + U2 = {ip=1353815544, desc="GB,U2,Langholm"}, + U3 = {ip=1042190336, desc="GB,U3,Dundee"}, + U4 = {ip=1043428036, desc="GB,U4,Newmilns"}, + U5 = {ip=1051334704, desc="GB,U5,Bishopbriggs"}, + U6 = {ip=1040628912, desc="GB,U6,Musselburgh"}, + U7 = {ip=1056881248, desc="GB,U7,Barrhead"}, + U8 = {ip=35188736, desc="GB,U8,Edinburgh"}, + U9 = {ip=1318744616, desc="GB,U9,Blackstone"}, + V1 = {ip=47947776, desc="GB,V1,Kirkcaldy"}, + V2 = {ip=35190784, desc="GB,V2,Glasgow"}, + V4 = {ip=1043417560, desc="GB,V4,Greenock"}, + V5 = {ip=3570359128, desc="GB,V5,Borthwick"}, + V6 = {ip=1398983520, desc="GB,V6,Findhorn"}, + V7 = {ip=1043452928, desc="GB,V7,Saltcoats"}, + V8 = {ip=523564544, desc="GB,V8,Bothwell"}, + V9 = {ip=1353706504, desc="GB,V9,Redland"}, + VT = {ip=201355264, desc="US,VT,Brattleboro"}, + W1 = {ip=1042195200, desc="GB,W1,Perth"}, + W2 = {ip=1043412560, desc="GB,W2,Paisley"}, + W4 = {ip=1056825616, desc="GB,W4,Dundonald"}, + W5 = {ip=1040411544, desc="GB,W5,Douglas"}, + W6 = {ip=41547776, desc="GB,W6,Stirling"}, + W7 = {ip=1443523584, desc="GB,W7,Bearsden"}, + W8 = {ip=534572928, desc="GB,W8,Cross"}, + W9 = {ip=1042221056, desc="GB,W9,Livingston"}, + WA = {ip=201806720, desc="US,WA,Issaquah"}, + WY = {ip=135495936, desc="US,WY,Casper"}, + X1 = {ip=1043425760, desc="GB,X1,Valley"}, + X2 = {ip=773988152, desc="GB,X2,Victoria"}, + X3 = {ip=35149824, desc="GB,X3,Bridgend"}, + X4 = {ip=1043402272, desc="GB,X4,Blackwood"}, + X5 = {ip=39946240, desc="GB,X5,Cardiff"}, + X6 = {ip=1043435700, desc="GB,X6,Aberystwyth"}, + X7 = {ip=1043408760, desc="GB,X7,Llanelli"}, + X8 = {ip=1368926208, desc="GB,X8,Abergele"}, + X9 = {ip=1043411032, desc="GB,X9,Rhyl"}, + Y1 = {ip=1043407256, desc="GB,Y1,Holywell"}, + Y2 = {ip=1043401576, desc="GB,Y2,Caernarfon"}, + Y4 = {ip=1043428692, desc="GB,Y4,Cwmbran"}, + Y5 = {ip=3265794544, desc="GB,Y5,Cwmafan"}, + Y6 = {ip=35153920, desc="GB,Y6,Newport"}, + Y7 = {ip=1353763984, desc="GB,Y7,Haverfordwest"}, + Y8 = {ip=1043430344, desc="GB,Y8,Welshpool"}, + Z1 = {ip=40116224, desc="GB,Z1,Swansea"}, + Z2 = {ip=40189952, desc="GB,Z2,Pontypool"}, + Z3 = {ip=35147776, desc="GB,Z3,Barry"}, + Z4 = {ip=40321024, desc="GB,Z4,Wrexham"} } local get_addresses = function(address, mask, domain, nameserver) - -- translate the IP's in the areaIPs to strings, as this is what the - -- DNS library expects - if ( "number" == type(address) ) then - address = ipOps.fromdword(address) - local a, b, c, d = address:match("(%d+)%.(%d+)%.(%d+)%.(%d+)") - address = ("%d.%d.%d.%d"):format(d,c,b,a) - end + -- translate the IP's in the areaIPs to strings, as this is what the + -- DNS library expects + if ( "number" == type(address) ) then + address = ipOps.fromdword(address) + local a, b, c, d = address:match("(%d+)%.(%d+)%.(%d+)%.(%d+)") + address = ("%d.%d.%d.%d"):format(d,c,b,a) + end - local subnet = { family = nmap.address_family(), address = address, mask = mask } - local status, resp = dns.query(domain, {host = nameserver, retAll=true, subnet=subnet}) - if ( not(status) ) then - return - end - if ( "table" ~= type(resp) ) then resp = { resp } end - return resp + local subnet = { family = nmap.address_family(), address = address, mask = mask } + local status, resp = dns.query(domain, {host = nameserver, retAll=true, subnet=subnet}) + if ( not(status) ) then + return + end + if ( "table" ~= type(resp) ) then resp = { resp } end + return resp end local function fail(err) return ("\n ERROR: %s"):format(err or "") end action = function(host, port) - if ( not(argDomain) ) then - return fail(SCRIPT_NAME .. ".domain was not specified") - end + if ( not(argDomain) ) then + return fail(SCRIPT_NAME .. ".domain was not specified") + end - local nameserver = argNS or (host and host.ip) - -- as the nameserver argument overrides the host.ip, the prerule should - -- already have done our work, so abort - if ( argNS and host ) then - return - -- if we have no nameserver argument and no host, we dont have sufficient - -- information to continue, abort - elseif ( not(argNS) and not(host) ) then - return - end + local nameserver = argNS or (host and host.ip) + -- as the nameserver argument overrides the host.ip, the prerule should + -- already have done our work, so abort + if ( argNS and host ) then + return + -- if we have no nameserver argument and no host, we dont have sufficient + -- information to continue, abort + elseif ( not(argNS) and not(host) ) then + return + end - local addrs = argAddr or areaIPs - if ( "string" == type(addrs) ) then addrs = {{ ip = addrs }} end + local addrs = argAddr or areaIPs + if ( "string" == type(addrs) ) then addrs = {{ ip = addrs }} end - local lookup, result = {}, { name = argDomain } - for _,ip in pairs(addrs) do - for _, addr in ipairs( get_addresses (ip.ip, argMask, argDomain, nameserver) ) do - lookup[addr] = true - end - end - for addr in pairs(lookup) do table.insert(result, addr) end - table.sort(result) - return stdnse.format_output(true, result) + local lookup, result = {}, { name = argDomain } + for _,ip in pairs(addrs) do + for _, addr in ipairs( get_addresses (ip.ip, argMask, argDomain, nameserver) ) do + lookup[addr] = true + end + end + for addr in pairs(lookup) do table.insert(result, addr) end + table.sort(result) + return stdnse.format_output(true, result) end diff --git a/scripts/dns-nsec-enum.nse b/scripts/dns-nsec-enum.nse index ade2c1fd8..60d9c6e46 100644 --- a/scripts/dns-nsec-enum.nse +++ b/scripts/dns-nsec-enum.nse @@ -52,63 +52,63 @@ categories = {"discovery", "intrusive"} portrule = shortport.port_or_service(53, "domain", {"tcp", "udp"}) local function remove_empty(t) - local result = {} + local result = {} - for _, v in ipairs(t) do - if v ~= "" then - result[#result + 1] = v - end - end + for _, v in ipairs(t) do + if v ~= "" then + result[#result + 1] = v + end + end - return result + return result end local function split(domain) - return stdnse.strsplit("%.", domain) + return stdnse.strsplit("%.", domain) end local function join(components) - return stdnse.strjoin(".", remove_empty(components)) + return stdnse.strjoin(".", remove_empty(components)) end -- Remove the first component of a domain name. Return nil if the number of -- components drops below min_length (default 0). local function remove_component(domain, min_length) - local components + local components - min_length = min_length or 0 - components = split(domain) - if #components <= min_length then - return nil - end - table.remove(components, 1) + min_length = min_length or 0 + components = split(domain) + if #components <= min_length then + return nil + end + table.remove(components, 1) - return join(components) + return join(components) end -- 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 -- components. local function guess_domain(host) - local name - local components + local name + local components - name = stdnse.get_hostname(host) - if name and name ~= host.ip then - return remove_component(name, 2) or name - else - return nil - end + name = stdnse.get_hostname(host) + if name and name ~= host.ip then + return remove_component(name, 2) or name + else + return nil + end end local function invert(t) - local result = {} + local result = {} - for k, v in pairs(t) do - result[v] = k - end + for k, v in pairs(t) do + result[v] = k + end - return result + return result end -- 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 -- lexicographically last. local function increment_component(name) - local i, bytes, indexes + local i, bytes, indexes - -- Easy cases first. - if #name == 0 then - return "0" - elseif #name < 63 then - return name .. "-" - elseif #name > 64 then - -- Shouldn't happen. - return nil - end + -- Easy cases first. + if #name == 0 then + return "0" + elseif #name < 63 then + return name .. "-" + elseif #name > 64 then + -- Shouldn't happen. + return nil + end - -- Convert the string into an array of indexes into DNS_CHARS. - indexes = {} - for i, b in ipairs({ string.byte(name, 1, -1) }) do - indexes[i] = DNS_CHARS_INV[b] - end - -- Increment. - i = #name - while i >= 1 do - repeat - indexes[i] = indexes[i] + 1 - -- No "-" in first position. - until not (i == 1 and string.char(DNS_CHARS[indexes[i]]) == "-") - if indexes[i] > #DNS_CHARS then - -- Wrap around, next digit. - indexes[i] = 1 - else - break - end - i = i - 1 - end - -- Overflow. - if i == 0 then - return nil - end - -- Convert array of indexes back into string. - bytes = {} - for i, index in ipairs(indexes) do - bytes[i] = DNS_CHARS[index] - end + -- Convert the string into an array of indexes into DNS_CHARS. + indexes = {} + for i, b in ipairs({ string.byte(name, 1, -1) }) do + indexes[i] = DNS_CHARS_INV[b] + end + -- Increment. + i = #name + while i >= 1 do + repeat + indexes[i] = indexes[i] + 1 + -- No "-" in first position. + until not (i == 1 and string.char(DNS_CHARS[indexes[i]]) == "-") + if indexes[i] > #DNS_CHARS then + -- Wrap around, next digit. + indexes[i] = 1 + else + break + end + i = i - 1 + end + -- Overflow. + if i == 0 then + return nil + end + -- Convert array of indexes back into string. + bytes = {} + for i, index in ipairs(indexes) do + bytes[i] = DNS_CHARS[index] + end - return string.char(table.unpack(bytes)) + return string.char(table.unpack(bytes)) end -- 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 -- subzone and on to more names. local function bump_domain(domain) - local components + local components - components = split(domain) - while #components > 0 do - components[1] = increment_component(components[1]) - if components[1] then - break - else - table.remove(components[1]) - end - end + components = split(domain) + while #components > 0 do + components[1] = increment_component(components[1]) + if components[1] then + break + else + table.remove(components[1]) + end + end - if #components == 0 then - return nil - else - return join(components) - end + if #components == 0 then + return nil + else + return join(components) + end end -- Return the lexicographically next domain name. This adds a new subdomain -- consisting of the smallest character. This function never returns a domain -- outside the current subzone. local function next_domain(domain) - if #domain == 0 then - return "0" - else - return "0" .. "." .. domain - end + if #domain == 0 then + return "0" + else + return "0" .. "." .. domain + end end -- Cut out a portion of an array and return it as a new array, setting the -- elements in the original array to nil. local function excise(t, i, j) - local result + local result - result = {} - if j < 0 then - j = #t + j + 1 - end - for i = i, j do - result[#result + 1] = t[i] - t[i] = nil - end + result = {} + if j < 0 then + j = #t + j + 1 + end + for i = i, j do + result[#result + 1] = t[i] + t[i] = nil + end - return result + return result end -- Remove a suffix from a domain (to isolate a subdomain from its parent). local function remove_suffix(domain, suffix) - local dc, sc + local dc, sc - dc = split(domain) - sc = split(suffix) - while #dc > 0 and #sc > 0 and dc[#dc] == sc[#sc] do - dc[#dc] = nil - sc[#sc] = nil - end + dc = split(domain) + sc = split(suffix) + while #dc > 0 and #sc > 0 and dc[#dc] == sc[#sc] do + dc[#dc] = nil + sc[#sc] = nil + end - return join(dc), join(sc) + return join(dc), join(sc) end -- Return the subset of authoritative records with the given label. local function auth_filter(retPkt, label) - local result = {} + local result = {} - for _, rec in ipairs(retPkt.auth) do - if rec[label] then - result[#result + 1] = rec[label] - end - end + for _, rec in ipairs(retPkt.auth) do + if rec[label] then + result[#result + 1] = rec[label] + end + end - return result + return result end -- "Less than" function for two domain names. Compares starting with the last -- component. local function domain_lt(a, b) - local a_parts, b_parts + local a_parts, b_parts - a_parts = split(a) - b_parts = split(b) - while #a_parts > 0 and #b_parts > 0 do - if a_parts[#a_parts] < b_parts[#b_parts] then - return true - elseif a_parts[#a_parts] > b_parts[#b_parts] then - return false - end - a_parts[#a_parts] = nil - b_parts[#b_parts] = nil - end + a_parts = split(a) + b_parts = split(b) + while #a_parts > 0 and #b_parts > 0 do + if a_parts[#a_parts] < b_parts[#b_parts] then + return true + elseif a_parts[#a_parts] > b_parts[#b_parts] then + return false + end + a_parts[#a_parts] = nil + b_parts[#b_parts] = nil + end - return #a_parts < #b_parts + return #a_parts < #b_parts end -- Find the NSEC record that brackets the given domain. local function get_next_nsec(retPkt, domain) - for _, nsec in ipairs(auth_filter(retPkt, "NSEC")) do - -- 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 - return nsec - end - if domain_lt(nsec.dname, domain) and domain_lt(domain, nsec.next_dname) then - return nsec - end - end + for _, nsec in ipairs(auth_filter(retPkt, "NSEC")) do + -- 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 + return nsec + end + if domain_lt(nsec.dname, domain) and domain_lt(domain, nsec.next_dname) then + return nsec + end + end end local function empty(t) - return not next(t) + return not next(t) end -- Enumerate a single domain. local function enum(host, port, domain) - local all_results = {} - local seen = {} - local subdomain = next_domain("") + local all_results = {} + local seen = {} + local subdomain = next_domain("") - while subdomain do - local result = {} - local status, result, nsec - 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}) - nsec = status and get_next_nsec(result, join({subdomain, domain})) or nil - if nsec then - local first, last, remainder - local index + while subdomain do + local result = {} + local status, result, nsec + 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}) + nsec = status and get_next_nsec(result, join({subdomain, domain})) or nil + if nsec then + local first, last, remainder + local index - first, remainder = remove_suffix(nsec.dname, domain) - if #remainder > 0 then - stdnse.print_debug("Result name %q doesn't end in %q.", nsec.dname, domain) - subdomain = nil - break - end - last, remainder = remove_suffix(nsec.next_dname, domain) - if #remainder > 0 then - stdnse.print_debug("Result name %q doesn't end in %q.", nsec.next_dname, domain) - subdomain = nil - break - end - if #last == 0 then - stdnse.print_debug("Wrapped") - subdomain = nil - break - end + first, remainder = remove_suffix(nsec.dname, domain) + if #remainder > 0 then + stdnse.print_debug("Result name %q doesn't end in %q.", nsec.dname, domain) + subdomain = nil + break + end + last, remainder = remove_suffix(nsec.next_dname, domain) + if #remainder > 0 then + stdnse.print_debug("Result name %q doesn't end in %q.", nsec.next_dname, domain) + subdomain = nil + break + end + if #last == 0 then + stdnse.print_debug("Wrapped") + subdomain = nil + break + end - if not seen[first] then - table.insert(all_results, join({first, domain})) - seen[first] = #all_results - end - index = seen[last] - if index then - -- Ignore if first is the original domain. - if #first > 0 then - subdomain = bump_domain(last) - -- Replace a chunk of the output with a sub-table for the zone. - all_results[index] = excise(all_results, index, -1) - end - else - stdnse.print_debug("adding %s", last) - subdomain = next_domain(last) - table.insert(all_results, join({last, domain})) - seen[last] = #all_results - end - else - local parent = remove_component(subdomain, 1) + if not seen[first] then + table.insert(all_results, join({first, domain})) + seen[first] = #all_results + end + index = seen[last] + if index then + -- Ignore if first is the original domain. + if #first > 0 then + subdomain = bump_domain(last) + -- Replace a chunk of the output with a sub-table for the zone. + all_results[index] = excise(all_results, index, -1) + end + else + stdnse.print_debug("adding %s", last) + subdomain = next_domain(last) + table.insert(all_results, join({last, domain})) + seen[last] = #all_results + end + else + local parent = remove_component(subdomain, 1) - -- This branch is entered if name resolution failed or - -- there were no NSEC records. If at the top, quit. - -- Otherwise continue to the next subdomain. - if parent then - subdomain = bump_domain(parent) - else - return nil - end - end - end + -- This branch is entered if name resolution failed or + -- there were no NSEC records. If at the top, quit. + -- Otherwise continue to the next subdomain. + if parent then + subdomain = bump_domain(parent) + else + return nil + end + end + end - return all_results + return all_results end action = function(host, port) - local output = {} - local domains + local output = {} + local domains - domains = stdnse.get_script_args('dns-nsec-enum.domains') - if not domains then - domains = guess_domain(host) - end - if not domains then - return string.format("Can't determine domain for host %s; use %s.domains script arg.", host.ip, SCRIPT_NAME) - end - if type(domains) == 'string' then - domains = { domains } - end + domains = stdnse.get_script_args('dns-nsec-enum.domains') + if not domains then + domains = guess_domain(host) + end + if not domains then + return string.format("Can't determine domain for host %s; use %s.domains script arg.", host.ip, SCRIPT_NAME) + end + if type(domains) == 'string' then + domains = { domains } + end - for _, domain in ipairs(domains) do - local result = enum(host, port, domain) - if type(result) == "table" then - result["name"] = domain - output[#output + 1] = result - else - output[#output + 1] = "No NSEC records found" - end - end + for _, domain in ipairs(domains) do + local result = enum(host, port, domain) + if type(result) == "table" then + result["name"] = domain + output[#output + 1] = result + else + output[#output + 1] = "No NSEC records found" + end + end - return stdnse.format_output(true, output) + return stdnse.format_output(true, output) end diff --git a/scripts/dns-nsec3-enum.nse b/scripts/dns-nsec3-enum.nse index 07b4e1260..c4b268519 100644 --- a/scripts/dns-nsec3-enum.nse +++ b/scripts/dns-nsec3-enum.nse @@ -82,345 +82,345 @@ all_results = {} -- get time (in miliseconds) when the script should finish local function get_end_time() - local t = nmap.timing_level() - local limit = stdnse.parse_timespec(stdnse.get_script_args('dns-nsec3-enum.timelimit') or "30m") - local end_time = 1000 * limit + nmap.clock_ms() - return end_time + local t = nmap.timing_level() + local limit = stdnse.parse_timespec(stdnse.get_script_args('dns-nsec3-enum.timelimit') or "30m") + local end_time = 1000 * limit + nmap.clock_ms() + return end_time end local function remove_empty(t) - local result = {} - for _, v in ipairs(t) do - if v ~= "" then - result[#result + 1] = v - end - end - return result + local result = {} + for _, v in ipairs(t) do + if v ~= "" then + result[#result + 1] = v + end + end + return result end local function split(domain) - return stdnse.strsplit("%.", domain) + return stdnse.strsplit("%.", domain) end local function join(components) - return stdnse.strjoin(".", remove_empty(components)) + return stdnse.strjoin(".", remove_empty(components)) end -- Remove the first component of a domain name. Return nil if the number of -- components drops below min_length (default 0). local function remove_component(domain, min_length) - local components + local components - min_length = min_length or 0 - components = split(domain) - if #components < min_length then - return nil - end - table.remove(components, 1) + min_length = min_length or 0 + components = split(domain) + if #components < min_length then + return nil + end + table.remove(components, 1) - return join(components) + return join(components) end -- 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 -- components. local function guess_domain(host) - local name - local components + local name + local components - name = stdnse.get_hostname(host) - if name and name ~= host.ip then - return remove_component(name, 2) or name - else - return nil - end + name = stdnse.get_hostname(host) + if name and name ~= host.ip then + return remove_component(name, 2) or name + else + return nil + end end -- Remove a suffix from a domain (to isolate a subdomain from its parent). local function remove_suffix(domain, suffix) - local dc, sc + local dc, sc - dc = split(domain) - sc = split(suffix) - while #dc > 0 and #sc > 0 and dc[#dc] == sc[#sc] do - dc[#dc] = nil - sc[#sc] = nil - end + dc = split(domain) + sc = split(suffix) + while #dc > 0 and #sc > 0 and dc[#dc] == sc[#sc] do + dc[#dc] = nil + sc[#sc] = nil + end - return join(dc), join(sc) + return join(dc), join(sc) end -- Return the subset of authoritative records with the given label. local function auth_filter(retPkt, label) - local result = {} + local result = {} - for _, rec in ipairs(retPkt.auth) do - if rec[label] then - result[#result + 1] = rec[label] - end - end + for _, rec in ipairs(retPkt.auth) do + if rec[label] then + result[#result + 1] = rec[label] + end + end - return result + return result end local function empty(t) - return not next(t) + return not next(t) end local function random_string() - return msrpc.random_crap(8,"etaoinshrdlucmfw") + return msrpc.random_crap(8,"etaoinshrdlucmfw") end -- generate a random hash with domains suffix -- return both domain and it's hash local function generate_hash(domain, iter, salt) - local rand_str = random_string() - local random_domain = rand_str .. "." .. domain - local packed_domain = "" - for word in string.gmatch(domain,"[^%.]+") do - packed_domain = packed_domain .. bin.pack("c",string.len(word)) .. word - end - 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 - local hash = openssl.sha1(to_hash) - for i=0,iter do - hash = hash .. bin.pack("H",salt) - hash = openssl.sha1(hash) - end - return string.lower(base32.enc(hash,true)), random_domain + local rand_str = random_string() + local random_domain = rand_str .. "." .. domain + local packed_domain = "" + for word in string.gmatch(domain,"[^%.]+") do + packed_domain = packed_domain .. bin.pack("c",string.len(word)) .. word + end + 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 + local hash = openssl.sha1(to_hash) + for i=0,iter do + hash = hash .. bin.pack("H",salt) + hash = openssl.sha1(hash) + end + return string.lower(base32.enc(hash,true)), random_domain end -- convenience function , returns size of a table local function table_size(tbl) - local numItems = 0 - for k,v in pairs(tbl) do - numItems = numItems + 1 - end - return numItems + local numItems = 0 + for k,v in pairs(tbl) do + numItems = numItems + 1 + end + return numItems end -- convenience function , return first item in a table local function get_first(tbl) - for k,v in pairs(tbl) do - return k,v - end + for k,v in pairs(tbl) do + return k,v + end end -- queries the domain and parses the results -- returns the list of new ranges local function query_for_hashes(host,subdomain,domain) - local status - local result - local ranges = {} - status, result = dns.query(subdomain, {host = host.ip, dtype='NSEC3', retAll=true, retPkt=true, dnssec=true}) - if status then - for _, nsec3 in ipairs(auth_filter(result, "NSEC3")) do - local h1 = string.lower(remove_suffix(nsec3.dname,domain)) - local h2 = string.lower(nsec3.hash.base32) - if not stdnse.contains(all_results,"nexthash " .. h1 .. " " .. h2) then - table.insert(all_results, "nexthash " .. h1 .. " " .. h2) - stdnse.print_debug("nexthash " .. h1 .. " " .. h2) - end - ranges[h1] = h2 - end - else - stdnse.print_debug(1, "DNS error: %s", result) - end - return ranges + local status + local result + local ranges = {} + status, result = dns.query(subdomain, {host = host.ip, dtype='NSEC3', retAll=true, retPkt=true, dnssec=true}) + if status then + for _, nsec3 in ipairs(auth_filter(result, "NSEC3")) do + local h1 = string.lower(remove_suffix(nsec3.dname,domain)) + local h2 = string.lower(nsec3.hash.base32) + if not stdnse.contains(all_results,"nexthash " .. h1 .. " " .. h2) then + table.insert(all_results, "nexthash " .. h1 .. " " .. h2) + stdnse.print_debug("nexthash " .. h1 .. " " .. h2) + end + ranges[h1] = h2 + end + else + stdnse.print_debug(1, "DNS error: %s", result) + end + return ranges end -- does the actuall enumeration local function enum(host, port, domain) - local seen, seen_subdomain = {}, {} - local ALG ={} - ALG[1] = "SHA-1" - local todo = {} - local dnssec, status, result = false, false, "No Answer" - local result = {} - local subdomain = msrpc.random_crap(8,"etaoinshrdlucmfw") - local full_domain = join({subdomain, domain}) - local iter - local salt - local end_time = get_end_time() + local seen, seen_subdomain = {}, {} + local ALG ={} + ALG[1] = "SHA-1" + local todo = {} + local dnssec, status, result = false, false, "No Answer" + local result = {} + local subdomain = msrpc.random_crap(8,"etaoinshrdlucmfw") + local full_domain = join({subdomain, domain}) + local iter + local salt + local end_time = get_end_time() - -- 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}) - if status then - local is_nsec3 = false - for _, nsec3 in ipairs(auth_filter(result, "NSEC3")) do -- parse the results and add initial ranges - is_nsec3 = true - dnssec = true - iter = nsec3.iterations - salt = nsec3.salt.hex - local h1 = string.lower(remove_suffix(nsec3.dname,domain)) - local h2 = string.lower(nsec3.hash.base32) - if table_size(todo) == 0 then - table.insert(all_results, "domain " .. domain) - stdnse.print_debug("domain " .. domain) - table.insert(all_results, "salt " .. salt) - stdnse.print_debug("salt " .. salt) - table.insert(all_results, "iterations " .. iter) - stdnse.print_debug("iterations " .. iter) - if h1 < h2 then - todo[h2] = h1 - else - todo[h1] = h2 - end - else - for b,a in pairs(todo) do - if h1 == b and h2 == a then -- h2:a b:h1 case - todo[b] = nil - break - end - if h1 == b and h2 > h1 then -- a b:h1 h2 case - todo[b] = nil - todo[h2] = a - break - end - if h1 == b and h2 < a then -- h2 a b:h1 - todo[b] = nil - todo[b] = h2 - break - end - if h1 > b then -- a b h1 h2 - todo[b] = nil - todo[b] = h1 - todo[h2] = a - break - end - if h1 < a then -- h1 h2 a b - todo[b] = nil - todo[b] = h1 - todo[h2] = a - break - end - end -- for - end -- else - table.insert(all_results, "nexthash " .. h1 .. " " .. h2) - stdnse.print_debug("nexthash " .. h1 .. " " .. h2) - end - end + -- 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}) + if status then + local is_nsec3 = false + for _, nsec3 in ipairs(auth_filter(result, "NSEC3")) do -- parse the results and add initial ranges + is_nsec3 = true + dnssec = true + iter = nsec3.iterations + salt = nsec3.salt.hex + local h1 = string.lower(remove_suffix(nsec3.dname,domain)) + local h2 = string.lower(nsec3.hash.base32) + if table_size(todo) == 0 then + table.insert(all_results, "domain " .. domain) + stdnse.print_debug("domain " .. domain) + table.insert(all_results, "salt " .. salt) + stdnse.print_debug("salt " .. salt) + table.insert(all_results, "iterations " .. iter) + stdnse.print_debug("iterations " .. iter) + if h1 < h2 then + todo[h2] = h1 + else + todo[h1] = h2 + end + else + for b,a in pairs(todo) do + if h1 == b and h2 == a then -- h2:a b:h1 case + todo[b] = nil + break + end + if h1 == b and h2 > h1 then -- a b:h1 h2 case + todo[b] = nil + todo[h2] = a + break + end + if h1 == b and h2 < a then -- h2 a b:h1 + todo[b] = nil + todo[b] = h2 + break + end + if h1 > b then -- a b h1 h2 + todo[b] = nil + todo[b] = h1 + todo[h2] = a + break + end + if h1 < a then -- h1 h2 a b + todo[b] = nil + todo[b] = h1 + todo[h2] = a + break + end + end -- for + end -- else + table.insert(all_results, "nexthash " .. h1 .. " " .. h2) + stdnse.print_debug("nexthash " .. h1 .. " " .. h2) + end + end - -- 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 - local hash - hash, subdomain = generate_hash(domain,iter,salt) - local queried = false - for a,b in pairs(todo) do - if a == b then - todo[a] = nil - break - end - if a < b then -- [] range - if hash > a and hash < b then - -- do the query - local hash_pairs = query_for_hashes(host,subdomain,domain) - queried = true - local changed = false - for h1,h2 in pairs(hash_pairs) do - if h1 == a and h2 == b then -- h1:a h2:b case - todo[a] = nil - changed = true - end - if h1 == a then -- h1:a h2 b case - todo[a] = nil - todo[h2] = b - changed = true - end - if h2 == b then -- a h1 bh:2 case - todo[a] = nil - todo[a] = h1 - changed = true - end - if h1 > a and h2 < b then -- a h1 h2 b case - todo[a] = nil - todo[a] = h1 - todo[h2] = b - changed = true - end - end --- if changed then --- stdnse.print_debug("break[]") - --break --- end - end - elseif a > b then -- ][ range - if hash > a or hash < b then - local hash_pairs = query_for_hashes(host,subdomain,domain) - queried = true - local changed = false - for h1,h2 in pairs(hash_pairs) do - if h1 == a and h2 == b then -- h2:b a:h1 case - todo[a] = nil - changed = true - end - if h1 == a and h2 > h1 then -- b a:h1 h2 case - todo[a] = nil - todo[h1] = b - changed = true - end - if h1 == a and h2 < b then -- h2 b a:h1 case - todo[a] = nil - todo[h2] = b - changed = true - end - if h1 > a and h2 > h1 then -- b a h1 h2 case - todo[a] = nil - todo[a] = h1 - todo[h2] = b - changed = true - end - if h1 > a and h2 < b then -- h2 b a h1 case - todo[a] = nil - todo[a] = h1 - todo[h2] = b - changed = true - end - if h1 < b then -- h1 h2 b a case - todo[a] = nil - todo[a] = h1 - todo[h2] = b - changed = true - end - end - if changed then - --break - end - end - end - if queried then - break - end - end - end - return dnssec, status, all_results + -- 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 + local hash + hash, subdomain = generate_hash(domain,iter,salt) + local queried = false + for a,b in pairs(todo) do + if a == b then + todo[a] = nil + break + end + if a < b then -- [] range + if hash > a and hash < b then + -- do the query + local hash_pairs = query_for_hashes(host,subdomain,domain) + queried = true + local changed = false + for h1,h2 in pairs(hash_pairs) do + if h1 == a and h2 == b then -- h1:a h2:b case + todo[a] = nil + changed = true + end + if h1 == a then -- h1:a h2 b case + todo[a] = nil + todo[h2] = b + changed = true + end + if h2 == b then -- a h1 bh:2 case + todo[a] = nil + todo[a] = h1 + changed = true + end + if h1 > a and h2 < b then -- a h1 h2 b case + todo[a] = nil + todo[a] = h1 + todo[h2] = b + changed = true + end + end + --if changed then + -- stdnse.print_debug("break[]") + --break + -- end + end + elseif a > b then -- ][ range + if hash > a or hash < b then + local hash_pairs = query_for_hashes(host,subdomain,domain) + queried = true + local changed = false + for h1,h2 in pairs(hash_pairs) do + if h1 == a and h2 == b then -- h2:b a:h1 case + todo[a] = nil + changed = true + end + if h1 == a and h2 > h1 then -- b a:h1 h2 case + todo[a] = nil + todo[h1] = b + changed = true + end + if h1 == a and h2 < b then -- h2 b a:h1 case + todo[a] = nil + todo[h2] = b + changed = true + end + if h1 > a and h2 > h1 then -- b a h1 h2 case + todo[a] = nil + todo[a] = h1 + todo[h2] = b + changed = true + end + if h1 > a and h2 < b then -- h2 b a h1 case + todo[a] = nil + todo[a] = h1 + todo[h2] = b + changed = true + end + if h1 < b then -- h1 h2 b a case + todo[a] = nil + todo[a] = h1 + todo[h2] = b + changed = true + end + end + if changed then + --break + end + end + end + if queried then + break + end + end + end + return dnssec, status, all_results end action = function(host, port) - local output = {} - local domains - domains = stdnse.get_script_args('dns-nsec3-enum.domains') - if not domains then - domains = guess_domain(host) - end - if not domains then - return string.format("Can't determine domain for host %s; use %s.domains script arg.", host.ip, SCRIPT_NAME) - end - if type(domains) == 'string' then - domains = { domains } - end + local output = {} + local domains + domains = stdnse.get_script_args('dns-nsec3-enum.domains') + if not domains then + domains = guess_domain(host) + end + if not domains then + return string.format("Can't determine domain for host %s; use %s.domains script arg.", host.ip, SCRIPT_NAME) + end + if type(domains) == 'string' then + domains = { domains } + end - for _, domain in ipairs(domains) do - local dnssec, status, result = enum(host, port, domain) - if dnssec and type(result) == "table" then - output[#output + 1] = result - output[#output + 1] = "Total hashes found: " .. #result + for _, domain in ipairs(domains) do + local dnssec, status, result = enum(host, port, domain) + if dnssec and type(result) == "table" then + output[#output + 1] = result + output[#output + 1] = "Total hashes found: " .. #result - else - output[#output + 1] = "DNSSEC NSEC3 not supported" - end - end - return stdnse.format_output(true, output) + else + output[#output + 1] = "DNSSEC NSEC3 not supported" + end + end + return stdnse.format_output(true, output) end diff --git a/scripts/dns-zone-transfer.nse b/scripts/dns-zone-transfer.nse index b42e15983..a35ee0dee 100644 --- a/scripts/dns-zone-transfer.nse +++ b/scripts/dns-zone-transfer.nse @@ -102,15 +102,15 @@ prerule = function() if not dns_opts.domain then stdnse.print_debug(3, - "Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.", - SCRIPT_NAME, SCRIPT_TYPE) + "Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.", + SCRIPT_NAME, SCRIPT_TYPE) return false end if not dns_opts.server then stdnse.print_debug(3, - "Skipping '%s' %s, 'dnszonetransfer.server' argument is missing.", - SCRIPT_NAME, SCRIPT_TYPE) + "Skipping '%s' %s, 'dnszonetransfer.server' argument is missing.", + SCRIPT_NAME, SCRIPT_TYPE) return false end @@ -132,8 +132,8 @@ portrule = function(host, port) else -- can't do anything without a hostname stdnse.print_debug(3, - "Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.", - SCRIPT_NAME, SCRIPT_TYPE) + "Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.", + SCRIPT_NAME, SCRIPT_TYPE) return false end end @@ -149,39 +149,39 @@ end --@class table --@name typetab local typetab = { 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', - 'NULL', 'WKS', 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT', 'RP', 'AFSDB', 'X25', - 'ISDN', 'RT', 'NSAP', 'NSAP-PTR', 'SIG', 'KEY', 'PX', 'GPOS', 'AAAA', 'LOC', - 'NXT', 'EID', 'NIMLOC', 'SRV', 'ATMA', 'NAPTR', 'KX', 'CERT', 'A6', 'DNAME', - 'SINK', 'OPT', 'APL', 'DS', 'SSHFP', 'IPSECKEY', 'RRSIG', 'NSEC', 'DNSKEY', - 'DHCID', 'NSEC3', 'NSEC3PARAM', 'TLSA', [55]='HIP', [56]='NINFO', [57]='RKEY', - [58]='TALINK', [59]='CDS', [99]='SPF', [100]='UINFO', [101]='UID', [102]='GID', - [103]='UNSPEC', [249]='TKEY', [250]='TSIG', [251]='IXFR', [252]='AXFR', - [253]='MAILB', [254]='MAILA', [255]='ANY', [256]='ZXFR', [257]='CAA', - [32768]='TA', [32769]='DLV', + 'NULL', 'WKS', 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT', 'RP', 'AFSDB', 'X25', + 'ISDN', 'RT', 'NSAP', 'NSAP-PTR', 'SIG', 'KEY', 'PX', 'GPOS', 'AAAA', 'LOC', + 'NXT', 'EID', 'NIMLOC', 'SRV', 'ATMA', 'NAPTR', 'KX', 'CERT', 'A6', 'DNAME', + 'SINK', 'OPT', 'APL', 'DS', 'SSHFP', 'IPSECKEY', 'RRSIG', 'NSEC', 'DNSKEY', + 'DHCID', 'NSEC3', 'NSEC3PARAM', 'TLSA', [55]='HIP', [56]='NINFO', [57]='RKEY', + [58]='TALINK', [59]='CDS', [99]='SPF', [100]='UINFO', [101]='UID', [102]='GID', + [103]='UNSPEC', [249]='TKEY', [250]='TSIG', [251]='IXFR', [252]='AXFR', + [253]='MAILB', [254]='MAILA', [255]='ANY', [256]='ZXFR', [257]='CAA', + [32768]='TA', [32769]='DLV', } --- Whitelist of TLDs. Only way to reliably determine the root of a domain --@class table --@name tld local tld = { - 'aero', 'asia', 'biz', 'cat', 'com', 'coop', 'info', 'jobs', 'mobi', 'museum', - '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', - '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', - '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', - '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', - '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', - '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', - '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', - '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', - 'vn','vu','wf','ws','ye','yt','yu','za','zm','zw' + 'aero', 'asia', 'biz', 'cat', 'com', 'coop', 'info', 'jobs', 'mobi', 'museum', + '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', + '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', + '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', + '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', + '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', + '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', + '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', + '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', + 'vn','vu','wf','ws','ye','yt','yu','za','zm','zw' } --- Convert two bytes into a 16bit number. @@ -189,61 +189,61 @@ local tld = { --@param idx Index in the string (first of two consecutive bytes). --@return 16 bit number represented by the two bytes. function bto16(data, idx) - local b1 = string.byte(data, idx) - local b2 = string.byte(data, idx+1) - -- (b2 & 0xff) | ((b1 & 0xff) << 8) - return bit.bor(bit.band(b2, 255), bit.lshift(bit.band(b1, 255), 8)) + local b1 = string.byte(data, idx) + local b2 = string.byte(data, idx+1) + -- (b2 & 0xff) | ((b1 & 0xff) << 8) + return bit.bor(bit.band(b2, 255), bit.lshift(bit.band(b1, 255), 8)) end --- Check if domain name element is a tld --@param elm Domain name element to check. --@return boolean function valid_tld(elm) - for i,v in ipairs(tld) do - if elm == v then return true end - end - return false + for i,v in ipairs(tld) do + if elm == v then return true end + end + return false end --- Parse an RFC 1035 domain name. --@param data String of data. --@param offset Offset in the string to read the domain name. function parse_domain(data, offset) - local offset, domain = dns.decStr(data, offset) - domain = domain or "" - return offset, string.format("%s.", domain) + local offset, domain = dns.decStr(data, offset) + domain = domain or "" + return offset, string.format("%s.", domain) end --- Build RFC 1035 root domain name from the name of the DNS server -- (e.g ns1.website.com.ar -> \007website\003com\002ar\000). --@param host The host. function build_domain(host) - local names, buf, x - local abs_name, i, tmp + local names, buf, x + local abs_name, i, tmp - buf = strbuf.new() - abs_name = {} + buf = strbuf.new() + abs_name = {} - names = stdnse.strsplit('%.', host) - if names == nil then names = {host} end + names = stdnse.strsplit('%.', host) + if names == nil then names = {host} end - -- try to determine root of domain name - for i, x in ipairs(listop.reverse(names)) do - table.insert(abs_name, x) - if not valid_tld(x) then break end - end + -- try to determine root of domain name + for i, x in ipairs(listop.reverse(names)) do + table.insert(abs_name, x) + if not valid_tld(x) then break end + end - i = 1 - abs_name = listop.reverse(abs_name) + i = 1 + abs_name = listop.reverse(abs_name) - -- prepend each element with its length - while i <= #abs_name do - buf = buf .. string.char(#abs_name[i]) .. abs_name[i] - i = i + 1 - end + -- prepend each element with its length + while i <= #abs_name do + buf = buf .. string.char(#abs_name[i]) .. abs_name[i] + i = i + 1 + end - buf = buf .. '\000' - return strbuf.dump(buf) + buf = buf .. '\000' + return strbuf.dump(buf) end local function parse_num_domain(data, offset) @@ -264,25 +264,25 @@ end --- Retrieve type specific data (rdata) from dns packets local RD = { 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, NS = parse_domain, MD = parse_domain, -- obsolete per rfc1035, use MX MF = parse_domain, -- obsolete per rfc1035, use MX CNAME = parse_domain, SOA = function(data, offset) - local field, info - info = strbuf.new() - -- name server - offset, field = parse_domain(data, offset) - info = info .. field; - -- mail box - offset, field = parse_domain(data, offset) - info = info .. field; - -- ignore other values - offset = offset + 20 - return offset, strbuf.dump(info, ' ') - end, + local field, info + info = strbuf.new() + -- name server + offset, field = parse_domain(data, offset) + info = info .. field; + -- mail box + offset, field = parse_domain(data, offset) + info = info .. field; + -- ignore other values + offset = offset + 20 + return offset, strbuf.dump(info, ' ') + end, MB = parse_domain, -- experimental per RFC 1035 MG = parse_domain, -- experimental per RFC 1035 MR = parse_domain, -- experimental per RFC 1035 @@ -392,7 +392,7 @@ local RD = { lon = 0-lon end 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, --NXT --obsolete RR relating to DNSSEC --EID NIMLOC --related to Nimrod DARPA project (Patton1995) @@ -453,44 +453,44 @@ local RD = { } function get_rdata(data, offset, ttype) - if typetab[ttype] == nil then - return offset, '' - elseif RD[typetab[ttype]] then - return RD[typetab[ttype]](data, offset) - else - local field - offset, field = bin.unpack("A" .. bto16(data, offset-2), data, offset) - return offset, ("hex: %s"):format(stdnse.tohex(field)) - end + if typetab[ttype] == nil then + return offset, '' + elseif RD[typetab[ttype]] then + return RD[typetab[ttype]](data, offset) + else + local field + offset, field = bin.unpack("A" .. bto16(data, offset-2), data, offset) + return offset, ("hex: %s"):format(stdnse.tohex(field)) + end end --- Get a single answer record from the current offset function get_answer_record(table, data, offset) - local line, rdlen, ttype + local line, rdlen, ttype - -- answer domain - offset, line = parse_domain(data, offset) - table.domain = line + -- answer domain + offset, line = parse_domain(data, offset) + table.domain = line - -- answer record type - ttype = bto16(data, offset) - if not(typetab[ttype] == nil) then - table.ttype = typetab[ttype] - end + -- answer record type + ttype = bto16(data, offset) + if not(typetab[ttype] == nil) then + table.ttype = typetab[ttype] + end - -- length of type specific data - rdlen = bto16(data, offset+8) + -- length of type specific data + rdlen = bto16(data, offset+8) - -- extra data, ignore ttl and class - offset, line = get_rdata(data, offset+10, ttype) - if(line == '') then - offset = offset + rdlen - return false, offset - else - table.rdata = line - end + -- extra data, ignore ttl and class + offset, line = get_rdata(data, offset+10, ttype) + if(line == '') then + offset = offset + rdlen + return false, offset + else + table.rdata = line + end - return true, offset + return true, offset end -- parse and save uniq records in the results table @@ -514,61 +514,61 @@ end -- parse and save only valid records function parse_records(number, data, results, offset) - while number > 0 do - local answer, st = {} - st, offset = get_answer_record(answer, data, offset) - if st then - parse_uniq_records(results, answer) - end - number = number - 1 + while number > 0 do + local answer, st = {} + st, offset = get_answer_record(answer, data, offset) + if st then + parse_uniq_records(results, answer) end - return offset + number = number - 1 + end + return offset end -- parse and save all records in order to dump them to output function parse_records_table(number, data, table, offset) - while number > 0 do - local answer, st = {} - st, offset = get_answer_record(answer, data, offset) - if st then - if answer.domain then - tab.add(table, 1, answer.domain) - end - if answer.ttype then - tab.add(table, 2, answer.ttype) - end - if answer.rdata then - tab.add(table, 3, answer.rdata) - end - tab.nextrow(table) - end - number = number - 1 + while number > 0 do + local answer, st = {} + st, offset = get_answer_record(answer, data, offset) + if st then + if answer.domain then + tab.add(table, 1, answer.domain) + end + if answer.ttype then + tab.add(table, 2, answer.ttype) + end + if answer.rdata then + tab.add(table, 3, answer.rdata) + end + tab.nextrow(table) end - return offset + number = number - 1 + end + return offset end -- 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). -- Responses returned by this iterator include the two-byte length prefix. function responses_iter(data) - local offset = 1 + local offset = 1 - return function() - local length, remaining, response + return function() + local length, remaining, response - remaining = #data - offset + 1 - if remaining == 0 then - 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 + remaining = #data - offset + 1 + if remaining == 0 then + 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 -- add axfr results to Nmap scan queue @@ -586,7 +586,7 @@ function add_zone_info(response) offset = offset + 12 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 if answers == 0 then @@ -601,8 +601,8 @@ function add_zone_info(response) -- parse all available resource records stdnse.print_debug(3, - "Script %s: parsing ANCOUNT == %d, NSCOUNT == %d, ARCOUNT == %d", - SCRIPT_NAME, answers, auth_answers, add_answers) + "Script %s: parsing ANCOUNT == %d, NSCOUNT == %d, ARCOUNT == %d", + SCRIPT_NAME, answers, auth_answers, add_answers) RR['Node Names'] = {} offset = parse_records(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) if not status then stdnse.print_debug(3, - "Error: failed to add all 'A' records.") + "Error: failed to add all 'A' records.") break end newhosts_count = newhosts_count + ret @@ -650,7 +650,7 @@ function add_zone_info(response) status, ret = target.add(rdata) if not status then stdnse.print_debug(3, - "Error: failed to add all '%s' records.", rectype) + "Error: failed to add all '%s' records.", rectype) break end newhosts_count = newhosts_count + ret @@ -672,8 +672,8 @@ function add_zone_info(response) end return true, tab.dump(outtab) .. "\n" .. - string.format("Total new targets added to Nmap scan queue: %d.", - nhosts) + string.format("Total new targets added to Nmap scan queue: %d.", + nhosts) end function dump_zone_info(table, response) @@ -690,11 +690,11 @@ function dump_zone_info(table, response) offset = offset + 12 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 if answers == 0 then - return false, 'transfer successful but no records' + return false, 'transfer successful but no records' end -- skip over the question section, we don't need it @@ -716,66 +716,66 @@ function dump_zone_info(table, response) end action = function(host, port) - if not dns_opts.domain then - return stdnse.format_output(false, - string.format("'%s' script needs a dnszonetransfer.domain argument.", - SCRIPT_TYPE)) + if not dns_opts.domain then + return stdnse.format_output(false, + string.format("'%s' script needs a dnszonetransfer.domain argument.", + 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 - 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 - return stdnse.format_output(true, ret) + return stdnse.format_output(true, ret) -- dump axfr results - else - local table = tab.new() - local status, ret = dump_zone_info(table, response_str) - if not status then - return stdnse.format_output(false, ret) - end - return '\n' .. tab.dump(table) + else + local table = tab.new() + local status, ret = dump_zone_info(table, response_str) + if not status then + return stdnse.format_output(false, ret) end + return '\n' .. tab.dump(table) + end end diff --git a/scripts/http-default-accounts.nse b/scripts/http-default-accounts.nse index 535366764..1639836ab 100644 --- a/scripts/http-default-accounts.nse +++ b/scripts/http-default-accounts.nse @@ -283,9 +283,9 @@ action = function(host, port) local path = basepath .. probe['path'] if http.page_exists(results[j], result_404, known_404, path, true) - and (not fingerprint.target_check - or fingerprint.target_check(host, port, path, results[j])) - then + and (not fingerprint.target_check + or fingerprint.target_check(host, port, path, results[j])) + then 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"]) --Check default credentials diff --git a/scripts/http-domino-enum-passwords.nse b/scripts/http-domino-enum-passwords.nse index 67d623047..797301822 100644 --- a/scripts/http-domino-enum-passwords.nse +++ b/scripts/http-domino-enum-passwords.nse @@ -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 path against which to check if authentication is required 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 - return true - elseif ( result.status == 200 and result.body and result.body:match("path @@ -121,14 +121,14 @@ end -- @param pass the password used for authentication -- @return true on valid access, false on failure local function isValidCredential( host, port, path, user, pass ) - -- we need to supply the no_cache directive, or else the http library - -- incorrectly tells us that the authentication was successfull - local result = http.get( host, port, path, { auth = { username = user, password = pass }, no_cache = true }) + -- we need to supply the no_cache directive, or else the http library + -- incorrectly tells us that the authentication was successfull + local result = http.get( host, port, path, { auth = { username = user, password = pass }, no_cache = true }) - if ( result.status == 401 ) then - return false - end - return true + if ( result.status == 401 ) then + return false + end + return true end --- Retrieves all uniq links in a pages @@ -138,28 +138,28 @@ end -- @param links [optional] table containing previousy retrieved links -- @return links table containing retrieved links local function getLinks( body, filter, links ) - local tmp = {} - local links = links or {} - local filter = filter or ".*" + local tmp = {} + local links = links or {} + local filter = filter or ".*" - if ( not(body) ) then return end - for _, v in ipairs( links ) do - tmp[v] = true - end + if ( not(body) ) then return end + for _, v in ipairs( links ) do + tmp[v] = true + end - for link in body:gmatch("") - local http_passwd = body:match("") - local dsp_http_passwd = body:match("") - local id_file = body:match("") + -- retrieve the details + local full_name = body:match("") + local http_passwd = body:match("") + local dsp_http_passwd = body:match("") + local id_file = body:match("") - -- Remove the parenthesis around the password - http_passwd = http_passwd:sub(2,-2) - -- In case we have more than one full name, return only the last - full_name = stdnse.strsplit(";%s*", full_name) - full_name = full_name[#full_name] + -- Remove the parenthesis around the password + http_passwd = http_passwd:sub(2,-2) + -- In case we have more than one full name, return only the last + full_name = stdnse.strsplit(";%s*", 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 --- Saves the ID file to disk @@ -199,162 +199,162 @@ end -- @return status true on success, false on failure -- @return err string containing error message if status is false local function saveIDFile( filename, data ) - local f = io.open( filename, "w") - if ( not(f) ) then - return false, ("Failed to open file (%s)"):format(filename) - end - if ( not(f:write( data ) ) ) then - return false, ("Failed to write file (%s)"):format(filename) - end - f:close() + local f = io.open( filename, "w") + if ( not(f) ) then + return false, ("Failed to open file (%s)"):format(filename) + end + if ( not(f:write( data ) ) ) then + return false, ("Failed to write file (%s)"):format(filename) + end + f:close() - return true + return true end action = function(host, port) - local path = "/names.nsf" - local download_path = stdnse.get_script_args('domino-enum-passwords.idpath') - local vhost= stdnse.get_script_args('domino-enum-passwords.hostname') - local user = stdnse.get_script_args('domino-enum-passwords.username') - local pass = stdnse.get_script_args('domino-enum-passwords.password') - local creds, pos, pager - local links, result, hashes,legacyHashes, id_files = {}, {}, {}, {},{} - 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 http_response + local path = "/names.nsf" + local download_path = stdnse.get_script_args('domino-enum-passwords.idpath') + local vhost= stdnse.get_script_args('domino-enum-passwords.hostname') + local user = stdnse.get_script_args('domino-enum-passwords.username') + local pass = stdnse.get_script_args('domino-enum-passwords.password') + local creds, pos, pager + local links, result, hashes,legacyHashes, id_files = {}, {}, {}, {},{} + 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 http_response - if ( nmap.registry['credentials'] and nmap.registry['credentials']['http'] ) then - creds = nmap.registry['credentials']['http'] - end + if ( nmap.registry['credentials'] and nmap.registry['credentials']['http'] ) then + creds = nmap.registry['credentials']['http'] + end - -- authentication required? - if ( requiresAuth( vhost or host, port, path ) ) then - if ( not(user) and not(creds) ) then - return " \n ERROR: No credentials supplied (see domino-enum-passwords.username and domino-enum-passwords.password)" - end + -- authentication required? + if ( requiresAuth( vhost or host, port, path ) ) then + if ( not(user) and not(creds) ) then + return " \n ERROR: No credentials supplied (see domino-enum-passwords.username and domino-enum-passwords.password)" + end - -- A user was provided, attempt to authenticate - if ( user ) then - if (not(isValidCredential( vhost or host, port, path, user, pass )) ) then - return " \n ERROR: The provided credentials where invalid" - end - elseif ( creds ) then - for _, cred in pairs(creds) do - if ( isValidCredential( vhost or host, port, path, cred.username, cred.password ) ) then - user = cred.username - pass = cred.password - break - end - end - end - end + -- A user was provided, attempt to authenticate + if ( user ) then + if (not(isValidCredential( vhost or host, port, path, user, pass )) ) then + return " \n ERROR: The provided credentials where invalid" + end + elseif ( creds ) then + for _, cred in pairs(creds) do + if ( isValidCredential( vhost or host, port, path, cred.username, cred.password ) ) then + user = cred.username + pass = cred.password + break + end + end + end + end - 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)" - end + 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)" + end - path = "/names.nsf/People?OpenView" - http_response = http.get( vhost or host, port, path, { auth = { username = user, password = pass }, no_cache = true }) - pager = getPager( http_response.body ) - if ( not(pager) ) then - if ( http_response.body and - http_response.body:match(".*.*" ) ) then - return " \n ERROR: Failed to authenticate" - else - return " \n ERROR: Failed to process results" - end - end - pos = 1 + path = "/names.nsf/People?OpenView" + http_response = http.get( vhost or host, port, path, { auth = { username = user, password = pass }, no_cache = true }) + pager = getPager( http_response.body ) + if ( not(pager) ) then + if ( http_response.body and + http_response.body:match(".*.*" ) ) then + return " \n ERROR: Failed to authenticate" + else + return " \n ERROR: Failed to process results" + end + end + pos = 1 - -- first collect all links - while( true ) do - path = pager .. "&Start=" .. pos - http_response = http.get( vhost or host, port, path, { auth = { username = user, password = pass }, no_cache = true }) + -- first collect all links + while( true ) do + path = pager .. "&Start=" .. pos + http_response = http.get( vhost or host, port, path, { auth = { username = user, password = pass }, no_cache = true }) - if ( http_response.status == 200 ) then - local size = #links - links = getLinks( http_response.body, "%?OpenDocument", links ) - -- No additions were made - if ( size == #links ) then - break - end - end + if ( http_response.status == 200 ) then + local size = #links + links = getLinks( http_response.body, "%?OpenDocument", links ) + -- No additions were made + if ( size == #links ) then + break + end + end - if ( max_fetch > 0 and max_fetch < #links ) then - break - end + if ( max_fetch > 0 and max_fetch < #links ) then + break + end - pos = pos + chunk_size - end + pos = pos + chunk_size + end - for _, link in ipairs(links) do - 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 }) - local u_details = getUserDetails( http_response.body ) + for _, link in ipairs(links) do + 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 }) + local u_details = getUserDetails( http_response.body ) - if ( max_fetch > 0 and (#hashes+#legacyHashes)>= max_fetch ) then - break - end + if ( max_fetch > 0 and (#hashes+#legacyHashes)>= max_fetch ) then + break + end - 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) - -- Old type are 32 bytes, new are 20 - if #u_details.passwd == 32 then - table.insert( legacyHashes, ("%s:%s"):format(u_details.fullname, u_details.passwd)) - else - table.insert( hashes, ("%s:(%s)"):format(u_details.fullname, u_details.passwd)) - end - end + 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) + -- Old type are 32 bytes, new are 20 + if #u_details.passwd == 32 then + table.insert( legacyHashes, ("%s:%s"):format(u_details.fullname, u_details.passwd)) + else + table.insert( hashes, ("%s:(%s)"):format(u_details.fullname, u_details.passwd)) + end + end - if ( u_details.idfile ) then - stdnse.print_debug(2, "Found ID file for user: %s", u_details.fullname) - if ( download_path ) then - 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 }) + if ( u_details.idfile ) then + stdnse.print_debug(2, "Found ID file for user: %s", u_details.fullname) + if ( download_path ) then + 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 }) - if ( http_response.status == 200 ) then - local filename = download_path .. "/" .. stdnse.filename_escape(u_details.fullname .. ".id") - local status, err = saveIDFile( filename, http_response.body ) - if ( status ) then - table.insert( id_files, ("%s ID File has been downloaded (%s)"):format(u_details.fullname, filename) ) - else - table.insert( id_files, ("%s ID File was not saved (error: %s)"):format(u_details.fullname, err ) ) - end - else - table.insert( id_files, ("%s ID File was not saved (error: unexpected response from server)"):format( u_details.fullname ) ) - end - else - table.insert( id_files, ("%s has ID File available for download"):format(u_details.fullname) ) - end - end - end + if ( http_response.status == 200 ) then + local filename = download_path .. "/" .. stdnse.filename_escape(u_details.fullname .. ".id") + local status, err = saveIDFile( filename, http_response.body ) + if ( status ) then + table.insert( id_files, ("%s ID File has been downloaded (%s)"):format(u_details.fullname, filename) ) + else + table.insert( id_files, ("%s ID File was not saved (error: %s)"):format(u_details.fullname, err ) ) + end + else + table.insert( id_files, ("%s ID File was not saved (error: unexpected response from server)"):format( u_details.fullname ) ) + end + else + table.insert( id_files, ("%s has ID File available for download"):format(u_details.fullname) ) + end + end + end - if( #hashes + #legacyHashes > 0) then - table.insert( result, { name = "Information", [1] = ("Information retrieved as: \"%s\""):format(user) } ) - end + if( #hashes + #legacyHashes > 0) then + table.insert( result, { name = "Information", [1] = ("Information retrieved as: \"%s\""):format(user) } ) + end - if ( #hashes ) then - hashes.name = "Internet hashes (salted, jtr: --format=DOMINOSEC)" - table.insert( result, hashes ) - end - if (#legacyHashes ) then - legacyHashes.name = "Internet hashes (unsalted, jtr: --format=lotus5)" - table.insert( result, legacyHashes ) - end + if ( #hashes ) then + hashes.name = "Internet hashes (salted, jtr: --format=DOMINOSEC)" + table.insert( result, hashes ) + end + if (#legacyHashes ) then + legacyHashes.name = "Internet hashes (unsalted, jtr: --format=lotus5)" + table.insert( result, legacyHashes ) + end - if ( #id_files ) then - id_files.name = "ID Files" - table.insert( result, id_files ) - end + if ( #id_files ) then + id_files.name = "ID Files" + table.insert( result, id_files ) + end - local result = stdnse.format_output(true, result) + local result = stdnse.format_output(true, result) - if ( max_fetch > 0 ) then - result = result .. (" \n Results limited to %d results (see domino-enum-passwords.count)"):format(max_fetch) - end + if ( max_fetch > 0 ) then + result = result .. (" \n Results limited to %d results (see domino-enum-passwords.count)"):format(max_fetch) + end - return result + return result end diff --git a/scripts/http-enum.nse b/scripts/http-enum.nse index a7585b110..965d10ad8 100644 --- a/scripts/http-enum.nse +++ b/scripts/http-enum.nse @@ -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). local function get_variations(filename) - local variations = {} + local variations = {} - if(filename == nil or filename == "" or filename == "/") then - return {} - end + if(filename == nil or filename == "" or filename == "/") then + return {} + end - local is_directory = (string.sub(filename, #filename, #filename) == "/") - if(is_directory) then - filename = string.sub(filename, 1, #filename - 1) - end + local is_directory = (string.sub(filename, #filename, #filename) == "/") + if(is_directory) then + filename = string.sub(filename, 1, #filename - 1) + end - -- Try some extensions - table.insert(variations, filename .. ".bak") - table.insert(variations, filename .. ".1") - table.insert(variations, filename .. ".tmp") + -- Try some extensions + table.insert(variations, filename .. ".bak") + table.insert(variations, filename .. ".1") + table.insert(variations, filename .. ".tmp") - -- Strip off the extension, if it has one, and try it all again. - -- For now, just look for three-character extensions. - if(string.sub(filename, #filename - 3, #filename - 3) == '.') then - local bare = string.sub(filename, 1, #filename - 4) - local extension = string.sub(filename, #filename - 3) + -- Strip off the extension, if it has one, and try it all again. + -- For now, just look for three-character extensions. + if(string.sub(filename, #filename - 3, #filename - 3) == '.') then + local bare = string.sub(filename, 1, #filename - 4) + local extension = string.sub(filename, #filename - 3) - table.insert(variations, bare .. ".bak") - table.insert(variations, bare .. ".1") - table.insert(variations, bare .. ".tmp") - table.insert(variations, bare .. "_1" .. extension) - table.insert(variations, bare .. "2" .. extension) - end + table.insert(variations, bare .. ".bak") + table.insert(variations, bare .. ".1") + table.insert(variations, bare .. ".tmp") + table.insert(variations, bare .. "_1" .. extension) + table.insert(variations, bare .. "2" .. extension) + end - -- Some Windowsy things - local onlyname = string.sub(filename, 2) - -- If the name contains a '/', forget it - if(string.find(onlyname, "/") == nil) then - table.insert(variations, "/Copy of " .. onlyname) - table.insert(variations, "/Copy (2) of " .. onlyname) - table.insert(variations, "/Copy of Copy of " .. onlyname) + -- Some Windowsy things + local onlyname = string.sub(filename, 2) + -- If the name contains a '/', forget it + if(string.find(onlyname, "/") == nil) then + table.insert(variations, "/Copy of " .. onlyname) + table.insert(variations, "/Copy (2) of " .. onlyname) + table.insert(variations, "/Copy of Copy of " .. onlyname) - -- Word/Excel/etc replace the first two characters with '~$', it seems - table.insert(variations, "/~$" .. string.sub(filename, 4)) - end + -- Word/Excel/etc replace the first two characters with '~$', it seems + table.insert(variations, "/~$" .. string.sub(filename, 4)) + end - -- Some editors add a '~' - table.insert(variations, filename .. "~") + -- Some editors add a '~' + table.insert(variations, filename .. "~") - -- Try some directories - table.insert(variations, "/bak" .. filename) - table.insert(variations, "/backup" .. filename) - table.insert(variations, "/backups" .. filename) - table.insert(variations, "/beta" .. filename) - table.insert(variations, "/test" .. filename) + -- Try some directories + table.insert(variations, "/bak" .. filename) + table.insert(variations, "/backup" .. filename) + table.insert(variations, "/backups" .. filename) + table.insert(variations, "/beta" .. filename) + table.insert(variations, "/test" .. filename) - -- If it's a directory, add a '/' after every entry - if(is_directory) then - for i, v in ipairs(variations) do - variations[i] = v .. "/" - end - end + -- If it's a directory, add a '/' after every entry + if(is_directory) then + for i, v in ipairs(variations) do + variations[i] = v .. "/" + end + end - -- 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 .. ".tar") - table.insert(variations, filename .. ".tar.gz") - table.insert(variations, filename .. ".tgz") - table.insert(variations, filename .. ".tar.bz2") + -- 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 .. ".tar") + table.insert(variations, filename .. ".tar.gz") + table.insert(variations, filename .. ".tgz") + table.insert(variations, filename .. ".tar.bz2") - return variations + return variations end ---Get the list of fingerprints from files. The files are defined in fingerprint_files. If category @@ -163,227 +163,227 @@ end -- --@return An array of entries, each of which have a checkdir field, and possibly a checkdesc. local function get_fingerprints(fingerprint_file, category) - local entries = {} - local i - local total_count = 0 -- Used for 'limit' + local entries = {} + local i + local total_count = 0 -- Used for 'limit' - -- 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 - -- of that would be minimal (and definitely isn't security) - if(nmap.registry.http_fingerprints ~= nil) then - stdnse.print_debug(1, "http-enum: Using cached HTTP fingerprints") - return nmap.registry.http_fingerprints - end + -- 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 + -- of that would be minimal (and definitely isn't security) + if(nmap.registry.http_fingerprints ~= nil) then + stdnse.print_debug(1, "http-enum: Using cached HTTP fingerprints") + return nmap.registry.http_fingerprints + end - -- 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) - if(not(filename_full)) then - filename_full = fingerprint_file - end + -- 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) + if(not(filename_full)) then + filename_full = fingerprint_file + end - stdnse.print_debug("http-enum: Loading fingerprint database: %s", filename_full) - local env = setmetatable({fingerprints = {}}, {__index = _G}) - local file = loadfile(filename_full, "t", env) - if(not(file)) then - stdnse.print_debug("http-enum: Couldn't load configuration file: %s", filename_full) - return false, "Couldn't load fingerprint file: " .. filename_full - end + stdnse.print_debug("http-enum: Loading fingerprint database: %s", filename_full) + local env = setmetatable({fingerprints = {}}, {__index = _G}) + local file = loadfile(filename_full, "t", env) + if(not(file)) then + stdnse.print_debug("http-enum: Couldn't load configuration file: %s", filename_full) + return false, "Couldn't load fingerprint file: " .. filename_full + 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 - -- stop and don't load the file. - for i, fingerprint in pairs(fingerprints) do - -- Make sure we have a valid index - if(type(i) ~= 'number') then - return false, "The 'fingerprints' table is an array, not a table; all indexes should be numeric" - end + -- Sanity check our file to ensure that all the fields were good. If any are bad, we + -- stop and don't load the file. + for i, fingerprint in pairs(fingerprints) do + -- Make sure we have a valid index + if(type(i) ~= 'number') then + return false, "The 'fingerprints' table is an array, not a table; all indexes should be numeric" + end - -- Make sure they have either a string or a table of probes - if(not(fingerprint.probes) or - (type(fingerprint.probes) ~= 'table' and type(fingerprint.probes) ~= 'string') or - (type(fingerprint.probes) == 'table' and #fingerprint.probes == 0)) then - return false, "Invalid path found for fingerprint " .. i - end + -- Make sure they have either a string or a table of probes + if(not(fingerprint.probes) or + (type(fingerprint.probes) ~= 'table' and type(fingerprint.probes) ~= 'string') or + (type(fingerprint.probes) == 'table' and #fingerprint.probes == 0)) then + return false, "Invalid path found for fingerprint " .. i + end - -- Make sure fingerprint.path is a table - if(type(fingerprint.probes) == 'string') then - fingerprint.probes = {fingerprint.probes} - end + -- Make sure fingerprint.path is a table + if(type(fingerprint.probes) == 'string') then + fingerprint.probes = {fingerprint.probes} + end - -- Make sure the elements in the probes array are strings or arrays - for i, probe in pairs(fingerprint.probes) do - -- Make sure we have a valid index - if(type(i) ~= 'number') then - return false, "The 'probes' table is an array, not a table; all indexes should be numeric" - end + -- Make sure the elements in the probes array are strings or arrays + for i, probe in pairs(fingerprint.probes) do + -- Make sure we have a valid index + if(type(i) ~= 'number') then + return false, "The 'probes' table is an array, not a table; all indexes should be numeric" + end - -- Convert the probe to a table if it's a string - if(type(probe) == 'string') then - fingerprint.probes[i] = {path=fingerprint.probes[i]} - probe = fingerprint.probes[i] - end + -- Convert the probe to a table if it's a string + if(type(probe) == 'string') then + fingerprint.probes[i] = {path=fingerprint.probes[i]} + probe = fingerprint.probes[i] + end - -- Make sure the probes table has a 'path' - if(not(probe['path'])) then - return false, "The 'probes' table requires each element to have a 'path'." - end + -- Make sure the probes table has a 'path' + if(not(probe['path'])) then + return false, "The 'probes' table requires each element to have a 'path'." + end - -- If they didn't set a method, set it to 'GET' - if(not(probe['method'])) then - probe['method'] = 'GET' - end + -- If they didn't set a method, set it to 'GET' + if(not(probe['method'])) then + probe['method'] = 'GET' + end - -- Make sure the method's a string - if(type(probe['method']) ~= 'string') then - return false, "The 'method' in the probes file has to be a string" - end - end + -- Make sure the method's a string + if(type(probe['method']) ~= 'string') then + return false, "The 'method' in the probes file has to be a string" + end + end - -- Ensure that matches is an array - if(type(fingerprint.matches) ~= 'table') then - return false, "'matches' field has to be a table" - end + -- Ensure that matches is an array + if(type(fingerprint.matches) ~= 'table') then + return false, "'matches' field has to be a table" + end - -- Loop through the matches - for i, match in pairs(fingerprint.matches) do - -- Make sure we have a valid index - if(type(i) ~= 'number') then - return false, "The 'matches' table is an array, not a table; all indexes should be numeric" - end + -- Loop through the matches + for i, match in pairs(fingerprint.matches) do + -- Make sure we have a valid index + if(type(i) ~= 'number') then + return false, "The 'matches' table is an array, not a table; all indexes should be numeric" + end - -- Check that every element in the table is an array - if(type(match) ~= 'table') then - return false, "Every element of 'matches' field has to be a table" - end + -- Check that every element in the table is an array + if(type(match) ~= 'table') then + return false, "Every element of 'matches' field has to be a table" + end - -- Check the output field - if(match['output'] == nil or type(match['output']) ~= 'string') then - return false, "The 'output' field in 'matches' has to be present and a string" - end + -- Check the output field + if(match['output'] == nil or type(match['output']) ~= 'string') then + return false, "The 'output' field in 'matches' has to be present and a string" + end - -- 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 - return false, "The 'match' and 'dontmatch' fields in 'matches' have to be strings, if they exist" - end + -- 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 + return false, "The 'match' and 'dontmatch' fields in 'matches' have to be strings, if they exist" + end - -- Change blank 'match' strings to '.*' so they match everything - if(not(match['match']) or match['match'] == '') then - match['match'] = '(.*)' - end - end + -- Change blank 'match' strings to '.*' so they match everything + if(not(match['match']) or match['match'] == '') then + match['match'] = '(.*)' + end + end - -- 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 - return false, "The 'severity' field has to be an integer between 1 and 4" - else - fingerprint.severity = 1 - end + -- 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 + return false, "The 'severity' field has to be an integer between 1 and 4" + else + fingerprint.severity = 1 + end - -- Make sure ignore_404 is a boolean. Default it to false. - if(fingerprint.ignore_404 and type(fingerprint.ignore_404) ~= 'boolean') then - return false, "The 'ignore_404' field has to be a boolean" - else - fingerprint.ignore_404 = false - end - end + -- Make sure ignore_404 is a boolean. Default it to false. + if(fingerprint.ignore_404 and type(fingerprint.ignore_404) ~= 'boolean') then + return false, "The 'ignore_404' field has to be a boolean" + else + fingerprint.ignore_404 = false + end + end - -- Make sure we have some fingerprints fingerprints - if(#fingerprints == 0) then - return false, "No fingerprints were loaded" - end + -- Make sure we have some fingerprints fingerprints + if(#fingerprints == 0) then + return false, "No fingerprints were loaded" + end - -- If the user wanted to filter by category, do it - if(category) then - local filtered_fingerprints = {} - for _, fingerprint in pairs(fingerprints) do - if(fingerprint.category == category) then - table.insert(filtered_fingerprints, fingerprint) - end - end + -- If the user wanted to filter by category, do it + if(category) then + local filtered_fingerprints = {} + for _, fingerprint in pairs(fingerprints) do + if(fingerprint.category == category) then + table.insert(filtered_fingerprints, fingerprint) + end + end - fingerprints = filtered_fingerprints + fingerprints = filtered_fingerprints - -- Make sure we still have fingerprints after the category filter - if(#fingerprints == 0) then - return false, "No fingerprints matched the given category (" .. category .. ")" - end - end + -- Make sure we still have fingerprints after the category filter + if(#fingerprints == 0) then + return false, "No fingerprints matched the given category (" .. category .. ")" + end + end --- -- If the user wants to try variations, add them --- if(try_variations) then --- -- Get a list of all variations for this directory --- local variations = get_variations(entry['checkdir']) --- --- -- Make a copy of the entry for each of them --- for _, variation in ipairs(variations) do --- new_entry = {} --- for k, v in pairs(entry) do --- new_entry[k] = v --- end --- new_entry['checkdesc'] = new_entry['checkdesc'] .. " (variation)" --- new_entry['checkdir'] = variation --- table.insert(entries, new_entry) --- count = count + 1 --- end --- end + -- -- If the user wants to try variations, add them + -- if(try_variations) then + -- -- Get a list of all variations for this directory + -- local variations = get_variations(entry['checkdir']) + -- + -- -- Make a copy of the entry for each of them + -- for _, variation in ipairs(variations) do + -- new_entry = {} + -- for k, v in pairs(entry) do + -- new_entry[k] = v + -- end + -- new_entry['checkdesc'] = new_entry['checkdesc'] .. " (variation)" + -- new_entry['checkdir'] = variation + -- table.insert(entries, new_entry) + -- count = count + 1 + -- end + -- end - -- Cache the fingerprints for other scripts, so we aren't reading the files every time --- nmap.registry.http_fingerprints = fingerprints + -- Cache the fingerprints for other scripts, so we aren't reading the files every time + -- nmap.registry.http_fingerprints = fingerprints - return true, fingerprints + return true, fingerprints end action = function(host, port) - local response = {} + local response = {} - -- Read the script-args, keeping the old ones for reverse compatibility - local basepath = stdnse.get_script_args({'http-enum.basepath', 'path'}) or '/' - 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 category = stdnse.get_script_args('http-enum.category') --- 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 + -- Read the script-args, keeping the old ones for reverse compatibility + local basepath = stdnse.get_script_args({'http-enum.basepath', 'path'}) or '/' + 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 category = stdnse.get_script_args('http-enum.category') + -- 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 - -- Add URLs from external files - local status, fingerprints = get_fingerprints(fingerprint_file, category) - if(not(status)) then - return stdnse.format_output(false, fingerprints) - end - stdnse.print_debug(1, "http-enum: Loaded %d fingerprints", #fingerprints) + -- Add URLs from external files + local status, fingerprints = get_fingerprints(fingerprint_file, category) + if(not(status)) then + return stdnse.format_output(false, fingerprints) + end + stdnse.print_debug(1, "http-enum: Loaded %d fingerprints", #fingerprints) - -- Check what response we get for a 404 - local result, result_404, known_404 = http.identify_404(host, port) - if(result == false) then - return stdnse.format_output(false, result_404) - end + -- Check what response we get for a 404 + local result, result_404, known_404 = http.identify_404(host, port) + if(result == false) then + return stdnse.format_output(false, result_404) + end - -- Queue up the checks - local all = {} + -- Queue up the checks + local all = {} - -- Remove trailing slash, if it exists - if(#basepath > 1 and string.sub(basepath, #basepath, #basepath) == '/') then - basepath = string.sub(basepath, 1, #basepath - 1) - end + -- Remove trailing slash, if it exists + if(#basepath > 1 and string.sub(basepath, #basepath, #basepath) == '/') then + basepath = string.sub(basepath, 1, #basepath - 1) + end - -- Add a leading slash, if it doesn't exist - if(#basepath <= 1) then - basepath = '' - else - if(string.sub(basepath, 1, 1) ~= '/') then - basepath = '/' .. basepath - end - end + -- Add a leading slash, if it doesn't exist + if(#basepath <= 1) then + basepath = '' + else + if(string.sub(basepath, 1, 1) ~= '/') then + basepath = '/' .. basepath + end + end local results_nopipeline = {} - -- Loop through the fingerprints - 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 - -- Add each path. The order very much matters here. - for j = 1, #fingerprints[i].probes, 1 do + -- Loop through the fingerprints + 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 + -- Add each path. The order very much matters here. + for j = 1, #fingerprints[i].probes, 1 do 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) 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') end end - end + end - -- Perform all the requests. - local results = http.pipeline_go(host, port, all, nil) + -- Perform all the requests. + local results = http.pipeline_go(host, port, all, nil) - -- Check for http.pipeline error - if(results == nil) then - stdnse.print_debug(1, "http-enum: http.pipeline_go encountered an error") - return stdnse.format_output(false, "http.pipeline_go encountered an error") - end + -- Check for http.pipeline error + if(results == nil) then + stdnse.print_debug(1, "http-enum: http.pipeline_go encountered an error") + return stdnse.format_output(false, "http.pipeline_go encountered an error") + end - -- Loop through the fingerprints. Note that for each fingerprint, we may have multiple results - local j = 1 + -- Loop through the fingerprints. Note that for each fingerprint, we may have multiple results + local j = 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 - -- have one result, so increment the result value at each iteration - for _, probe in ipairs(fingerprint.probes) do + -- 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 + for _, probe in ipairs(fingerprint.probes) do local result if probe.nopipeline then result = results_nopipeline[j_nopipeline] @@ -422,66 +422,66 @@ action = function(host, port) result = results[j] j = j + 1 end - if(result) then - local path = basepath .. probe['path'] - local good = true - local output = nil - -- 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 - good = false - else - -- Loop through our matches table and see if anything matches our result - for _, match in ipairs(fingerprint.matches) do - if(match.match) then - local result, matches = http.response_contains(result, match.match) - if(result) then - output = match.output - good = true - for k, value in ipairs(matches) do - output = string.gsub(output, '\\' .. k, matches[k]) - end - end - else - output = match.output - end + if(result) then + local path = basepath .. probe['path'] + local good = true + local output = nil + -- 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 + good = false + else + -- Loop through our matches table and see if anything matches our result + for _, match in ipairs(fingerprint.matches) do + if(match.match) then + local result, matches = http.response_contains(result, match.match) + if(result) then + output = match.output + good = true + for k, value in ipairs(matches) do + output = string.gsub(output, '\\' .. k, matches[k]) + end + end + else + output = match.output + end - -- If nothing matched, turn off the match - if(not(output)) then - good = false - end + -- If nothing matched, turn off the match + if(not(output)) then + good = false + end - -- 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 - output = nil - good = false - end + -- 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 + output = nil + good = false + end - -- Break the loop if we found it - if(output) then - break - end - end - end + -- Break the loop if we found it + if(output) then + break + end + end + end - if(good) then - -- Save the path in the registry - http.save_path(stdnse.get_hostname(host), port.number, path, result.status) + if(good) then + -- Save the path in the registry + http.save_path(stdnse.get_hostname(host), port.number, path, result.status) - -- Add the path to the output - output = string.format("%s: %s", path, output) + -- Add the path to the output + output = string.format("%s: %s", path, output) - -- Build the status code, if it isn't a 200 - if(result.status ~= 200) then - output = output .. " (" .. http.get_status_string(result) .. ")" - end + -- Build the status code, if it isn't a 200 + if(result.status ~= 200) then + output = output .. " (" .. http.get_status_string(result) .. ")" + 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) - end - end - end - end + table.insert(response, output) + end + end + end + end - return stdnse.format_output(true, response) + return stdnse.format_output(true, response) end diff --git a/scripts/http-exif-spider.nse b/scripts/http-exif-spider.nse index fa3505516..66dad911b 100644 --- a/scripts/http-exif-spider.nse +++ b/scripts/http-exif-spider.nse @@ -500,27 +500,27 @@ function action(host, port) return string.match(url.file, "%.jpg") or string.match(url.file, "%.jpeg") 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 - return - end + if ( not(crawler) ) then + return + end - while(true) do + while(true) do -- Begin the crawler - local status, r = crawler:crawl() + local status, r = crawler:crawl() -- Make sure there's no error - if ( not(status) ) then - if ( r.err ) then - return stdnse.format_output(false, r.reason) - else - break - end - end + if ( not(status) ) then + if ( r.err ) then + return stdnse.format_output(false, r.reason) + else + break + end + end -- 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 stdnse.print_debug(1, "Attempting to read exif data from %s", r.url.raw) status, result = parse_jpeg(r.response.body) @@ -533,7 +533,7 @@ function action(host, port) table.insert(results, result) end end - end + end end return stdnse.format_output(true, results) diff --git a/scripts/http-fileupload-exploiter.nse b/scripts/http-fileupload-exploiter.nse index 2f3b4438f..6ae231a79 100644 --- a/scripts/http-fileupload-exploiter.nse +++ b/scripts/http-fileupload-exploiter.nse @@ -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. payloads = { { filename = "1.php", content = "", check = "777777" }, - { filename = "1.php3", content = "", check = "777777" }, --- { filename = "1.php4", content = "", check = "777777" }, --- { filename = "1.shtml", content = "", check = "777777" }, --- { filename = "1.py", 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.jsp", content = "<%= 123456 + 654321 %>", check = "777777" }, --- { filename = "1.asp", content = "<%= 123456 + 654321 %>", check = "777777" }, - } + { filename = "1.php3", content = "", check = "777777" }, +-- { filename = "1.php4", content = "", check = "777777" }, +-- { filename = "1.shtml", content = "", check = "777777" }, +-- { filename = "1.py", 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.jsp", content = "<%= 123456 + 654321 %>", check = "777777" }, +-- { filename = "1.asp", content = "<%= 123456 + 654321 %>", check = "777777" }, +} listofrequests = {} -- Escape for jsp and asp payloads. local escape = function(s) - return (s:gsub('%%', '%%%%')) + return (s:gsub('%%', '%%%%')) end -- Represents an upload-request. local function UploadRequest(host, port, submission, partofrequest, name, filename, mime, payload, check) - local request = { - host = host; - port = port; - submission = submission; - mime = mime; - name = name; - filename = filename; - partofrequest = partofrequest; - payload = payload; - check = check; - uploadedpaths = {}; - success = 0; + local request = { + host = host; + port = port; + submission = submission; + mime = mime; + name = name; + filename = filename; + partofrequest = partofrequest; + payload = payload; + check = check; + uploadedpaths = {}; + success = 0; - make = function(self) - local options = { header={} } - 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--' + make = function(self) + local options = { header={} } + 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--' - 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 - end; + return response.body + end; - checkPayload = function(self, uploadspaths) - for _, uploadpath in ipairs(uploadspaths) do - local response = http.get(host, port, uploadpath .. '/' .. filename, { no_cache = true } ) + checkPayload = function(self, uploadspaths) + for _, uploadpath in ipairs(uploadspaths) do + local response = http.get(host, port, uploadpath .. '/' .. filename, { no_cache = true } ) - if response.status ~= 404 then - if (response.body:match(self.check)) then - self.success = 1 - table.insert(self.uploadedpaths, uploadpath) - end - end - end - end; - } - table.insert(listofrequests, request) - return request + if response.status ~= 404 then + if (response.body:match(self.check)) then + self.success = 1 + table.insert(self.uploadedpaths, uploadpath) + end + end + end + end; + } + table.insert(listofrequests, request) + return request end -- Create customized requests for all of our payloads. local buildRequests = function(host, port, submission, name, mime, partofrequest, uploadspaths, image) - for i, p in ipairs(payloads) do - if image then - 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']) + for i, p in ipairs(payloads) do + if image then + 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 @@ -151,179 +151,179 @@ end -- Check if the payloads were succesfull by checking the content of pages in the uploadspaths array. local makeAndCheckRequests = function(uploadspaths) - local exit = 0 - local output = {"Succesfully uploaded and executed payloads: "} + local exit = 0 + local output = {"Succesfully uploaded and executed payloads: "} - for i=1, #listofrequests, 1 do - listofrequests[i]:make() - listofrequests[i]:checkPayload(uploadspaths) - if (listofrequests[i].success == 1) then - exit = 1 - table.insert(output, " Filename: " .. listofrequests[i].filename .. ", MIME: " .. listofrequests[i].mime .. ", Uploaded on: ") - for _, uploadedpath in ipairs(listofrequests[i].uploadedpaths) do - table.insert(output, uploadedpath .. "/" .. listofrequests[i].filename) - end - end + for i=1, #listofrequests, 1 do + listofrequests[i]:make() + listofrequests[i]:checkPayload(uploadspaths) + if (listofrequests[i].success == 1) then + exit = 1 + table.insert(output, " Filename: " .. listofrequests[i].filename .. ", MIME: " .. listofrequests[i].mime .. ", Uploaded on: ") + for _, uploadedpath in ipairs(listofrequests[i].uploadedpaths) do + table.insert(output, uploadedpath .. "/" .. listofrequests[i].filename) + end end + end - if exit == 1 then - return output - end + if exit == 1 then + return output + end - listofrequests = {} + listofrequests = {} end local prepareRequest = function(fields, fieldvalues) - local filefield = 0 - local req = "" - local value + local filefield = 0 + local req = "" + local value - for _, field in ipairs(fields) do - if field["type"] == "file" then - filefield = field - elseif field["type"] == "text" or field["type"] == "textarea" or field["type"] == "radio" or field["type"] == "checkbox" then - if fieldvalues[field["name"]] ~= nil then - value = fieldvalues[field["name"]] - else - value = "SampleData0" - end - req = req .. '--AaB03x\nContent-Disposition: form-data; name="' .. field["name"] .. '";\n\n' .. value .. '\n' - end + for _, field in ipairs(fields) do + if field["type"] == "file" then + filefield = field + elseif field["type"] == "text" or field["type"] == "textarea" or field["type"] == "radio" or field["type"] == "checkbox" then + if fieldvalues[field["name"]] ~= nil then + value = fieldvalues[field["name"]] + else + value = "SampleData0" + end + req = req .. '--AaB03x\nContent-Disposition: form-data; name="' .. field["name"] .. '";\n\n' .. value .. '\n' end + end - return req, filefield + return req, filefield end action = function(host, port) - 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 fieldvalues = stdnse.get_script_args("http-fileupload-exploiter.fieldvalues") or {} + 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 fieldvalues = stdnse.get_script_args("http-fileupload-exploiter.fieldvalues") or {} - local returntable = {} + local returntable = {} - local result - local foundform = 0 - local foundfield = 0 - local fail = 0 + local result + local foundform = 0 + local foundfield = 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 - return - end + if (not(crawler)) then + return + 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 - k, target = next(formpaths, index) - if (k == nil) then - break - end - response = http.get(host, port, target) + if formpaths then + k, target = next(formpaths, index) + if (k == nil) then + break + end + 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 - - 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 - + break 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 - 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 diff --git a/scripts/http-slowloris.nse b/scripts/http-slowloris.nse index 24a2b1239..75abedf2c 100644 --- a/scripts/http-slowloris.nse +++ b/scripts/http-slowloris.nse @@ -85,240 +85,240 @@ local Bestopt -- get time (in miliseconds) when the script should finish local function get_end_time() - if TimeLimit == nil then - return -1 - end - return 1000 * TimeLimit + nmap.clock_ms() + if TimeLimit == nil then + return -1 + end + return 1000 * TimeLimit + nmap.clock_ms() end local function set_parameters() - SendInterval = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.send_interval') or '100s') - if stdnse.get_script_args('http-slowloris.runforever') then - TimeLimit = nil - else - TimeLimit = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.timelimit') or '30m') - end + SendInterval = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.send_interval') or '100s') + if stdnse.get_script_args('http-slowloris.runforever') then + TimeLimit = nil + else + TimeLimit = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.timelimit') or '30m') + end end local function do_half_http(host, port, obj) local condvar = nmap.condvar(obj) - if StopAll then - condvar("signal") - return - end + if StopAll then + condvar("signal") + return + end - -- Create 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 + -- Create 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 - ThreadCount = ThreadCount + 1 - local catch = function() - -- This connection is now dead - ThreadCount = ThreadCount - 1 - stdnse.print_debug(SCRIPT_NAME .. " [HALF HTTP]: lost connection") - slowloris:close() - slowloris = nil - condvar("signal") - end + ThreadCount = ThreadCount + 1 + local catch = function() + -- This connection is now dead + ThreadCount = ThreadCount - 1 + stdnse.print_debug(SCRIPT_NAME .. " [HALF HTTP]: lost connection") + slowloris:close() + slowloris = nil + condvar("signal") + end - local try = nmap.new_try(catch) - try(slowloris:connect(host.ip, port, Bestopt)) + local try = nmap.new_try(catch) + try(slowloris:connect(host.ip, port, Bestopt)) - -- Build a half-http header. - local half_http = "POST /" .. tostring(math.random(100000, 900000)) .. " HTTP/1.1\r\n" .. - "Host: " .. host.ip .. "\r\n" .. - "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" .. - "Content-Length: 42\r\n" + -- Build a half-http header. + local half_http = "POST /" .. tostring(math.random(100000, 900000)) .. " HTTP/1.1\r\n" .. + "Host: " .. host.ip .. "\r\n" .. + "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" .. + "Content-Length: 42\r\n" - try(slowloris:send(half_http)) - ServerNotice = " (attack against " .. host.ip .. "): HTTP stream started." + try(slowloris:send(half_http)) + ServerNotice = " (attack against " .. host.ip .. "): HTTP stream started." - -- During the attack some connections will die and other will respawn. - -- Here we keep in mind the maximum concurrent connections reached. + -- During the attack some connections will die and other will respawn. + -- 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 - while true do - if StopAll then - break - end - stdnse.sleep(SendInterval) - try(slowloris:send("X-a: b\r\n")) - ServerNotice = " (attack against " .. host.ip .. "): Feeding HTTP stream..." - Queries = Queries + 1 - ServerNotice = ServerNotice .. "\n(attack against " .. host.ip .. "): " .. Queries .. " queries sent using " .. ThreadCount .. " connections." - end - slowloris:close() - ThreadCount = ThreadCount - 1 - condvar("signal") + -- Maintain a pending HTTP request by adding a new line at a regular 'feed' interval + while true do + if StopAll then + break + end + stdnse.sleep(SendInterval) + try(slowloris:send("X-a: b\r\n")) + ServerNotice = " (attack against " .. host.ip .. "): Feeding HTTP stream..." + Queries = Queries + 1 + ServerNotice = ServerNotice .. "\n(attack against " .. host.ip .. "): " .. Queries .. " queries sent using " .. ThreadCount .. " connections." + end + slowloris:close() + ThreadCount = ThreadCount - 1 + condvar("signal") end -- Monitor the web server local function do_monitor(host, port) - local general_faults = 0 - local request_faults = 0 -- keeps track of how many times we didn't get a reply from the server + local general_faults = 0 + 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" .. - "Host: " .. host.ip .. - "\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" - local opts = {} - local _ + local request = "GET / HTTP/1.1\r\n" .. + "Host: " .. host.ip .. + "\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" + local opts = {} + 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 - local monitor = nmap.new_socket() - local status = monitor:connect(host.ip, port, Bestopt) - if not status then - general_faults = general_faults + 1 - if general_faults > 3 then - Reason = "not-slowloris" - DOSed = true - break - end - else - status = monitor:send(request) - if not status then - general_faults = general_faults + 1 - if general_faults > 3 then - Reason = "not-slowloris" - DOSed = true - break - end - end - status, _ = monitor:receive_lines(1) - if not status then - stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: Didn't get a reply from " .. host.ip .. "." ) - monitor:close() - request_faults = request_faults +1 - if request_faults > 3 then - if TimeLimit then - stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: server " .. host.ip .. " is now unavailable. The attack worked.") - DOSed = true - end - monitor:close() - break - end - else - request_faults = 0 - general_faults = 0 - stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: ".. host.ip .." still up, answer received.") - stdnse.sleep(10) - monitor:close() - end - if StopAll then - break - end - end - end + while not StopAll do + local monitor = nmap.new_socket() + local status = monitor:connect(host.ip, port, Bestopt) + if not status then + general_faults = general_faults + 1 + if general_faults > 3 then + Reason = "not-slowloris" + DOSed = true + break + end + else + status = monitor:send(request) + if not status then + general_faults = general_faults + 1 + if general_faults > 3 then + Reason = "not-slowloris" + DOSed = true + break + end + end + status, _ = monitor:receive_lines(1) + if not status then + stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: Didn't get a reply from " .. host.ip .. "." ) + monitor:close() + request_faults = request_faults +1 + if request_faults > 3 then + if TimeLimit then + stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: server " .. host.ip .. " is now unavailable. The attack worked.") + DOSed = true + end + monitor:close() + break + end + else + request_faults = 0 + general_faults = 0 + stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: ".. host.ip .." still up, answer received.") + stdnse.sleep(10) + monitor:close() + end + if StopAll then + break + end + end + end end local Mutex = nmap.mutex("http-slowloris") local function worker_scheduler(host, port) - local Threads = {} - local obj = {} - local condvar = nmap.condvar(obj) - local i + local Threads = {} + local obj = {} + local condvar = nmap.condvar(obj) + local i - for i = 1, 1000 do - -- The real amount of sockets is triggered by the - -- '--max-parallelism' option. The remaining threads will replace - -- dead sockets during the attack - local co = stdnse.new_thread(do_half_http, host, port, obj) - Threads[co] = true - end + for i = 1, 1000 do + -- The real amount of sockets is triggered by the + -- '--max-parallelism' option. The remaining threads will replace + -- dead sockets during the attack + local co = stdnse.new_thread(do_half_http, host, port, obj) + Threads[co] = true + end - while not DOSed and not StopAll do - -- keep creating new threads, in case we want to run the attack indefinitely - repeat - if StopAll then - return - end + while not DOSed and not StopAll do + -- keep creating new threads, in case we want to run the attack indefinitely + repeat + if StopAll then + return + end - for thread in pairs(Threads) do - if coroutine.status(thread) == "dead" then - Threads[thread] = nil - end - end - stdnse.print_debug(SCRIPT_NAME .. " [SCHEDULER]: starting new thread") - local co = stdnse.new_thread(do_half_http, host, port, obj) - Threads[co] = true - if ( next(Threads) ) then - condvar("wait") - end - until next(Threads) == nil; - end + for thread in pairs(Threads) do + if coroutine.status(thread) == "dead" then + Threads[thread] = nil + end + end + stdnse.print_debug(SCRIPT_NAME .. " [SCHEDULER]: starting new thread") + local co = stdnse.new_thread(do_half_http, host, port, obj) + Threads[co] = true + if ( next(Threads) ) then + condvar("wait") + end + until next(Threads) == nil; + end end action = function(host, port) - Mutex("lock") -- we want only one slowloris instance running at a single - -- time even if multiple hosts are specified - -- in order to have as many sockets as we can available to - -- this script + Mutex("lock") -- we want only one slowloris instance running at a single + -- time even if multiple hosts are specified + -- in order to have as many sockets as we can available to + -- this script - set_parameters() + set_parameters() - local output = {} - local start, stop, dos_time + local output = {} + local start, stop, dos_time - start = os.date("!*t") - -- The first thread is for monitoring and is launched before the attack threads - stdnse.new_thread(do_monitor, host, port) - stdnse.sleep(2) -- let the monitor make the first request + start = os.date("!*t") + -- The first thread is for monitoring and is launched before the attack threads + stdnse.new_thread(do_monitor, host, port) + stdnse.sleep(2) -- let the monitor make the first request - stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: starting scheduler") - stdnse.new_thread(worker_scheduler, host, port) - local end_time = get_end_time() - local last_message - if TimeLimit == nil then - stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: running forever!") - end + stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: starting scheduler") + stdnse.new_thread(worker_scheduler, host, port) + local end_time = get_end_time() + local last_message + if TimeLimit == nil then + stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: running forever!") + end - -- return a live notice from time to time - while (nmap.clock_ms() < end_time or TimeLimit == nil) and not StopAll do - if ServerNotice ~= last_message then - -- don't flood the output by repeating the same info - stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: " .. ServerNotice) - last_message = ServerNotice - end - if DOSed and TimeLimit ~= nil then - break - end - stdnse.sleep(10) - end + -- return a live notice from time to time + while (nmap.clock_ms() < end_time or TimeLimit == nil) and not StopAll do + if ServerNotice ~= last_message then + -- don't flood the output by repeating the same info + stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: " .. ServerNotice) + last_message = ServerNotice + end + if DOSed and TimeLimit ~= nil then + break + end + stdnse.sleep(10) + end - stop = os.date("!*t") - dos_time = stdnse.format_difftime(stop, start) - StopAll = true - if DOSed then - if Reason == "slowloris" then - stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped, building output") - output = "Vulnerable:\n" .. - "the DoS attack took ".. - dos_time .. "\n" .. - "with ".. Sockets .. " concurrent connections\n" .. - "and " .. Queries .." sent queries" - else - stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped. Monitor couldn't communicate with the server.") - output = "Probably vulnerable:\n" .. - "the DoS attack took " .. dos_time .. "\n" .. - "with " .. Sockets .. " concurrent connections\n" .. - "and " .. Queries .. " sent queries\n" .. - "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." - end - Mutex("done") -- release the mutex - return stdnse.format_output(true, output) - end - Mutex("done") -- release the mutex - return false + stop = os.date("!*t") + dos_time = stdnse.format_difftime(stop, start) + StopAll = true + if DOSed then + if Reason == "slowloris" then + stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped, building output") + output = "Vulnerable:\n" .. + "the DoS attack took ".. + dos_time .. "\n" .. + "with ".. Sockets .. " concurrent connections\n" .. + "and " .. Queries .." sent queries" + else + stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped. Monitor couldn't communicate with the server.") + output = "Probably vulnerable:\n" .. + "the DoS attack took " .. dos_time .. "\n" .. + "with " .. Sockets .. " concurrent connections\n" .. + "and " .. Queries .. " sent queries\n" .. + "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." + end + Mutex("done") -- release the mutex + return stdnse.format_output(true, output) + end + Mutex("done") -- release the mutex + return false end diff --git a/scripts/http-waf-fingerprint.nse b/scripts/http-waf-fingerprint.nse index 01adcc3c5..edf543567 100644 --- a/scripts/http-waf-fingerprint.nse +++ b/scripts/http-waf-fingerprint.nse @@ -63,615 +63,615 @@ portrule = shortport.service("http") local bigip bigip = { - name = "F5 BigIP", - detected = false, - version = nil, + name = "F5 BigIP", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do + match = function(responses) + for _, response in pairs(responses) do - if response.header['x-cnection'] then - stdnse.print_debug("%s BigIP detected through X-Cnection header.", SCRIPT_NAME) - bigip.detected = true - return - end + if response.header['x-cnection'] then + stdnse.print_debug("%s BigIP detected through X-Cnection header.", SCRIPT_NAME) + bigip.detected = true + return + end - if response.header.server == 'BigIP' then -- - stdnse.print_debug("%s BigIP detected through Server header.", SCRIPT_NAME) - bigip.detected = true - return - end + if response.header.server == 'BigIP' then -- + stdnse.print_debug("%s BigIP detected through Server header.", SCRIPT_NAME) + bigip.detected = true + return + end - for _, cookie in pairs(response.cookies) do -- - if string.find(cookie.name, "BIGipServer") then - stdnse.print_debug("%s BigIP detected through cookies.", SCRIPT_NAME) - bigip.detected = true - return - end - -- Application Security Manager module - if string.match(cookie.name, 'TS%w+') and string.len(cookie.name) <= 8 then - stdnse.print_debug("%s F5 ASM detected through cookies.", SCRIPT_NAME) - bigip.detected = true - return - end - end + for _, cookie in pairs(response.cookies) do -- + if string.find(cookie.name, "BIGipServer") then + stdnse.print_debug("%s BigIP detected through cookies.", SCRIPT_NAME) + bigip.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + -- Application Security Manager module + if string.match(cookie.name, 'TS%w+') and string.len(cookie.name) <= 8 then + stdnse.print_debug("%s F5 ASM detected through cookies.", SCRIPT_NAME) + bigip.detected = true + return + end + end + end + end, + intensive = function(host, port, root, responses) + end, } local webknight webknight = { - name = "Webknight", - detected = false, - version = nil, + name = "Webknight", + detected = false, + version = nil, - match = function(responses) - for name, response in pairs(responses) do - if response.header.server and string.find(response.header.server, 'WebKnight/') then -- - stdnse.print_debug("%s WebKnight detected through Server Header.", SCRIPT_NAME) - webknight.version = string.sub(response.header.server, 11) - webknight.detected = true - return - end - if response.status == 999 then - if not webknight.detected then stdnse.print_debug("%s WebKnight detected through 999 response status code.", SCRIPT_NAME) end - webknight.detected = true - end - end - end, - intensive = function(host, port, root, responses) - end, + match = function(responses) + for name, response in pairs(responses) do + if response.header.server and string.find(response.header.server, 'WebKnight/') then -- + stdnse.print_debug("%s WebKnight detected through Server Header.", SCRIPT_NAME) + webknight.version = string.sub(response.header.server, 11) + webknight.detected = true + return + end + if response.status == 999 then + if not webknight.detected then stdnse.print_debug("%s WebKnight detected through 999 response status code.", SCRIPT_NAME) end + webknight.detected = true + end + end + end, + intensive = function(host, port, root, responses) + end, } local isaserver isaserver = { - name = "ISA Server", - detected = false, - version = nil, - -- TODO Check if version detection is possible - -- based on the response reason - reason = {"Forbidden %( The server denied the specified Uniform Resource Locator %(URL%). Contact the server administrator. %)", - "Forbidden %( The ISA Server denied the specified Uniform Resource Locator %(URL%)" - }, + name = "ISA Server", + detected = false, + version = nil, + -- TODO Check if version detection is possible + -- based on the response reason + reason = {"Forbidden %( The server denied the specified Uniform Resource Locator %(URL%). Contact the server administrator. %)", + "Forbidden %( The ISA Server denied the specified Uniform Resource Locator %(URL%)" + }, - match = function(responses) - for _, response in pairs(responses) do - for _, reason in pairs(isaserver.reason) do -- - if http.response_contains(response, reason, true) then -- TODO Replace with something more performant - stdnse.print_debug("%s ISA Server detected through response reason.", SCRIPT_NAME) - isaserver.detected = true - return - end - end + match = function(responses) + for _, response in pairs(responses) do + for _, reason in pairs(isaserver.reason) do -- + if http.response_contains(response, reason, true) then -- TODO Replace with something more performant + stdnse.print_debug("%s ISA Server detected through response reason.", SCRIPT_NAME) + isaserver.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local airlock airlock = { - name = "Airlock", - detected = false, - version = nil, + name = "Airlock", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - for _, cookie in pairs(response.cookies) do -- - -- TODO Check if version detection is possible - -- based on the difference in cookies name - if cookie.name == "AL_LB" and string.sub(cookie.value, 1, 4) == '$xc/' then - stdnse.print_debug("%s Airlock detected through AL_LB cookies.", SCRIPT_NAME) - airlock.detected = true - return - end - if cookie.name == "AL_SESS" and (string.sub(cookie.value, 1, 5) == 'AAABL' - or string.sub(cookie.value, 1, 5) == 'LgEAA' )then - stdnse.print_debug("%s Airlock detected through AL_SESS cookies.", SCRIPT_NAME) - airlock.detected = true - return - end - end + match = function(responses) + for _, response in pairs(responses) do + for _, cookie in pairs(response.cookies) do -- + -- TODO Check if version detection is possible + -- based on the difference in cookies name + if cookie.name == "AL_LB" and string.sub(cookie.value, 1, 4) == '$xc/' then + stdnse.print_debug("%s Airlock detected through AL_LB cookies.", SCRIPT_NAME) + airlock.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + if cookie.name == "AL_SESS" and (string.sub(cookie.value, 1, 5) == 'AAABL' + or string.sub(cookie.value, 1, 5) == 'LgEAA' )then + stdnse.print_debug("%s Airlock detected through AL_SESS cookies.", SCRIPT_NAME) + airlock.detected = true + return + end + end + end + end, + intensive = function(host, port, root, responses) + end, } local barracuda barracuda = { - name = "Barracuda", - detected = false, - version = nil, + name = "Barracuda", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - for _, cookie in pairs(response.cookies) do - if cookie.name == "barra_counter_session" then - stdnse.print_debug("%s Barracuda detected through cookies.", SCRIPT_NAME) - barracuda.detected = true - return - end - end + match = function(responses) + for _, response in pairs(responses) do + for _, cookie in pairs(response.cookies) do + if cookie.name == "barra_counter_session" then + stdnse.print_debug("%s Barracuda detected through cookies.", SCRIPT_NAME) + barracuda.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local denyall denyall = { - name = "Denyall", - detected = false, - version = nil, + name = "Denyall", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - for _, cookie in pairs(response.cookies) do - -- TODO Check accuracy - if cookie.name == "sessioncookie" then - stdnse.print_debug("%s Denyall detected through cookies.", SCRIPT_NAME) - denyall.detected = true - return - end - end + match = function(responses) + for _, response in pairs(responses) do + for _, cookie in pairs(response.cookies) do + -- TODO Check accuracy + if cookie.name == "sessioncookie" then + stdnse.print_debug("%s Denyall detected through cookies.", SCRIPT_NAME) + denyall.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local f5trafficshield f5trafficshield = { - name = "F5 Traffic Shield", - detected = false, - version = nil, + name = "F5 Traffic Shield", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - -- TODO Check for version detection possibility - -- based on the cookie name / server header presence - if response.header.server == "F5-TrafficShield" then - stdnse.print_debug("%s F5 Traffic Shield detected through Server header.", SCRIPT_NAME) - f5trafficshield.detected = true - return - end + match = function(responses) + for _, response in pairs(responses) do + -- TODO Check for version detection possibility + -- based on the cookie name / server header presence + if response.header.server == "F5-TrafficShield" then + stdnse.print_debug("%s F5 Traffic Shield detected through Server header.", SCRIPT_NAME) + f5trafficshield.detected = true + return + end - for _, cookie in pairs(response.cookies) do - if cookie.name == "ASINFO" then - stdnse.print_debug("%s F5 Traffic Shield detected through cookies.", SCRIPT_NAME) - f5trafficshield.detected = true - return - end - end + for _, cookie in pairs(response.cookies) do + if cookie.name == "ASINFO" then + stdnse.print_debug("%s F5 Traffic Shield detected through cookies.", SCRIPT_NAME) + f5trafficshield.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local teros teros = { - name = "Teros / Citrix Application Firewall Enterprise", -- CAF EX, according to citrix documentation - detected = false, - version = nil, + name = "Teros / Citrix Application Firewall Enterprise", -- CAF EX, according to citrix documentation + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - for _, cookie in pairs(response.cookies) do - if cookie.name == "st8id" or cookie.name == "st8_wat" or cookie.name == "st8_wlf" then - stdnse.print_debug("%s Teros / CAF detected through cookies.", SCRIPT_NAME) - teros.detected = true - return - end - end + match = function(responses) + for _, response in pairs(responses) do + for _, cookie in pairs(response.cookies) do + if cookie.name == "st8id" or cookie.name == "st8_wat" or cookie.name == "st8_wlf" then + stdnse.print_debug("%s Teros / CAF detected through cookies.", SCRIPT_NAME) + teros.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local binarysec binarysec = { - name = "BinarySec", - detected = false, - version = nil, + name = "BinarySec", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - if response.header.server and string.find(response.header.server, 'BinarySEC/') then -- - stdnse.print_debug("%s BinarySec detected through Server Header.", SCRIPT_NAME) - binarysec.version = string.sub(response.header.server, 11) - binarysec.detected = true - return - end - if response.header['x-binarysec-via'] or response.header['x-binarysec-nocache']then - if not binarysec.detected then stdnse.print_debug("%s BinarySec detected through header.", SCRIPT_NAME) end - binarysec.detected = true - end - end - end, - intensive = function(host, port, root, responses) - end, + match = function(responses) + for _, response in pairs(responses) do + if response.header.server and string.find(response.header.server, 'BinarySEC/') then -- + stdnse.print_debug("%s BinarySec detected through Server Header.", SCRIPT_NAME) + binarysec.version = string.sub(response.header.server, 11) + binarysec.detected = true + return + end + if response.header['x-binarysec-via'] or response.header['x-binarysec-nocache']then + if not binarysec.detected then stdnse.print_debug("%s BinarySec detected through header.", SCRIPT_NAME) end + binarysec.detected = true + end + end + end, + intensive = function(host, port, root, responses) + end, } local profense profense = { - name = "Profense", - detected = false, - version = nil, + name = "Profense", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - if response.header.server == 'Profense' then - stdnse.print_debug("%s Profense detected through Server header.", SCRIPT_NAME) - profense.detected = true - return - end - for _, cookie in pairs(response.cookies) do - if cookie.name == "PLBSID" then - stdnse.print_debug("%s Profense detected through cookies.", SCRIPT_NAME) - profense.detected = true - return - end - end + match = function(responses) + for _, response in pairs(responses) do + if response.header.server == 'Profense' then + stdnse.print_debug("%s Profense detected through Server header.", SCRIPT_NAME) + profense.detected = true + return + end + for _, cookie in pairs(response.cookies) do + if cookie.name == "PLBSID" then + stdnse.print_debug("%s Profense detected through cookies.", SCRIPT_NAME) + profense.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local netscaler netscaler = { - name = "Citrix Netscaler", - detected = false, - version = nil, + name = "Citrix Netscaler", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do + match = function(responses) + for _, response in pairs(responses) do - -- TODO Check for other version detection possibilities - -- based on fingerprint difference - if response.header.via and string.find(response.header.via, 'NS%-CACHE') then -- - stdnse.print_debug("%s Citrix Netscaler detected through Via Header.", SCRIPT_NAME) - netscaler.version = string.sub(response.header.via, 10, 12) - netscaler.detected = true - return - end + -- TODO Check for other version detection possibilities + -- based on fingerprint difference + if response.header.via and string.find(response.header.via, 'NS%-CACHE') then -- + stdnse.print_debug("%s Citrix Netscaler detected through Via Header.", SCRIPT_NAME) + netscaler.version = string.sub(response.header.via, 10, 12) + netscaler.detected = true + return + end - if response.header.cneonction == "close" or response.header.nncoection == "close" then - if not netscaler.detected then stdnse.print_debug("%s Netscaler detected through Cneoction/nnCoection header.", SCRIPT_NAME) end - netscaler.detected = true - end + if response.header.cneonction == "close" or response.header.nncoection == "close" then + if not netscaler.detected then stdnse.print_debug("%s Netscaler detected through Cneoction/nnCoection header.", SCRIPT_NAME) end + netscaler.detected = true + end - -- TODO Does X-CLIENT-IP apply to Citrix Application Firewall too ? - if response.header['x-client-ip'] then - if not netscaler.detected then stdnse.print_debug("%s Netscaler detected through X-CLIENT-IP header.", SCRIPT_NAME) end - netscaler.detected = true - end + -- TODO Does X-CLIENT-IP apply to Citrix Application Firewall too ? + if response.header['x-client-ip'] then + if not netscaler.detected then stdnse.print_debug("%s Netscaler detected through X-CLIENT-IP header.", SCRIPT_NAME) end + netscaler.detected = true + end - for _, cookie in pairs(response.cookies) do - if cookie.name == "ns_af" or cookie.name == "citrix_ns_id" or - string.find(cookie.name, "NSC_") then - if not netscaler.detected then stdnse.print_debug("%s Netscaler detected through cookies.", SCRIPT_NAME) end - netscaler.detected = true - end - end + for _, cookie in pairs(response.cookies) do + if cookie.name == "ns_af" or cookie.name == "citrix_ns_id" or + string.find(cookie.name, "NSC_") then + if not netscaler.detected then stdnse.print_debug("%s Netscaler detected through cookies.", SCRIPT_NAME) end + netscaler.detected = true end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local dotdefender dotdefender = { - name = "dotDefender", - detected = false, - version = nil, + name = "dotDefender", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - if response.header['X-dotdefender-denied'] == "1" then - stdnse.print_debug("%s dotDefender detected through X-dotDefender-denied header.", SCRIPT_NAME) - dotdefender.detected = true - return - end - end - end, - intensive = function(host, port, root, responses) - end, + match = function(responses) + for _, response in pairs(responses) do + if response.header['X-dotdefender-denied'] == "1" then + stdnse.print_debug("%s dotDefender detected through X-dotDefender-denied header.", SCRIPT_NAME) + dotdefender.detected = true + return + end + end + end, + intensive = function(host, port, root, responses) + end, } local ibmdatapower ibmdatapower = { - name = "IBM DataPower", - detected = false, - version = nil, + name = "IBM DataPower", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - if response.header['x-backside-transport'] then - stdnse.print_debug("%s IBM DataPower detected through X-Backside-Transport header.", SCRIPT_NAME) - ibmdatapower.detected = true - return - end - end - end, - intensive = function(host, port, root, responses) - end, + match = function(responses) + for _, response in pairs(responses) do + if response.header['x-backside-transport'] then + stdnse.print_debug("%s IBM DataPower detected through X-Backside-Transport header.", SCRIPT_NAME) + ibmdatapower.detected = true + return + end + end + end, + intensive = function(host, port, root, responses) + end, } local cloudflare cloudflare = { - name = "Cloudflare", - detected = false, - version = nil, + name = "Cloudflare", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - if response.header.server == 'cloudflare-nginx' then - stdnse.print_debug("%s Cloudflare detected through Server header.", SCRIPT_NAME) - cloudflare.detected = true - return - end - for _, cookie in pairs(response.cookies) do - if cookie.name == "__cfduid" then - stdnse.print_debug("%s Cloudflare detected through cookies.", SCRIPT_NAME) - cloudflare.detected = true - return - end - end + match = function(responses) + for _, response in pairs(responses) do + if response.header.server == 'cloudflare-nginx' then + stdnse.print_debug("%s Cloudflare detected through Server header.", SCRIPT_NAME) + cloudflare.detected = true + return + end + for _, cookie in pairs(response.cookies) do + if cookie.name == "__cfduid" then + stdnse.print_debug("%s Cloudflare detected through cookies.", SCRIPT_NAME) + cloudflare.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local incapsula incapsula = { - name = "Incapsula WAF", - detected = false, - version = nil, + name = "Incapsula WAF", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - for _, cookie in pairs(response.cookies) do - if string.find(cookie.name, 'incap_ses') or string.find(cookie.name, 'visid_incap') then - stdnse.print_debug("%s Incapsula WAF detected through cookies.", SCRIPT_NAME) - incapsula.detected = true - return - end - end + match = function(responses) + for _, response in pairs(responses) do + for _, cookie in pairs(response.cookies) do + if string.find(cookie.name, 'incap_ses') or string.find(cookie.name, 'visid_incap') then + stdnse.print_debug("%s Incapsula WAF detected through cookies.", SCRIPT_NAME) + incapsula.detected = true + return end - end, - intensive = function(host, port, root, responses) - end, + end + end + end, + intensive = function(host, port, root, responses) + end, } local uspses uspses = { - name = "USP Secure Entry Server", - detected = false, - version = nil, + name = "USP Secure Entry Server", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - if response.header.server == 'Secure Entry Server' then - stdnse.print_debug("%s USP-SES detected through Server header.", SCRIPT_NAME) - uspses.detected = true - return - end - end - end, - intensive = function(host, port, root, responses) - end, + match = function(responses) + for _, response in pairs(responses) do + if response.header.server == 'Secure Entry Server' then + stdnse.print_debug("%s USP-SES detected through Server header.", SCRIPT_NAME) + uspses.detected = true + return + end + end + end, + intensive = function(host, port, root, responses) + end, } local ciscoacexml ciscoacexml = { - name = "Cisco ACE XML Gateway", - detected = false, - version = nil, + name = "Cisco ACE XML Gateway", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - if response.header.server == 'ACE XML Gateway' then - stdnse.print_debug("%s Cisco ACE XML Gateway detected through Server header.", SCRIPT_NAME) - ciscoacexml.detected = true - return - end - end - end, - intensive = function(host, port, root, responses) - end, + match = function(responses) + for _, response in pairs(responses) do + if response.header.server == 'ACE XML Gateway' then + stdnse.print_debug("%s Cisco ACE XML Gateway detected through Server header.", SCRIPT_NAME) + ciscoacexml.detected = true + return + end + end + end, + intensive = function(host, port, root, responses) + end, } local modsecurity modsecurity = { - -- Credit to Brendan Coles - name = "ModSecurity", - detected = false, - version = nil, + -- Credit to Brendan Coles + name = "ModSecurity", + detected = false, + version = nil, - match = function(responses) - for _, response in pairs(responses) do - if response.header.server and string.find(response.header.server, 'mod_security/') then - stdnse.print_debug("%s Modsecurity detected through Server Header.", SCRIPT_NAME) - local pos = string.find(response.header.server, 'mod_security/') - modsecurity.version = string.sub(response.header.server, pos + 13, pos + 18) - modsecurity.detected = true - return - end + match = function(responses) + for _, response in pairs(responses) do + if response.header.server and string.find(response.header.server, 'mod_security/') then + stdnse.print_debug("%s Modsecurity detected through Server Header.", SCRIPT_NAME) + local pos = string.find(response.header.server, 'mod_security/') + modsecurity.version = string.sub(response.header.server, pos + 13, pos + 18) + modsecurity.detected = true + return + end - if response.header.server and string.find(response.header.server, 'Mod_Security') then - stdnse.print_debug("%s Modsecurity detected through Server Header.", SCRIPT_NAME) - modsecurity.version = string.sub(response.header.server, 13, -9) - modsecurity.detected = true - return - end + if response.header.server and string.find(response.header.server, 'Mod_Security') then + stdnse.print_debug("%s Modsecurity detected through Server Header.", SCRIPT_NAME) + modsecurity.version = string.sub(response.header.server, 13, -9) + modsecurity.detected = true + return + end - -- The default SecServerSignature value is "NOYB" <= TODO For older versions, so we could - -- probably do some version detection out of it. - if response.header.server == 'NOYB' then - stdnse.print_debug("%s modsecurity detected through Server header.", SCRIPT_NAME) - modsecurity.detected = true - end - end - end, - intensive = function(host, port, root, responses) - end, + -- The default SecServerSignature value is "NOYB" <= TODO For older versions, so we could + -- probably do some version detection out of it. + if response.header.server == 'NOYB' then + stdnse.print_debug("%s modsecurity detected through Server header.", SCRIPT_NAME) + modsecurity.detected = true + end + end + end, + intensive = function(host, port, root, responses) + end, } local naxsi naxsi = { - name = "Naxsi", - detected = false, - version = nil, + name = "Naxsi", + detected = false, + version = nil, - match = function(responses) - end, - intensive = function(host, port, root, responses) - -- Credit to Henri Doreau - local response = http.get(host, port, root .. "?a=[") -- This shouldn't trigget the rules - local response2 = http.get(host, port, root .. "?a=[[[]]]][[[]") -- This should trigger the score based rules + match = function(responses) + end, + intensive = function(host, port, root, responses) + -- Credit to Henri Doreau + local response = http.get(host, port, root .. "?a=[") -- This shouldn't trigget the rules + local response2 = http.get(host, port, root .. "?a=[[[]]]][[[]") -- This should trigger the score based rules - if response.status ~= response2.status then - stdnse.print_debug("%s Naxsi detected through intensive scan.", SCRIPT_NAME) - naxsi.detected = true - end - return - end, + if response.status ~= response2.status then + stdnse.print_debug("%s Naxsi detected through intensive scan.", SCRIPT_NAME) + naxsi.detected = true + end + return + end, } local wafs = { - -- WAFs that are commented out don't have reliable fingerprints - -- with no false positives yet. + -- WAFs that are commented out don't have reliable fingerprints + -- with no false positives yet. - bigip = bigip, - webknight = webknight, - isaserver = isaserver, - airlock = airlock, - barracuda = barracuda, - denyall = denyall, - f5trafficshield = f5trafficshield, - teros = teros, - binarysec = binarysec, - profense = profense, - netscaler = netscaler, - dotdefender = dotdefender, - ibmdatapower = ibmdatapower, - cloudflare = cloudflare, - incapsula = incapsula, - uspses = uspses, - ciscoacexml = ciscoacexml, - modsecurity = modsecurity, - naxsi = naxsi, --- netcontinuum = netcontinuum, --- secureiis = secureiis, --- urlscan = urlscan, --- beeware = beeware, --- hyperguard = hyperguard, --- websecurity = websecurity, --- imperva = imperva, --- ibmwas = ibmwas, --- nevisProxy = nevisProxy, --- genericwaf = genericwaf, + bigip = bigip, + webknight = webknight, + isaserver = isaserver, + airlock = airlock, + barracuda = barracuda, + denyall = denyall, + f5trafficshield = f5trafficshield, + teros = teros, + binarysec = binarysec, + profense = profense, + netscaler = netscaler, + dotdefender = dotdefender, + ibmdatapower = ibmdatapower, + cloudflare = cloudflare, + incapsula = incapsula, + uspses = uspses, + ciscoacexml = ciscoacexml, + modsecurity = modsecurity, + naxsi = naxsi, + -- netcontinuum = netcontinuum, + -- secureiis = secureiis, + -- urlscan = urlscan, + -- beeware = beeware, + -- hyperguard = hyperguard, + -- websecurity = websecurity, + -- imperva = imperva, + -- ibmwas = ibmwas, + -- nevisProxy = nevisProxy, + -- genericwaf = genericwaf, } local send_requests = function(host, port, root) - local requests, all, responses = {}, {}, {} + local requests, all, responses = {}, {}, {} - local dirtraversal = "../../../etc/passwd" - local cleanhtml = "hello" - local xssstring = "" - local cmdexe = "cmd.exe" + local dirtraversal = "../../../etc/passwd" + local cleanhtml = "hello" + local xssstring = "" + local cmdexe = "cmd.exe" - -- Normal index - all = http.pipeline_add(root, nil, all, "GET") - table.insert(requests,"normal") + -- Normal index + all = http.pipeline_add(root, nil, all, "GET") + table.insert(requests,"normal") - -- Normal inexisting - all = http.pipeline_add(root .. "asofKlj", nil, all, "GET") - table.insert(requests,"inexisting") + -- Normal inexisting + all = http.pipeline_add(root .. "asofKlj", nil, all, "GET") + table.insert(requests,"inexisting") - -- Invalid Method - all = http.pipeline_add(root, nil, all, "ASDE") - table.insert(requests,"invalidmethod") + -- Invalid Method + all = http.pipeline_add(root, nil, all, "ASDE") + table.insert(requests,"invalidmethod") - -- Directory traversal - all = http.pipeline_add(root .. "?parameter=" .. dirtraversal, nil, all, "GET") - table.insert(requests,"invalidmethod") + -- Directory traversal + all = http.pipeline_add(root .. "?parameter=" .. dirtraversal, nil, all, "GET") + table.insert(requests,"invalidmethod") - -- Invalid Host - all = http.pipeline_add(root , {header= {Host = "somerandomsite.com"}}, all, "GET") - table.insert(requests,"invalidhost") + -- Invalid Host + all = http.pipeline_add(root , {header= {Host = "somerandomsite.com"}}, all, "GET") + table.insert(requests,"invalidhost") - --Clean HTML encoded - all = http.pipeline_add(root .. "?parameter=" .. cleanhtml , nil, all, "GET") - table.insert(requests,"cleanhtml") + --Clean HTML encoded + all = http.pipeline_add(root .. "?parameter=" .. cleanhtml , nil, all, "GET") + table.insert(requests,"cleanhtml") - --Clean HTML - all = http.pipeline_add(root .. "?parameter=" .. url.escape(cleanhtml), nil, all, "GET") - table.insert(requests,"cleanhtmlencoded") + --Clean HTML + all = http.pipeline_add(root .. "?parameter=" .. url.escape(cleanhtml), nil, all, "GET") + table.insert(requests,"cleanhtmlencoded") - -- XSS - all = http.pipeline_add(root .. "?parameter=" .. xssstring, nil, all, "GET") - table.insert(requests,"xss") + -- XSS + all = http.pipeline_add(root .. "?parameter=" .. xssstring, nil, all, "GET") + table.insert(requests,"xss") - -- XSS encoded - all = http.pipeline_add(root .. "?parameter=" .. url.escape(xssstring), nil, all, "GET") - table.insert(requests,"xssencoded") + -- XSS encoded + all = http.pipeline_add(root .. "?parameter=" .. url.escape(xssstring), nil, all, "GET") + table.insert(requests,"xssencoded") - -- cmdexe - all = http.pipeline_add(root .. "?parameter=" .. cmdexe, nil, all, "GET") - table.insert(requests,"cmdexe") + -- cmdexe + all = http.pipeline_add(root .. "?parameter=" .. cmdexe, nil, all, "GET") + table.insert(requests,"cmdexe") - -- send all requests - local pipeline_responses = http.pipeline_go(host, port, all) - if not pipeline_responses then - stdnse.print_debug("%s No response from pipelined requests", SCRIPT_NAME) - return nil - end + -- send all requests + local pipeline_responses = http.pipeline_go(host, port, all) + if not pipeline_responses then + stdnse.print_debug("%s No response from pipelined requests", SCRIPT_NAME) + return nil + end - -- Associate responses with requests names - for i, response in pairs(pipeline_responses) do - responses[requests[i]] = response - end + -- Associate responses with requests names + for i, response in pairs(pipeline_responses) do + responses[requests[i]] = response + end - return responses + return responses end action = function(host, port) - local root = stdnse.get_script_args(SCRIPT_NAME .. '.root') or "/" - local intensive = stdnse.get_script_args(SCRIPT_NAME .. '.intensive') - local result = {"Detected WAF", {}} + local root = stdnse.get_script_args(SCRIPT_NAME .. '.root') or "/" + local intensive = stdnse.get_script_args(SCRIPT_NAME .. '.intensive') + local result = {"Detected WAF", {}} - -- We send requests - local responses = send_requests(host, port, root) - if not responses then - return nil - end + -- We send requests + local responses = send_requests(host, port, root) + if not responses then + return nil + end - -- We iterate over wafs table passing the responses list to each function to analyze - -- the presence of any fingerprints. - for _, waf in pairs(wafs) do - waf.match(responses) - if intensive then waf.intensive(host, port, root, responses) end - if waf.detected then - if waf.version then - table.insert(result[2], waf.name .. " version " .. waf.version) - else - table.insert(result[2], waf.name) - end - end - end - if #result[2] > 0 then - return stdnse.format_output(true, result) + -- We iterate over wafs table passing the responses list to each function to analyze + -- the presence of any fingerprints. + for _, waf in pairs(wafs) do + waf.match(responses) + if intensive then waf.intensive(host, port, root, responses) end + if waf.detected then + if waf.version then + table.insert(result[2], waf.name .. " version " .. waf.version) + else + table.insert(result[2], waf.name) + end end + end + if #result[2] > 0 then + return stdnse.format_output(true, result) + end end diff --git a/scripts/ip-geolocation-maxmind.nse b/scripts/ip-geolocation-maxmind.nse index 6158e1968..b42eba5c6 100644 --- a/scripts/ip-geolocation-maxmind.nse +++ b/scripts/ip-geolocation-maxmind.nse @@ -33,48 +33,48 @@ categories = {"discovery","external","safe"} hostrule = function(host) - local is_private, err = ipOps.isPrivate( host.ip ) - if is_private == nil then - stdnse.print_debug( "%s Error in Hostrule: %s.", SCRIPT_NAME, err ) - return false - end - return not is_private + local is_private, err = ipOps.isPrivate( host.ip ) + if is_private == nil then + stdnse.print_debug( "%s Error in Hostrule: %s.", SCRIPT_NAME, err ) + return false + end + return not is_private end local MaxmindDef = { - -- Database structure constants - COUNTRY_BEGIN = 16776960, - STATE_BEGIN_REV0 = 16700000, - STATE_BEGIN_REV1 = 16000000, + -- Database structure constants + COUNTRY_BEGIN = 16776960, + STATE_BEGIN_REV0 = 16700000, + STATE_BEGIN_REV1 = 16000000, - STRUCTURE_INFO_MAX_SIZE = 20, - DATABASE_INFO_MAX_SIZE = 100, + STRUCTURE_INFO_MAX_SIZE = 20, + DATABASE_INFO_MAX_SIZE = 100, - -- Database editions, - COUNTRY_EDITION = 1, - REGION_EDITION_REV0 = 7, - REGION_EDITION_REV1 = 3, - CITY_EDITION_REV0 = 6, - CITY_EDITION_REV1 = 2, - ORG_EDITION = 5, - ISP_EDITION = 4, - PROXY_EDITION = 8, - ASNUM_EDITION = 9, - NETSPEED_EDITION = 11, - COUNTRY_EDITION_V6 = 12, + -- Database editions, + COUNTRY_EDITION = 1, + REGION_EDITION_REV0 = 7, + REGION_EDITION_REV1 = 3, + CITY_EDITION_REV0 = 6, + CITY_EDITION_REV1 = 2, + ORG_EDITION = 5, + ISP_EDITION = 4, + PROXY_EDITION = 8, + ASNUM_EDITION = 9, + NETSPEED_EDITION = 11, + COUNTRY_EDITION_V6 = 12, - SEGMENT_RECORD_LENGTH = 3, - STANDARD_RECORD_LENGTH = 3, - ORG_RECORD_LENGTH = 4, - MAX_RECORD_LENGTH = 4, - MAX_ORG_RECORD_LENGTH = 300, - FULL_RECORD_LENGTH = 50, + SEGMENT_RECORD_LENGTH = 3, + STANDARD_RECORD_LENGTH = 3, + ORG_RECORD_LENGTH = 4, + MAX_RECORD_LENGTH = 4, + MAX_ORG_RECORD_LENGTH = 300, + FULL_RECORD_LENGTH = 50, - US_OFFSET = 1, - CANADA_OFFSET = 677, - WORLD_OFFSET = 1353, - FIPS_RANGE = 360, - DMA_MAP = { + US_OFFSET = 1, + CANADA_OFFSET = 677, + WORLD_OFFSET = 1353, + FIPS_RANGE = 360, + DMA_MAP = { [500] = 'Portland-Auburn, ME', [501] = 'New York, NY', [502] = 'Binghamton, NY', @@ -287,8 +287,8 @@ local MaxmindDef = { [866] = 'Fresno, CA', [868] = 'Chico-Redding, CA', [881] = 'Spokane, WA' - }, - COUNTRY_CODES = { + }, + COUNTRY_CODES = { '', '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', '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', 'VU', 'WF', 'WS', 'YE', 'YT', 'RS', 'ZA', 'ZM', 'ME', 'ZW', 'A1', 'A2', 'O1', 'AX', 'GG', 'IM', 'JE', 'BL', 'MF' - }, - COUNTRY_CODES3 = { + }, + COUNTRY_CODES3 = { '','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', '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', 'WLF','WSM','YEM','YT','SRB','ZAF','ZMB','MNE','ZWE','A1','A2','O1', 'ALA','GGY','IMN','JEY','BLM','MAF' - }, - COUNTRY_NAMES = { + }, + COUNTRY_NAMES = { "", "Asia/Pacific Region", "Europe", "Andorra", "United Arab Emirates", "Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia", "Netherlands Antilles", "Angola", "Antarctica", "Argentina", "American Samoa", @@ -386,226 +386,226 @@ local MaxmindDef = { "Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe", "Anonymous Proxy","Satellite Provider","Other", "Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin" - } + } } local ip2long=function(ip_str) - local ip = stdnse.strsplit('%.',ip_str) - local ip_num = (tonumber(ip[1])*16777216 + tonumber(ip[2])*65536 - + tonumber(ip[3])*256 + tonumber(ip[4])) - return ip_num + local ip = stdnse.strsplit('%.',ip_str) + local ip_num = (tonumber(ip[1])*16777216 + tonumber(ip[2])*65536 + + tonumber(ip[3])*256 + tonumber(ip[4])) + return ip_num end local GeoIP = { - new = function(self, filename) - local o = {} - setmetatable(o, self) - self.__index = self - o._filename=filename - local err - o._filehandle= assert(io.open(filename,'rb')) - o._databaseType = MaxmindDef.COUNTRY_EDITION - o._recordLength = MaxmindDef.STANDARD_RECORD_LENGTH + new = function(self, filename) + local o = {} + setmetatable(o, self) + self.__index = self + o._filename=filename + local err + o._filehandle= assert(io.open(filename,'rb')) + o._databaseType = MaxmindDef.COUNTRY_EDITION + o._recordLength = MaxmindDef.STANDARD_RECORD_LENGTH - local filepos = o._filehandle:seek() - o._filehandle:seek("end",-3) + local filepos = o._filehandle:seek() + o._filehandle:seek("end",-3) - for i=1,MaxmindDef.STRUCTURE_INFO_MAX_SIZE do - local delim = o._filehandle:read(3) + for i=1,MaxmindDef.STRUCTURE_INFO_MAX_SIZE do + local delim = o._filehandle:read(3) - if delim == '\255\255\255' then - o._databaseType = o._filehandle:read(1):byte() - -- backward compatibility with databases from April 2003 and earlier - if (o._databaseType >= 106) then - o._databaseType = o._databaseType - 105 - end + if delim == '\255\255\255' then + o._databaseType = o._filehandle:read(1):byte() + -- backward compatibility with databases from April 2003 and earlier + if (o._databaseType >= 106) then + o._databaseType = o._databaseType - 105 + end - local fast_combo1={[MaxmindDef.CITY_EDITION_REV0]=true, - [MaxmindDef.CITY_EDITION_REV1]=true, - [MaxmindDef.ORG_EDITION]=true, - [MaxmindDef.ISP_EDITION]=true, - [MaxmindDef.ASNUM_EDITION]=true} + local fast_combo1={[MaxmindDef.CITY_EDITION_REV0]=true, + [MaxmindDef.CITY_EDITION_REV1]=true, + [MaxmindDef.ORG_EDITION]=true, + [MaxmindDef.ISP_EDITION]=true, + [MaxmindDef.ASNUM_EDITION]=true} - if o._databaseType == MaxmindDef.REGION_EDITION_REV0 then - o._databaseSegments = MaxmindDef.STATE_BEGIN_REV0 - elseif o._databaseType == MaxmindDef.REGION_EDITION_REV1 then - o._databaseSegments = MaxmindDef.STATE_BEGIN_REV1 - elseif fast_combo1[o._databaseType] then - o._databaseSegments = 0 - local buf = o._filehandle:read(MaxmindDef.SEGMENT_RECORD_LENGTH) + if o._databaseType == MaxmindDef.REGION_EDITION_REV0 then + o._databaseSegments = MaxmindDef.STATE_BEGIN_REV0 + elseif o._databaseType == MaxmindDef.REGION_EDITION_REV1 then + o._databaseSegments = MaxmindDef.STATE_BEGIN_REV1 + elseif fast_combo1[o._databaseType] then + o._databaseSegments = 0 + local buf = o._filehandle:read(MaxmindDef.SEGMENT_RECORD_LENGTH) - -- the original representation in the MaxMind API is ANSI C integer - -- which should not overflow the greatest value Lua can offer ;) - for j=0,(MaxmindDef.SEGMENT_RECORD_LENGTH-1) do - o._databaseSegments = o._databaseSegments + bit.lshift( buf:byte(j+1), j*8) - end + -- the original representation in the MaxMind API is ANSI C integer + -- which should not overflow the greatest value Lua can offer ;) + for j=0,(MaxmindDef.SEGMENT_RECORD_LENGTH-1) do + o._databaseSegments = o._databaseSegments + bit.lshift( buf:byte(j+1), j*8) + end - if o._databaseType == MaxmindDef.ORG_EDITION or o._databaseType == MaxmindDef.ISP_EDITION then - o._recordLength = MaxmindDef.ORG_RECORD_LENGTH - end - end - break - else - o._filehandle:seek("cur",-4) - end - end + if o._databaseType == MaxmindDef.ORG_EDITION or o._databaseType == MaxmindDef.ISP_EDITION then + o._recordLength = MaxmindDef.ORG_RECORD_LENGTH + end + end + break + else + o._filehandle:seek("cur",-4) + end + end - if o._databaseType == MaxmindDef.COUNTRY_EDITION then - o._databaseSegments = MaxmindDef.COUNTRY_BEGIN - end - o._filehandle:seek("set",filepos) + if o._databaseType == MaxmindDef.COUNTRY_EDITION then + o._databaseSegments = MaxmindDef.COUNTRY_BEGIN + end + o._filehandle:seek("set",filepos) - return o - end, + return o + end, - output_record_by_addr = function(self,addr) - local loc = self:record_by_addr(addr) - if not loc then return nil end + output_record_by_addr = function(self,addr) + local loc = self:record_by_addr(addr) + if not loc then return nil end - local output = {} - --output.name = "Maxmind database" - table.insert(output, "coordinates (lat,lon): " .. loc.latitude .. "," .. loc.longitude) + local output = {} + --output.name = "Maxmind database" + table.insert(output, "coordinates (lat,lon): " .. loc.latitude .. "," .. loc.longitude) - local str = "" - if loc.city then - str = str.."city: "..loc.city - end - if loc.metro_code then - str = str .. ", "..loc.metro_code - end - if loc.country_name then - str = str .. ", "..loc.country_name - end - table.insert(output,str) + local str = "" + if loc.city then + str = str.."city: "..loc.city + end + if loc.metro_code then + str = str .. ", "..loc.metro_code + end + if loc.country_name then + str = str .. ", "..loc.country_name + end + table.insert(output,str) - return output - end, + return output + end, - record_by_addr=function(self,addr) - local ipnum = ip2long(addr) - return self:_get_record(ipnum) - end, + record_by_addr=function(self,addr) + local ipnum = ip2long(addr) + return self:_get_record(ipnum) + end, - _get_record=function(self,ipnum) - local seek_country = self:_seek_country(ipnum) - if seek_country == self._databaseSegments then - return nil - end - local record_pointer = seek_country + (2 * self._recordLength - 1) * self._databaseSegments + _get_record=function(self,ipnum) + local seek_country = self:_seek_country(ipnum) + if seek_country == self._databaseSegments then + return nil + end + local record_pointer = seek_country + (2 * self._recordLength - 1) * self._databaseSegments - self._filehandle:seek("set",record_pointer) - local record_buf = self._filehandle:read(MaxmindDef.FULL_RECORD_LENGTH) + self._filehandle:seek("set",record_pointer) + local record_buf = self._filehandle:read(MaxmindDef.FULL_RECORD_LENGTH) - local record = {} - local start_pos = 1 - local char = record_buf:byte(start_pos) - char=char+1 - record.country_code = MaxmindDef.COUNTRY_CODES[char] - record.country_code3 = MaxmindDef.COUNTRY_CODES3[char] - record.country_name = MaxmindDef.COUNTRY_NAMES[char] - start_pos = start_pos + 1 - local end_pos = 0 + local record = {} + local start_pos = 1 + local char = record_buf:byte(start_pos) + char=char+1 + record.country_code = MaxmindDef.COUNTRY_CODES[char] + record.country_code3 = MaxmindDef.COUNTRY_CODES3[char] + record.country_name = MaxmindDef.COUNTRY_NAMES[char] + start_pos = start_pos + 1 + local end_pos = 0 - end_pos = record_buf:find("\0",start_pos) - if start_pos ~= end_pos then - record.region_name = record_buf:sub(start_pos, end_pos-1) - end - start_pos = end_pos + 1 + end_pos = record_buf:find("\0",start_pos) + if start_pos ~= end_pos then + record.region_name = record_buf:sub(start_pos, end_pos-1) + end + start_pos = end_pos + 1 - end_pos = record_buf:find("\0",start_pos) - if start_pos ~= end_pos then - record.city = record_buf:sub(start_pos, end_pos-1) - end - start_pos = end_pos + 1 + end_pos = record_buf:find("\0",start_pos) + if start_pos ~= end_pos then + record.city = record_buf:sub(start_pos, end_pos-1) + end + start_pos = end_pos + 1 - end_pos = record_buf:find("\0",start_pos) - if start_pos ~= end_pos then - record.postal_code = record_buf:sub(start_pos, end_pos-1) - end - start_pos = end_pos + 1 + end_pos = record_buf:find("\0",start_pos) + if start_pos ~= end_pos then + record.postal_code = record_buf:sub(start_pos, end_pos-1) + end + start_pos = end_pos + 1 - 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 - 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 + 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 - 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 + start_pos = start_pos +3 - if self._databaseType == MaxmindDef.CITY_EDITION_REV1 and record.country_code=='US' then - 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) - record.dma_code = math.floor(dmaarea_combo/1000) - record.area_code = dmaarea_combo % 1000 - else - record.dma_code = nil - record.area_code = nil - end + if self._databaseType == MaxmindDef.CITY_EDITION_REV1 and record.country_code=='US' then + 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) + record.dma_code = math.floor(dmaarea_combo/1000) + record.area_code = dmaarea_combo % 1000 + else + record.dma_code = nil + record.area_code = nil + end - if record.dma_code and MaxmindDef.DMA_MAP[record.dma_code] then - record.metro_code = MaxmindDef.DMA_MAP[record.dma_code] - else - record.metro_code = nil - end + if record.dma_code and MaxmindDef.DMA_MAP[record.dma_code] then + record.metro_code = MaxmindDef.DMA_MAP[record.dma_code] + else + record.metro_code = nil + end - return record - end, + return record + end, - _seek_country=function(self,ipnum) - local offset = 0 - for depth=31,0,-1 do - self._filehandle:seek("set", 2 * self._recordLength * offset) - local buf = self._filehandle:read(2*self._recordLength) + _seek_country=function(self,ipnum) + local offset = 0 + for depth=31,0,-1 do + self._filehandle:seek("set", 2 * self._recordLength * offset) + local buf = self._filehandle:read(2*self._recordLength) - local x = {} - x[0],x[1] = 0,0 + local x = {} + x[0],x[1] = 0,0 - for i=0,1 do - for j=0,(self._recordLength-1) do - x[i] = x[i] + bit.lshift(buf:byte((self._recordLength * i + j) +1 ), j*8) - end - end - -- Gotta test this out thorougly because of the ipnum - if bit.band(ipnum, bit.lshift(1,depth)) ~= 0 then - if x[1] >= self._databaseSegments then - return x[1] - end - offset = x[1] - else - if x[0] >= self._databaseSegments then - return x[0] - end - offset = x[0] - end - end - stdnse.print_debug('Error traversing database - perhaps it is corrupt?') - return nil - end, + for i=0,1 do + for j=0,(self._recordLength-1) do + x[i] = x[i] + bit.lshift(buf:byte((self._recordLength * i + j) +1 ), j*8) + end + end + -- Gotta test this out thorougly because of the ipnum + if bit.band(ipnum, bit.lshift(1,depth)) ~= 0 then + if x[1] >= self._databaseSegments then + return x[1] + end + offset = x[1] + else + if x[0] >= self._databaseSegments then + return x[0] + end + offset = x[0] + end + end + stdnse.print_debug('Error traversing database - perhaps it is corrupt?') + return nil + end, } 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 then - local gi = assert( GeoIP:new(f_maxmind), "Wrong file specified for a Maxmind database") - local out = gi:output_record_by_addr(host.ip) - output = out - else - 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) - output = out - end + --if f_maxmind is a string, it should specify the filename of the database + if f_maxmind then + local gi = assert( GeoIP:new(f_maxmind), "Wrong file specified for a Maxmind database") + local out = gi:output_record_by_addr(host.ip) + output = out + else + 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) + output = out + end - if(#output~=0) then - output.name = host.ip - if host.targetname then - output.name = output.name.." ("..host.targetname..")" - end - end + if(#output~=0) then + output.name = host.ip + if host.targetname then + output.name = output.name.." ("..host.targetname..")" + end + end - return stdnse.format_output(true,output) + return stdnse.format_output(true,output) end diff --git a/scripts/ipv6-node-info.nse b/scripts/ipv6-node-info.nse index 13603d4d2..5609ea124 100644 --- a/scripts/ipv6-node-info.nse +++ b/scripts/ipv6-node-info.nse @@ -52,142 +52,142 @@ local QTYPE_NODEADDRESSES = 3 local QTYPE_NODEIPV4ADDRESSES = 4 local QTYPE_STRINGS = { - [QTYPE_NOOP] = "NOOP", - [QTYPE_NODENAME] = "Hostnames", - [QTYPE_NODEADDRESSES] = "IPv6 addresses", - [QTYPE_NODEIPV4ADDRESSES] = "IPv4 addresses", + [QTYPE_NOOP] = "NOOP", + [QTYPE_NODENAME] = "Hostnames", + [QTYPE_NODEADDRESSES] = "IPv6 addresses", + [QTYPE_NODEIPV4ADDRESSES] = "IPv4 addresses", } local function build_ni_query(src, dst, qtype) - local payload, p, flags - local nonce + local payload, p, flags + local nonce - nonce = openssl.rand_pseudo_bytes(8) - if qtype == QTYPE_NODENAME then - flags = 0x0000 - elseif qtype == QTYPE_NODEADDRESSES then - -- Set all the flags GSLCA (see RFC 4620, Figure 3). - flags = 0x003E - elseif qtype == QTYPE_NODEIPV4ADDRESSES then - -- Set the A flag (see RFC 4620, Figure 4). - flags = 0x0002 - else - error("Unknown qtype " .. qtype) - end - payload = bin.pack(">SSAA", qtype, flags, nonce, dst) - p = packet.Packet:new() - p:build_icmpv6_header(ICMPv6_NODEINFOQUERY, ICMPv6_NODEINFOQUERY_IPv6ADDR, payload, src, dst) - p:build_ipv6_packet(src, dst, packet.IPPROTO_ICMPV6) + nonce = openssl.rand_pseudo_bytes(8) + if qtype == QTYPE_NODENAME then + flags = 0x0000 + elseif qtype == QTYPE_NODEADDRESSES then + -- Set all the flags GSLCA (see RFC 4620, Figure 3). + flags = 0x003E + elseif qtype == QTYPE_NODEIPV4ADDRESSES then + -- Set the A flag (see RFC 4620, Figure 4). + flags = 0x0002 + else + error("Unknown qtype " .. qtype) + end + payload = bin.pack(">SSAA", qtype, flags, nonce, dst) + p = packet.Packet:new() + p:build_icmpv6_header(ICMPv6_NODEINFOQUERY, ICMPv6_NODEINFOQUERY_IPv6ADDR, payload, src, dst) + p:build_ipv6_packet(src, dst, packet.IPPROTO_ICMPV6) - return p.buf + return p.buf end 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 local function open_sniffer(host) - local bpf - local s + local bpf + local s - s = nmap.new_socket() - bpf = string.format("ip6 and src host %s", host.ip) - s:pcap_open(host.interface, 1500, false, bpf) + s = nmap.new_socket() + bpf = string.format("ip6 and src host %s", host.ip) + s:pcap_open(host.interface, 1500, false, bpf) - return s + return s end local function send_queries(host) - local dnet + local dnet - dnet = nmap.new_dnet() - dnet:ip_open() - local p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEADDRESSES) - dnet:ip_send(p, host) - p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODENAME) - dnet:ip_send(p, host) - p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEIPV4ADDRESSES) - dnet:ip_send(p, host) - dnet:ip_close() + dnet = nmap.new_dnet() + dnet:ip_open() + local p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEADDRESSES) + dnet:ip_send(p, host) + p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODENAME) + dnet:ip_send(p, host) + p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEIPV4ADDRESSES) + dnet:ip_send(p, host) + dnet:ip_close() end local function empty(t) - return not next(t) + return not next(t) end -- 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 -- partial list of names that were parsed prior to the error. local function try_decode_nodenames(data) - local ttl - local names = {} - local pos = nil + local ttl + local names = {} + local pos = nil - pos, ttl = bin.unpack(">I", data, pos) - if not ttl then - return false, names - end - while pos <= #data do - local name + pos, ttl = bin.unpack(">I", data, pos) + if not ttl then + return false, names + end + while pos <= #data do + local name - pos, name = dns.decStr(data, pos) - if not name then - return false, names - end - -- Ignore empty names, such as those at the end. - if name ~= "" then - names[#names + 1] = name - end - end + pos, name = dns.decStr(data, pos) + if not name then + return false, names + end + -- Ignore empty names, such as those at the end. + if name ~= "" then + names[#names + 1] = name + end + end - return true, names + return true, names end local function stringify_noop(flags, data) - return "replied" + return "replied" end -- RFC 4620, section 6.3. local function stringify_nodename(flags, data) - local status, names - local text + local status, names + local text - status, names = try_decode_nodenames(data) - if empty(names) then - return - end - text = stdnse.strjoin(", ", names) - if not status then - text = text .. " (parsing error)" - end + status, names = try_decode_nodenames(data) + if empty(names) then + return + end + text = stdnse.strjoin(", ", names) + if not status then + text = text .. " (parsing error)" + end - return text + return text end -- RFC 4620, section 6.3. local function stringify_nodeaddresses(flags, data) - local ttl, binaddr - local text - local addrs = {} - local pos = nil + local ttl, binaddr + local text + local addrs = {} + local pos = nil - while true do - pos, ttl, binaddr = bin.unpack(">IA16", data, pos) - if not ttl then - break - end - addrs[#addrs + 1] = packet.toipv6(binaddr) - end - if empty(addrs) then - return - end + while true do + pos, ttl, binaddr = bin.unpack(">IA16", data, pos) + if not ttl then + break + end + addrs[#addrs + 1] = packet.toipv6(binaddr) + end + if empty(addrs) then + return + end - text = stdnse.strjoin(", ", addrs) - if bit.band(flags, 0x01) ~= 0 then - text = text .. " (more omitted for space reasons)" - end + text = stdnse.strjoin(", ", addrs) + if bit.band(flags, 0x01) ~= 0 then + text = text .. " (more omitted for space reasons)" + end - return text + return text end -- 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 -- 63 61 6c cal local function stringify_nodeipv4addresses(flags, data) - local status, names - local ttl, binaddr - local text - local addrs = {} - local pos = nil + local status, names + local ttl, binaddr + local text + local addrs = {} + local pos = nil - -- Check for DNS names. - status, names = try_decode_nodenames(data .. "\0\0") - if status then - return "(actually hostnames) " .. stdnse.strjoin(", ", names) - end + -- Check for DNS names. + status, names = try_decode_nodenames(data .. "\0\0") + if status then + return "(actually hostnames) " .. stdnse.strjoin(", ", names) + end - -- Okay, looks like it's really IP addresses. - while true do - pos, ttl, binaddr = bin.unpack(">IA4", data, pos) - if not ttl then - break - end - addrs[#addrs + 1] = packet.toip(binaddr) - end - if empty(addrs) then - return - end + -- Okay, looks like it's really IP addresses. + while true do + pos, ttl, binaddr = bin.unpack(">IA4", data, pos) + if not ttl then + break + end + addrs[#addrs + 1] = packet.toip(binaddr) + end + if empty(addrs) then + return + end - text = stdnse.strjoin(", ", addrs) - if bit.band(flags, 0x01) ~= 0 then - text = text .. " (more omitted for space reasons)" - end + text = stdnse.strjoin(", ", addrs) + if bit.band(flags, 0x01) ~= 0 then + text = text .. " (more omitted for space reasons)" + end - return text + return text end local STRINGIFY = { - [QTYPE_NOOP] = stringify_noop, - [QTYPE_NODENAME] = stringify_nodename, - [QTYPE_NODEADDRESSES] = stringify_nodeaddresses, - [QTYPE_NODEIPV4ADDRESSES] = stringify_nodeipv4addresses, + [QTYPE_NOOP] = stringify_noop, + [QTYPE_NODENAME] = stringify_nodename, + [QTYPE_NODEADDRESSES] = stringify_nodeaddresses, + [QTYPE_NODEIPV4ADDRESSES] = stringify_nodeipv4addresses, } local function handle_received_packet(buf) - local p, qtype, flags, data - local text + local p, qtype, flags, data + local text - p = packet.Packet:new(buf) - if p.icmpv6_type ~= ICMPv6_NODEINFORESP then - return - end - qtype = packet.u16(p.buf, p.icmpv6_offset + 4) - flags = packet.u16(p.buf, p.icmpv6_offset + 6) - data = string.sub(p.buf, p.icmpv6_offset + 16 + 1) + p = packet.Packet:new(buf) + if p.icmpv6_type ~= ICMPv6_NODEINFORESP then + return + end + qtype = packet.u16(p.buf, p.icmpv6_offset + 4) + flags = packet.u16(p.buf, p.icmpv6_offset + 6) + data = string.sub(p.buf, p.icmpv6_offset + 16 + 1) - if not STRINGIFY[qtype] then - -- 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) - return - end + if not STRINGIFY[qtype] then + -- 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) + return + end - if p.icmpv6_code == ICMPv6_NODEINFORESP_SUCCESS then - text = STRINGIFY[qtype](flags, data) - elseif p.icmpv6_code == ICMPv6_NODEINFORESP_REFUSED then - text = "refused" - elseif p.icmpv6_code == ICMPv6_NODEINFORESP_UNKNOWN then - text = string.format("target said: qtype %d is unknown", qtype) - else - text = string.format("unknown ICMPv6 code %d for qtype %d", p.icmpv6_code, qtype) - end + if p.icmpv6_code == ICMPv6_NODEINFORESP_SUCCESS then + text = STRINGIFY[qtype](flags, data) + elseif p.icmpv6_code == ICMPv6_NODEINFORESP_REFUSED then + text = "refused" + elseif p.icmpv6_code == ICMPv6_NODEINFORESP_UNKNOWN then + text = string.format("target said: qtype %d is unknown", qtype) + else + text = string.format("unknown ICMPv6 code %d for qtype %d", p.icmpv6_code, qtype) + end - return qtype, text + return qtype, text end local function format_results(results) - local QTYPE_ORDER = { - QTYPE_NOOP, - QTYPE_NODENAME, - QTYPE_NODEADDRESSES, - QTYPE_NODEIPV4ADDRESSES, - } - local output + local QTYPE_ORDER = { + QTYPE_NOOP, + QTYPE_NODENAME, + QTYPE_NODEADDRESSES, + QTYPE_NODEIPV4ADDRESSES, + } + local output - output = {} - for _, qtype in ipairs(QTYPE_ORDER) do - if results[qtype] then - output[#output + 1] = QTYPE_STRINGS[qtype] .. ": " .. results[qtype] - end - end + output = {} + for _, qtype in ipairs(QTYPE_ORDER) do + if results[qtype] then + output[#output + 1] = QTYPE_STRINGS[qtype] .. ": " .. results[qtype] + end + end - return stdnse.format_output(true, output) + return stdnse.format_output(true, output) end function action(host) - local s - local timeout, end_time, now - local pending, results + local s + local timeout, end_time, now + 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 = { - [QTYPE_NODENAME] = true, - [QTYPE_NODEADDRESSES] = true, - [QTYPE_NODEIPV4ADDRESSES] = true, - } - results = {} + pending = { + [QTYPE_NODENAME] = true, + [QTYPE_NODEADDRESSES] = true, + [QTYPE_NODEIPV4ADDRESSES] = true, + } + results = {} - now = nmap.clock_ms() - end_time = now + timeout - repeat - local _, status, buf + now = nmap.clock_ms() + end_time = now + timeout + repeat + local _, status, buf - s:set_timeout((end_time - now) * 1000) + s:set_timeout((end_time - now) * 1000) - status, _, _, buf = s:pcap_receive() - if status then - local qtype, text = handle_received_packet(buf) - if qtype then - results[qtype] = text - pending[qtype] = nil - end - end + status, _, _, buf = s:pcap_receive() + if status then + local qtype, text = handle_received_packet(buf) + if qtype then + results[qtype] = text + pending[qtype] = nil + end + end - now = nmap.clock_ms() - until empty(pending) or now > end_time + now = nmap.clock_ms() + until empty(pending) or now > end_time - s:pcap_close() + s:pcap_close() - return format_results(results) + return format_results(results) end diff --git a/scripts/irc-botnet-channels.nse b/scripts/irc-botnet-channels.nse index d07b62d52..7f9709958 100644 --- a/scripts/irc-botnet-channels.nse +++ b/scripts/irc-botnet-channels.nse @@ -56,22 +56,22 @@ local RPL_LIST = "322" local RPL_LISTEND = "323" local DEFAULT_CHANNELS = { - "loic", - "Agobot", - "Slackbot", - "Mytob", - "Rbot", - "SdBot", - "poebot", - "IRCBot", - "VanBot", - "MPack", - "Storm", - "GTbot", - "Spybot", - "Phatbot", - "Wargbot", - "RxBot", + "loic", + "Agobot", + "Slackbot", + "Mytob", + "Rbot", + "SdBot", + "poebot", + "IRCBot", + "VanBot", + "MPack", + "Storm", + "GTbot", + "Spybot", + "Phatbot", + "Wargbot", + "RxBot", } 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. local function irc_parse_message(s) - local prefix, command, params - local _, p, t + local prefix, command, params + local _, p, t - s = string.gsub(s, "\r?\n$", "") - if string.match(s, "^ *$") then - return true, nil - end + s = string.gsub(s, "\r?\n$", "") + if string.match(s, "^ *$") then + return true, nil + end - p = 0 - _, t, prefix = string.find(s, "^:([^ ]+) +", p + 1) - if t then - p = t - end + p = 0 + _, t, prefix = string.find(s, "^:([^ ]+) +", p + 1) + if t then + p = t + end - -- We do not check for any special format of the command name or - -- number. - _, p, command = string.find(s, "^([^ ]+)", p + 1) - if not p then - return nil, "Presumed message is missing a command." - end + -- We do not check for any special format of the command name or + -- number. + _, p, command = string.find(s, "^([^ ]+)", p + 1) + if not p then + return nil, "Presumed message is missing a command." + end - params = {} - while p + 1 <= #s do - local param + params = {} + while p + 1 <= #s do + local param - _, p = string.find(s, "^ +", p + 1) - if not p then - return nil, "Missing a space before param." - end - -- We don't do any checks on the contents of params. - if #params == 14 then - params[#params + 1] = string.sub(s, p + 1) - break - elseif string.match(s, "^:", p + 1) then - params[#params + 1] = string.sub(s, p + 2) - break - else - _, p, param = string.find(s, "^([^ ]+)", p + 1) - if not p then - return nil, "Missing a param." - end - params[#params + 1] = param - end - end + _, p = string.find(s, "^ +", p + 1) + if not p then + return nil, "Missing a space before param." + end + -- We don't do any checks on the contents of params. + if #params == 14 then + params[#params + 1] = string.sub(s, p + 1) + break + elseif string.match(s, "^:", p + 1) then + params[#params + 1] = string.sub(s, p + 2) + break + else + _, p, param = string.find(s, "^([^ ]+)", p + 1) + if not p then + return nil, "Missing a param." + end + params[#params + 1] = param + end + end - return true, prefix, command, params + return true, prefix, command, params end local function irc_compose_message(prefix, command, ...) - local parts, params + local parts, params - parts = {} - if prefix then - parts[#parts + 1] = prefix - end + parts = {} + if prefix then + parts[#parts + 1] = prefix + end - if string.match(command, "^:") then - return nil, "Command may not begin with ':'." - end - parts[#parts + 1] = command + if string.match(command, "^:") then + return nil, "Command may not begin with ':'." + end + parts[#parts + 1] = command - params = {...} - for i, param in ipairs(params) do - if not string.match(param, "^[^\0\r\n :][^\0\r\n ]*$") then - if i < #params then - return nil, "Bad format for param." - else - parts[#parts + 1] = ":" .. param - end - else - parts[#parts + 1] = param - end - end + params = {...} + for i, param in ipairs(params) do + if not string.match(param, "^[^\0\r\n :][^\0\r\n ]*$") then + if i < #params then + return nil, "Bad format for param." + else + parts[#parts + 1] = ":" .. param + end + else + parts[#parts + 1] = param + end + end - return stdnse.strjoin(" ", parts) .. "\r\n" + return stdnse.strjoin(" ", parts) .. "\r\n" end local function random_nick() - local nick = {} + local nick = {} - for i = 1, 9 do - nick[#nick + 1] = string.char(math.random(string.byte("a"), string.byte("z"))) - end + for i = 1, 9 do + nick[#nick + 1] = string.char(math.random(string.byte("a"), string.byte("z"))) + end - return table.concat(nick) + return table.concat(nick) end local function splitlines(s) - local lines = {} - local _, i, j + local lines = {} + local _, i, j - i = 1 - while i <= #s do - _, j = string.find(s, "\r?\n", i) - lines[#lines + 1] = string.sub(s, i, j) - if not j then - break - end - i = j + 1 - end + i = 1 + while i <= #s do + _, j = string.find(s, "\r?\n", i) + lines[#lines + 1] = string.sub(s, i, j) + if not j then + break + end + i = j + 1 + end - return lines + return lines end local function irc_connect(host, port, nick, user, pass) - local commands = {} - local irc = {} - local banner + local commands = {} + local irc = {} + local banner - -- Section 3.1.1. - if pass then - commands[#commands + 1] = irc_compose_message(nil, "PASS", pass) - end - nick = nick or random_nick() - commands[#commands + 1] = irc_compose_message(nil, "NICK", nick) - user = user or nick - commands[#commands + 1] = irc_compose_message(nil, "USER", user, "8", "*", user) + -- Section 3.1.1. + if pass then + commands[#commands + 1] = irc_compose_message(nil, "PASS", pass) + end + nick = nick or random_nick() + commands[#commands + 1] = irc_compose_message(nil, "NICK", nick) + user = user or nick + commands[#commands + 1] = irc_compose_message(nil, "USER", user, "8", "*", user) - irc.sd, banner = comm.tryssl(host, port, table.concat(commands)) - if not irc.sd then - return nil, "Unable to open connection." - end + irc.sd, banner = comm.tryssl(host, port, table.concat(commands)) + if not irc.sd then + return nil, "Unable to open connection." + end - irc.sd:set_timeout(60 * 1000) + irc.sd:set_timeout(60 * 1000) - -- Buffer these initial lines for irc_readline. - irc.linebuf = splitlines(banner) + -- Buffer these initial lines for irc_readline. + 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 local function irc_disconnect(irc) - irc.sd:close() + irc.sd:close() end local function irc_readline(irc) - local line + local line - if next(irc.linebuf) then - line = table.remove(irc.linebuf, 1) - if string.match(line, "\r?\n$") then - return line - else - -- We had only half a line buffered. - return line .. irc.buf() - end - else - return irc.buf() - end + if next(irc.linebuf) then + line = table.remove(irc.linebuf, 1) + if string.match(line, "\r?\n$") then + return line + else + -- We had only half a line buffered. + return line .. irc.buf() + end + else + return irc.buf() + end end local function irc_read_message(irc) - local line, err + local line, err - line, err = irc_readline(irc) - if not line then - return nil, err - end + line, err = irc_readline(irc) + if not line then + return nil, err + end - return irc_parse_message(line) + return irc_parse_message(line) end local function irc_send_message(irc, prefix, command, ...) - local line + local line - line = irc_compose_message(prefix, command, ...) - irc.sd:send(line) + line = irc_compose_message(prefix, command, ...) + irc.sd:send(line) end -- Prefix channel names with '#' if necessary and concatenate into a -- comma-separated list. local function concat_channel_list(channels) - local mod = {} + local mod = {} - for _, channel in ipairs(channels) do - if not string.match(channel, "^#") then - channel = "#" .. channel - end - mod[#mod + 1] = channel - end + for _, channel in ipairs(channels) do + if not string.match(channel, "^#") then + channel = "#" .. channel + end + mod[#mod + 1] = channel + end - return stdnse.strjoin(",", mod) + return stdnse.strjoin(",", mod) end function action(host, port) - local irc - local search_channels - local channels - local errorparams + local irc + local search_channels + local channels + local errorparams - search_channels = stdnse.get_script_args(SCRIPT_NAME .. ".channels") - if not search_channels then - search_channels = DEFAULT_CHANNELS - elseif type(search_channels) == "string" then - search_channels = {search_channels} - end + search_channels = stdnse.get_script_args(SCRIPT_NAME .. ".channels") + if not search_channels then + search_channels = DEFAULT_CHANNELS + elseif type(search_channels) == "string" then + search_channels = {search_channels} + end - irc = irc_connect(host, port) - irc_send_message(irc, "LIST", concat_channel_list(search_channels)) + irc = irc_connect(host, port) + irc_send_message(irc, "LIST", concat_channel_list(search_channels)) - channels = {} - while true do - local status, prefix, code, params + channels = {} + while true do + local status, prefix, code, params - status, prefix, code, params = irc_read_message(irc) - if not status then - -- Error message from irc_read_message. - errorparams = {prefix} - break - elseif code == "ERROR" then - errorparams = params - break - elseif code == RPL_TRYAGAIN then - errorparams = params - break - elseif code == RPL_LIST then - if #params >= 2 then - channels[#channels + 1] = params[2] - else - stdnse.print_debug("Got short " .. RPL_LIST .. "response.") - end - elseif code == RPL_LISTEND then - break - end - end - irc_disconnect(irc) + status, prefix, code, params = irc_read_message(irc) + if not status then + -- Error message from irc_read_message. + errorparams = {prefix} + break + elseif code == "ERROR" then + errorparams = params + break + elseif code == RPL_TRYAGAIN then + errorparams = params + break + elseif code == RPL_LIST then + if #params >= 2 then + channels[#channels + 1] = params[2] + else + stdnse.print_debug("Got short " .. RPL_LIST .. "response.") + end + elseif code == RPL_LISTEND then + break + end + end + irc_disconnect(irc) - if errorparams then - channels[#channels + 1] = "ERROR: " .. stdnse.strjoin(" ", errorparams) - end + if errorparams then + channels[#channels + 1] = "ERROR: " .. stdnse.strjoin(" ", errorparams) + end - return stdnse.format_output(true, channels) + return stdnse.format_output(true, channels) end diff --git a/scripts/krb5-enum-users.nse b/scripts/krb5-enum-users.nse index bdddcac5c..2c14dd0bb 100644 --- a/scripts/krb5-enum-users.nse +++ b/scripts/krb5-enum-users.nse @@ -56,235 +56,235 @@ portrule = shortport.port_or_service( 88, {"kerberos-sec"}, {"udp","tcp"}, {"ope -- of it. KRB5 = { - -- Valid Kerberos message types - MessageType = { - ['AS-REQ'] = 10, - ['AS-REP'] = 11, - ['KRB-ERROR'] = 30, - }, + -- Valid Kerberos message types + MessageType = { + ['AS-REQ'] = 10, + ['AS-REP'] = 11, + ['KRB-ERROR'] = 30, + }, - -- Some of the used error messages - ErrorMessages = { - ['KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN'] = 6, - ['KRB5KDC_ERR_PREAUTH_REQUIRED'] = 25, - ['KDC_ERR_WRONG_REALM'] = 68, - }, + -- Some of the used error messages + ErrorMessages = { + ['KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN'] = 6, + ['KRB5KDC_ERR_PREAUTH_REQUIRED'] = 25, + ['KDC_ERR_WRONG_REALM'] = 68, + }, - -- A list of some ot the encryption types - EncryptionTypes = { - { ['aes256-cts-hmac-sha1-96'] = 18 }, - { ['aes128-cts-hmac-sha1-96'] = 17 }, - { ['des3-cbc-sha1'] = 16 }, - { ['rc4-hmac'] = 23 }, - -- { ['des-cbc-crc'] = 1 }, - -- { ['des-cbc-md5'] = 3 }, - -- { ['des-cbc-md4'] = 2 } - }, + -- A list of some ot the encryption types + EncryptionTypes = { + { ['aes256-cts-hmac-sha1-96'] = 18 }, + { ['aes128-cts-hmac-sha1-96'] = 17 }, + { ['des3-cbc-sha1'] = 16 }, + { ['rc4-hmac'] = 23 }, + -- { ['des-cbc-crc'] = 1 }, + -- { ['des-cbc-md5'] = 3 }, + -- { ['des-cbc-md4'] = 2 } + }, - -- A list of principal name types - NameTypes = { - ['NT-PRINCIPAL'] = 1, - ['NT-SRV-INST'] = 2, - }, + -- A list of principal name types + NameTypes = { + ['NT-PRINCIPAL'] = 1, + ['NT-SRV-INST'] = 2, + }, - -- Creates a new Krb5 instance - -- @return o as the new instance - new = function(self) - local o = {} - setmetatable(o, self) - self.__index = self - return o + -- Creates a new Krb5 instance + -- @return o as the new instance + new = function(self) + local o = {} + setmetatable(o, self) + self.__index = self + 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, - -- A number of custom ASN1 decoders needed to decode the response - tagDecoder = { + ["1B"] = function( ... ) return KRB5.tagDecoder["18"](...) end, - ["18"] = function( self, encStr, elen, pos ) - return bin.unpack("A" .. elen, encStr, pos) - end, + ["6B"] = function( self, encStr, elen, pos ) + local seq + 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 - ["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, + -- A few Kerberos ASN1 encoders + tagEncoder = { - }, + ['table'] = function(self, val) - -- A few Kerberos ASN1 encoders - tagEncoder = { + local types = { + ['GeneralizedTime'] = 0x18, + ['GeneralString'] = 0x1B, + } - ['table'] = function(self, val) + local len = asn1.ASN1Encoder.encodeLength(#val[1]) - local types = { - ['GeneralizedTime'] = 0x18, - ['GeneralString'] = 0x1B, - } + if ( val._type and types[val._type] ) then + return bin.pack("CAA", types[val._type], len, val[1]) + 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 - return bin.pack("CAA", types[val._type], len, val[1]) - elseif ( val._type and 'number' == type(val._type) ) then - return bin.pack("CAA", val._type, len, val[1]) - end + -- Encodes a sequence using a custom type + -- @param encoder class containing an instance of a ASN1Encoder + -- @param seqtype number the sequence type to encode + -- @param seq string containing the sequence to encode + 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 - -- @param encoder class containing an instance of a ASN1Encoder - -- @param seqtype number the sequence type to encode - -- @param seq string containing the sequence to encode - encodeSequence = function(self, encoder, seqtype, seq) - return encoder:encode( { _type = seqtype, seq } ) - end, + for _, n in ipairs(names) do + princ = princ .. encoder:encode( { _type = 'GeneralString', n } ) + 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 = "" + princ = self:encodeSequence(encoder, 0x30, princ) + princ = self:encodeSequence(encoder, 0xa1, princ) + princ = encoder:encode( name_type ) .. princ - for _, n in ipairs(names) do - princ = princ .. encoder:encode( { _type = 'GeneralString', n } ) - end + -- not sure about how this works, but apparently it does + princ = bin.pack("H", "A003") .. princ + princ = self:encodeSequence(encoder,0x30, princ) - princ = self:encodeSequence(encoder, 0x30, princ) - princ = self:encodeSequence(encoder, 0xa1, princ) - princ = encoder:encode( name_type ) .. princ + return princ + end, - -- not sure about how this works, but apparently it does - princ = bin.pack("H", "A003") .. princ - princ = self:encodeSequence(encoder,0x30, princ) + -- Encodes the Kerberos AS-REQ request + -- @param realm string containing the Kerberos REALM + -- @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 - end, + assert(protocol == "tcp" or protocol == "udp", + "Protocol has to be either \"tcp\" or \"udp\"") - -- Encodes the Kerberos AS-REQ request - -- @param realm string containing the Kerberos REALM - -- @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) + local encoder = asn1.ASN1Encoder:new() + encoder:registerTagEncoders(KRB5.tagEncoder) - assert(protocol == "tcp" or protocol == "udp", - "Protocol has to be either \"tcp\" or \"udp\"") + local data = "" - local encoder = asn1.ASN1Encoder:new() - encoder:registerTagEncoders(KRB5.tagEncoder) + -- encode encryption types + 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 - for _,enctype in ipairs(KRB5.EncryptionTypes) do - for k, v in pairs( enctype ) do - data = data .. encoder:encode(v) - end - end + -- encode nonce + local nonce = 155874945 + data = self:encodeSequence(encoder, 0xA7, encoder:encode(nonce) ) .. data - data = self:encodeSequence(encoder, 0x30, data ) - data = self:encodeSequence(encoder, 0xA8, data ) + -- encode from/to + 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 nonce = 155874945 - data = self:encodeSequence(encoder, 0xA7, encoder:encode(nonce) ) .. data + local names = { "krbtgt", realm } + local sname = self:encodePrincipal( encoder, KRB5.NameTypes['NT-SRV-INST'], names ) + sname = self:encodeSequence(encoder, 0xA3, sname) + data = sname .. data - -- encode from/to - 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 + -- realm + data = self:encodeSequence(encoder, 0xA2, encoder:encode( { _type = 'GeneralString', realm })) .. data - local names = { "krbtgt", realm } - local sname = self:encodePrincipal( encoder, KRB5.NameTypes['NT-SRV-INST'], names ) - sname = self:encodeSequence(encoder, 0xA3, sname) - data = sname .. data + local cname = self:encodePrincipal(encoder, KRB5.NameTypes['NT-PRINCIPAL'], { user }) + cname = self:encodeSequence(encoder, 0xA1, cname) + data = cname .. data - -- realm - data = self:encodeSequence(encoder, 0xA2, encoder:encode( { _type = 'GeneralString', realm })) .. data + -- forwardable + local kdc_options = 0x40000000 + data = bin.pack(">I", kdc_options) .. data - local cname = self:encodePrincipal(encoder, KRB5.NameTypes['NT-PRINCIPAL'], { user }) - cname = self:encodeSequence(encoder, 0xA1, cname) - data = cname .. data + -- add padding + data = bin.pack("C", 0) .. data - -- forwardable - local kdc_options = 0x40000000 - data = bin.pack(">I", kdc_options) .. data + -- hmm, wonder what this is + data = bin.pack("H", "A0070305") .. 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 - data = bin.pack("C", 0) .. data + local pvno = 5 + data = self:encodeSequence(encoder, 0xA1, encoder:encode(pvno) ) .. data - -- hmm, wonder what this is - data = bin.pack("H", "A0070305") .. 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 + data = self:encodeSequence(encoder, 0x30, data) + data = self:encodeSequence(encoder, 0x6a, data) - local pvno = 5 - data = self:encodeSequence(encoder, 0xA1, encoder:encode(pvno) ) .. data + if ( protocol == "tcp" ) then + data = bin.pack(">I", #data) .. data + end - data = self:encodeSequence(encoder, 0x30, data) - data = self:encodeSequence(encoder, 0x6a, data) + return data + end, - if ( protocol == "tcp" ) then - data = bin.pack(">I", #data) .. data - end + -- Parses the result from the AS-REQ + -- @param data string containing the raw unparsed data + -- @return status boolean true on success, false on failure + -- @return msg table containing the fields type and + -- error_code if the type is an error. + parseResult = function(self, data) - return data - end, - - -- Parses the result from the AS-REQ - -- @param data string containing the raw unparsed data - -- @return status boolean true on success, false on failure - -- @return msg table containing the fields type and - -- error_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 = {} + 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 - return false, nil - end + if ( #result == 0 or #result[1] < 2 or #result[1][2] < 1 ) then + return false, nil + end - msg.type = result[1][2][1] + msg.type = result[1][2][1] - if ( msg.type == KRB5.MessageType['KRB-ERROR'] ) then - if ( #result[1] < 5 and #result[1][5] < 1 ) then - return false, nil - end + if ( msg.type == KRB5.MessageType['KRB-ERROR'] ) then + if ( #result[1] < 5 and #result[1][5] < 1 ) then + return false, nil + end - msg.error_code = result[1][5][1] - return true, msg - elseif ( msg.type == KRB5.MessageType['AS-REP'] ) then - return true, msg - end + msg.error_code = result[1][5][1] + return true, msg + elseif ( msg.type == KRB5.MessageType['AS-REP'] ) then + return true, msg + end - return false, nil - end, + return false, nil + end, } @@ -297,42 +297,42 @@ KRB5 = { -- @return state VALID or INVALID or error message if status was false local function checkUser( host, port, realm, user ) - local krb5 = KRB5:new() - local data = krb5:encodeASREQ(realm, user, port.protocol) - local socket = nmap.new_socket() - local status = socket:connect(host, port) + local krb5 = KRB5:new() + local data = krb5:encodeASREQ(realm, user, port.protocol) + local socket = nmap.new_socket() + local status = socket:connect(host, port) - if ( not(status) ) then - return false, "ERROR: Failed to connect to Kerberos service" - end + if ( not(status) ) then + return false, "ERROR: Failed to connect to Kerberos service" + end - socket:send(data) - status, data = socket:receive() + socket:send(data) + 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 - return false, "ERROR: Failed to receive result from Kerberos service" - end - socket:close() + if ( not(status) ) then + return false, "ERROR: Failed to receive result from Kerberos service" + end + socket:close() - local msg - status, msg = krb5:parseResult(data) + local msg + status, msg = krb5:parseResult(data) - if ( not(status) ) then - return false, "ERROR: Failed to parse the result returned from the Kerberos service" - end + if ( not(status) ) then + return false, "ERROR: Failed to parse the result returned from the Kerberos service" + end - if ( msg and msg.error_code ) then - if ( msg.error_code == KRB5.ErrorMessages['KRB5KDC_ERR_PREAUTH_REQUIRED'] ) then - return true, "VALID" - elseif ( msg.error_code == KRB5.ErrorMessages['KDC_ERR_WRONG_REALM'] ) then - return false, "Invalid Kerberos REALM" - end - elseif ( msg.type == KRB5.MessageType['AS-REP'] ) then - return true, "VALID" - end - return true, "INVALID" + if ( msg and msg.error_code ) then + if ( msg.error_code == KRB5.ErrorMessages['KRB5KDC_ERR_PREAUTH_REQUIRED'] ) then + return true, "VALID" + elseif ( msg.error_code == KRB5.ErrorMessages['KDC_ERR_WRONG_REALM'] ) then + return false, "Invalid Kerberos REALM" + end + elseif ( msg.type == KRB5.MessageType['AS-REP'] ) then + return true, "VALID" + end + return true, "INVALID" end -- Checks whether the Kerberos REALM exists or not @@ -341,7 +341,7 @@ end -- @param realm string containing the Kerberos REALM -- @return status boolean, true on success, false on failure local function isValidRealm( host, port, realm ) - return checkUser( host, port, realm, "nmap") + return checkUser( host, port, realm, "nmap") end -- 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 result table to which all discovered users are added local function checkUserThread( host, port, realm, user, result ) - local condvar = nmap.condvar(result) - local status, state = checkUser(host, port, realm, user) - if ( status and state == "VALID" ) then - table.insert(result, ("%s@%s"):format(user,realm)) - end - condvar "signal" + local condvar = nmap.condvar(result) + local status, state = checkUser(host, port, realm, user) + if ( status and state == "VALID" ) then + table.insert(result, ("%s@%s"):format(user,realm)) + end + condvar "signal" end action = function( host, port ) - local realm = stdnse.get_script_args("krb5-enum-users.realm") - local result = {} - local condvar = nmap.condvar(result) + local realm = stdnse.get_script_args("krb5-enum-users.realm") + local result = {} + local condvar = nmap.condvar(result) - -- did the user supply a realm - if ( not(realm) ) then - return "ERROR: No Kerberos REALM was supplied, aborting ..." - end + -- did the user supply a realm + if ( not(realm) ) then + return "ERROR: No Kerberos REALM was supplied, aborting ..." + end - -- does the realm appear to exist - if ( not(isValidRealm(host, port, realm)) ) then - return "ERROR: Invalid Kerberos REALM, aborting ..." - end + -- does the realm appear to exist + if ( not(isValidRealm(host, port, realm)) ) then + return "ERROR: Invalid Kerberos REALM, aborting ..." + end - -- load our user database from unpwdb - local status, usernames = unpwdb.usernames() - if( not(status) ) then return "ERROR: Failed to load unpwdb usernames" end + -- load our user database from unpwdb + local status, usernames = unpwdb.usernames() + if( not(status) ) then return "ERROR: Failed to load unpwdb usernames" end - -- start as many threads as there are names in the list - local threads = {} - for user in usernames do - local co = stdnse.new_thread( checkUserThread, host, port, realm, user, result ) - threads[co] = true - end + -- start as many threads as there are names in the list + local threads = {} + for user in usernames do + local co = stdnse.new_thread( checkUserThread, host, port, realm, user, result ) + threads[co] = true + end - -- wait for all threads to finish up - repeat - for t in pairs(threads) do - if ( coroutine.status(t) == "dead" ) then threads[t] = nil end - end - if ( next(threads) ) then - condvar "wait" - end - until( next(threads) == nil ) + -- wait for all threads to finish up + repeat + for t in pairs(threads) do + if ( coroutine.status(t) == "dead" ) then threads[t] = nil end + end + if ( next(threads) ) then + condvar "wait" + end + until( next(threads) == nil ) - if ( #result > 0 ) then - result = { name = "Discovered Kerberos principals", result } - end - return stdnse.format_output(true, result) + if ( #result > 0 ) then + result = { name = "Discovered Kerberos principals", result } + end + return stdnse.format_output(true, result) end diff --git a/scripts/ldap-brute.nse b/scripts/ldap-brute.nse index 178f477f4..a7dc9a482 100644 --- a/scripts/ldap-brute.nse +++ b/scripts/ldap-brute.nse @@ -98,25 +98,25 @@ portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"}) -- @return string containing a valid naming context function get_naming_context( socket ) - local req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = { "defaultNamingContext", "namingContexts" } } - local status, searchResEntries = ldap.searchRequest( socket, req ) + local req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = { "defaultNamingContext", "namingContexts" } } + local status, searchResEntries = ldap.searchRequest( socket, req ) - if not status then - return nil - end + if not status then + return nil + end - local contexts = ldap.extractAttribute( searchResEntries, "defaultNamingContext" ) + local contexts = ldap.extractAttribute( searchResEntries, "defaultNamingContext" ) - -- OpenLDAP does not have a defaultNamingContext - if not contexts then - contexts = ldap.extractAttribute( searchResEntries, "namingContexts" ) - end + -- OpenLDAP does not have a defaultNamingContext + if not contexts then + contexts = ldap.extractAttribute( searchResEntries, "namingContexts" ) + end - if contexts and #contexts > 0 then - return contexts[1] - end + if contexts and #contexts > 0 then + return contexts[1] + end - return nil + return nil end --- 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 -- @return true if credentials are valid and search was a success, false if not. function is_valid_credential( socket, context ) - local req = { baseObject = context, scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = nil } - local status, searchResEntries = ldap.searchRequest( socket, req ) + local req = { baseObject = context, scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = nil } + local status, searchResEntries = ldap.searchRequest( socket, req ) - return status + return status end action = function( host, port ) - local result, response, status, err, context, output, valid_accounts = {}, nil, nil, nil, nil, nil, {} - local usernames, passwords, username, password, fq_username - local user_cnt, invalid_account_cnt, tot_tries = 0, 0, 0 + local result, response, status, err, context, output, valid_accounts = {}, nil, nil, nil, nil, nil, {} + local usernames, passwords, username, password, fq_username + 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 socket, _, opt = comm.tryssl( host, port, ldap_anonymous_bind, nil ) + 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 base_dn = stdnse.get_script_args('ldap.base') - local upn_suffix = stdnse.get_script_args('ldap.upnsuffix') + local base_dn = stdnse.get_script_args('ldap.base') + 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 - if ( stdnse.get_script_args('ldap.saveprefix') ) then - output_prefix = stdnse.get_script_args('ldap.saveprefix') - elseif ( output_type ) then - output_prefix = "ldap-brute" - end + local output_prefix = nil + if ( stdnse.get_script_args('ldap.saveprefix') ) then + output_prefix = stdnse.get_script_args('ldap.saveprefix') + elseif ( output_type ) then + output_prefix = "ldap-brute" + end - local credTable = creds.Credentials:new(SCRIPT_NAME, host, port) + local credTable = creds.Credentials:new(SCRIPT_NAME, host, port) - if not socket then - return - end + if not socket then + return + end - -- We close and re-open the socket so that the anonymous bind does not distract us - socket:close() - -- set a reasonable timeout value - socket:set_timeout(5000) - status = socket:connect(host, port, opt) - if not status then - return - end + -- We close and re-open the socket so that the anonymous bind does not distract us + socket:close() + -- set a reasonable timeout value + socket:set_timeout(5000) + status = socket:connect(host, port, opt) + if not status then + return + end - context = get_naming_context(socket) + context = get_naming_context(socket) - if not context then - stdnse.print_debug("Failed to retrieve namingContext") - socket:close() - return - end + if not context then + stdnse.print_debug("Failed to retrieve namingContext") + socket:close() + return + end - status, usernames = unpwdb.usernames() - if not status then - return - end + status, usernames = unpwdb.usernames() + if not status then + return + end - status, passwords = unpwdb.passwords() - if not status then - return - end + status, passwords = unpwdb.passwords() + if not status then + return + end - for username in usernames do - -- if a base DN was set append our username (CN) to the base - if base_dn then - fq_username = ("cn=%s,%s"):format(username, base_dn) - elseif upn_suffix then - fq_username = ("%s@%s"):format(username, upn_suffix) - else - fq_username = username - end + for username in usernames do + -- if a base DN was set append our username (CN) to the base + if base_dn then + fq_username = ("cn=%s,%s"):format(username, base_dn) + elseif upn_suffix then + fq_username = ("%s@%s"):format(username, upn_suffix) + else + fq_username = username + end - user_cnt = user_cnt + 1 - for password in passwords do - tot_tries = tot_tries + 1 + user_cnt = user_cnt + 1 + for password in passwords do + tot_tries = tot_tries + 1 - -- handle special case where we want to guess the username as password - if password == "%username%" then - password = username - end + -- handle special case where we want to guess the username as password + if password == "%username%" then + password = username + end - stdnse.print_debug( "Trying %s/%s ...", fq_username, password ) - status, response = ldap.bindRequest( socket, { version=3, ['username']=fq_username, ['password']=password} ) + stdnse.print_debug( "Trying %s/%s ...", fq_username, password ) + status, response = ldap.bindRequest( socket, { version=3, ['username']=fq_username, ['password']=password} ) - -- if the DN (username) does not exist, break loop - if not status and response:match("invalid DN") then - stdnse.print_debug( "%s returned: \"Invalid DN\"", fq_username ) - invalid_account_cnt = invalid_account_cnt + 1 - break - end + -- if the DN (username) does not exist, break loop + if not status and response:match("invalid DN") then + stdnse.print_debug( "%s returned: \"Invalid DN\"", fq_username ) + invalid_account_cnt = invalid_account_cnt + 1 + break + end - -- Is AD telling us the account does not exist? - if not status and response:match("AcceptSecurityContext error, data 525,") then - invalid_account_cnt = invalid_account_cnt + 1 - break - end + -- Is AD telling us the account does not exist? + if not status and response:match("AcceptSecurityContext error, data 525,") then + invalid_account_cnt = invalid_account_cnt + 1 + break + end - -- Account Locked Out - if not status and response:match("AcceptSecurityContext error, data 775,") then - 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 )) - credTable:add(fq_username,password, creds.State.LOCKED_VALID) - break - end + -- Account Locked Out + if not status and response:match("AcceptSecurityContext error, data 775,") then + 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 )) + credTable:add(fq_username,password, creds.State.LOCKED_VALID) + break + end - -- Login correct, account disabled - 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 "" ) ) - stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account disabled", fq_username, password:len()>0 and password or "" )) - credTable:add(fq_username,password, creds.State.DISABLED_VALID) - break - end + -- Login correct, account disabled + 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 "" ) ) + stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account disabled", fq_username, password:len()>0 and password or "" )) + credTable:add(fq_username,password, creds.State.DISABLED_VALID) + break + end - -- Login correct, user must change password - 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 "" ) ) - 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 "" )) - credTable:add(fq_username,password, creds.State.CHANGEPW) - break - end + -- Login correct, user must change password + 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 "" ) ) + 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 "" )) + credTable:add(fq_username,password, creds.State.CHANGEPW) + break + end - -- Login correct, user account expired - 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 "" ) ) - stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account expired", fq_username, password:len()>0 and password or "" )) - credTable:add(fq_username,password, creds.State.EXPIRED) - break - end + -- Login correct, user account expired + 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 "" ) ) + stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials, account expired", fq_username, password:len()>0 and password or "" )) + credTable:add(fq_username,password, creds.State.EXPIRED) + break + end - -- Login correct, user account logon time restricted - 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 "" ) ) - 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 "" )) - credTable:add(fq_username,password, creds.State.TIME_RESTRICTED) - break - end + -- Login correct, user account logon time restricted + 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 "" ) ) + 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 "" )) + credTable:add(fq_username,password, creds.State.TIME_RESTRICTED) + break + end - -- Login correct, user account can only log in from certain workstations - 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 "" ) ) - 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 "" )) - credTable:add(fq_username,password, creds.State.HOST_RESTRICTED) - break - end + -- Login correct, user account can only log in from certain workstations + 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 "" ) ) + 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 "" )) + credTable:add(fq_username,password, creds.State.HOST_RESTRICTED) + break + end - --Login, correct - if status then - status = is_valid_credential( socket, context ) - if status then - table.insert( valid_accounts, string.format("%s:%s => Valid credentials", fq_username, password:len()>0 and password or "" ) ) - stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials", fq_username, password:len()>0 and password or "" ) ) - -- Add credentials for other ldap scripts to use - if nmap.registry.ldapaccounts == nil then - nmap.registry.ldapaccounts = {} - end - nmap.registry.ldapaccounts[fq_username]=password - credTable:add(fq_username,password, creds.State.VALID) + --Login, correct + if status then + status = is_valid_credential( socket, context ) + if status then + table.insert( valid_accounts, string.format("%s:%s => Valid credentials", fq_username, password:len()>0 and password or "" ) ) + stdnse.print_verbose(2, string.format(" ldap-brute: %s:%s => Valid credentials", fq_username, password:len()>0 and password or "" ) ) + -- Add credentials for other ldap scripts to use + if nmap.registry.ldapaccounts == nil then + nmap.registry.ldapaccounts = {} + end + nmap.registry.ldapaccounts[fq_username]=password + credTable:add(fq_username,password, creds.State.VALID) - break - end - end - end - passwords("reset") - end + break + end + end + end + passwords("reset") + 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 - return "WARNING: All usernames were invalid. Invalid LDAP base?" - end + if ( invalid_account_cnt == user_cnt and base_dn ~= nil ) then + return "WARNING: All usernames were invalid. Invalid LDAP base?" + end - if output_prefix then - local output_file = output_prefix .. "_" .. host.ip .. "_" .. port.number - status, err = credTable:saveToFile(output_file,output_type) - if not status then - stdnse.print_debug(err) - end - end + if output_prefix then + local output_file = output_prefix .. "_" .. host.ip .. "_" .. port.number + status, err = credTable:saveToFile(output_file,output_type) + if not status then + stdnse.print_debug(err) + end + end - if err then - output = stdnse.format_output(true, valid_accounts ) .. stdnse.format_output(true, err) or stdnse.format_output(true, err) - else - output = stdnse.format_output(true, valid_accounts) or "" - end + if err then + output = stdnse.format_output(true, valid_accounts ) .. stdnse.format_output(true, err) or stdnse.format_output(true, err) + else + output = stdnse.format_output(true, valid_accounts) or "" + end - return output + return output end diff --git a/scripts/ldap-search.nse b/scripts/ldap-search.nse index 47fb93dbc..5ab5b3bd1 100644 --- a/scripts/ldap-search.nse +++ b/scripts/ldap-search.nse @@ -103,198 +103,198 @@ portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"}) function action(host,port) - local status - local socket, opt - local args = nmap.registry.args - local username = stdnse.get_script_args('ldap.username') - local password = stdnse.get_script_args('ldap.password') - local qfilter = stdnse.get_script_args('ldap.qfilter') - local searchAttrib = stdnse.get_script_args('ldap.searchattrib') - local searchValue = stdnse.get_script_args('ldap.searchvalue') - local base = stdnse.get_script_args('ldap.base') - local attribs = stdnse.get_script_args('ldap.attrib') - local saveFile = stdnse.get_script_args('ldap.savesearch') - local accounts - local objCount = 0 - local maxObjects = stdnse.get_script_args('ldap.maxobjects') and tonumber(stdnse.get_script_args('ldap.maxobjects')) or 20 + local status + local socket, opt + local args = nmap.registry.args + local username = stdnse.get_script_args('ldap.username') + local password = stdnse.get_script_args('ldap.password') + local qfilter = stdnse.get_script_args('ldap.qfilter') + local searchAttrib = stdnse.get_script_args('ldap.searchattrib') + local searchValue = stdnse.get_script_args('ldap.searchvalue') + local base = stdnse.get_script_args('ldap.base') + local attribs = stdnse.get_script_args('ldap.attrib') + local saveFile = stdnse.get_script_args('ldap.savesearch') + local accounts + local objCount = 0 + 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 - -- 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 _ - socket, _, opt = comm.tryssl( host, port, ldap_anonymous_bind, nil ) + -- 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 + 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 ) - if not socket then - return - end + if not socket then + return + end - -- Check if ldap-brute stored us some credentials - if ( not(username) and nmap.registry.ldapaccounts~=nil ) then - accounts = nmap.registry.ldapaccounts - end + -- Check if ldap-brute stored us some credentials + if ( not(username) and nmap.registry.ldapaccounts~=nil ) then + accounts = nmap.registry.ldapaccounts + end - -- We close and re-open the socket so that the anonymous bind does not distract us - socket:close() - status = socket:connect(host, port, opt) - socket:set_timeout(10000) + -- We close and re-open the socket so that the anonymous bind does not distract us + socket:close() + status = socket:connect(host, port, opt) + socket:set_timeout(10000) - local req - local searchResEntries - local contexts = {} - local result = {} - local filter + local req + local searchResEntries + local contexts = {} + local result = {} + local filter - if base == nil then - req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = { "defaultNamingContext", "namingContexts" } } - status, searchResEntries = ldap.searchRequest( socket, req ) + if base == nil then + req = { baseObject = "", scope = ldap.SCOPE.base, derefPolicy = ldap.DEREFPOLICY.default, attributes = { "defaultNamingContext", "namingContexts" } } + status, searchResEntries = ldap.searchRequest( socket, req ) - if not status then - socket:close() - return - end + if not status then + socket:close() + return + end - contexts = ldap.extractAttribute( searchResEntries, "defaultNamingContext" ) + contexts = ldap.extractAttribute( searchResEntries, "defaultNamingContext" ) - -- OpenLDAP does not have a defaultNamingContext - if not contexts then - contexts = ldap.extractAttribute( searchResEntries, "namingContexts" ) - end - else - table.insert(contexts, base) - end + -- OpenLDAP does not have a defaultNamingContext + if not contexts then + contexts = ldap.extractAttribute( searchResEntries, "namingContexts" ) + end + else + table.insert(contexts, base) + end - if ( not(contexts) or #contexts == 0 ) then - stdnse.print_debug( "Failed to retrieve namingContexts" ) - contexts = {""} - end + if ( not(contexts) or #contexts == 0 ) then + stdnse.print_debug( "Failed to retrieve namingContexts" ) + contexts = {""} + end - -- perform a bind only if we have valid credentials - if ( username ) then - local bindParam = { version=3, ['username']=username, ['password']=password} - local status, errmsg = ldap.bindRequest( socket, bindParam ) + -- perform a bind only if we have valid credentials + if ( username ) then + local bindParam = { version=3, ['username']=username, ['password']=password} + local status, errmsg = ldap.bindRequest( socket, bindParam ) - if not status then - stdnse.print_debug( string.format("ldap-search failed to bind: %s", errmsg) ) - return " \n ERROR: Authentication failed" - end - -- or if ldap-brute found us something - elseif ( accounts ) then - for username, password in pairs(accounts) do - local bindParam = { version=3, ['username']=username, ['password']=password} - local status, errmsg = ldap.bindRequest( socket, bindParam ) + if not status then + stdnse.print_debug( string.format("ldap-search failed to bind: %s", errmsg) ) + return " \n ERROR: Authentication failed" + end + -- or if ldap-brute found us something + elseif ( accounts ) then + for username, password in pairs(accounts) do + local bindParam = { version=3, ['username']=username, ['password']=password} + local status, errmsg = ldap.bindRequest( socket, bindParam ) - if status then - break - end - end - end + if status then + break + end + end + end - if qfilter == "users" then - filter = { op=ldap.FILTER._or, val= - { - { op=ldap.FILTER.equalityMatch, obj='objectClass', val='user' }, - { op=ldap.FILTER.equalityMatch, obj='objectClass', val='posixAccount' }, - { op=ldap.FILTER.equalityMatch, obj='objectClass', val='person' } - } - } - elseif qfilter == "computers" or qfilter == "computer" then - filter = { op=ldap.FILTER.equalityMatch, obj='objectClass', val='computer' } + if qfilter == "users" then + filter = { op=ldap.FILTER._or, val= + { + { op=ldap.FILTER.equalityMatch, obj='objectClass', val='user' }, + { op=ldap.FILTER.equalityMatch, obj='objectClass', val='posixAccount' }, + { op=ldap.FILTER.equalityMatch, obj='objectClass', val='person' } + } + } + elseif qfilter == "computers" or qfilter == "computer" then + filter = { op=ldap.FILTER.equalityMatch, obj='objectClass', val='computer' } - elseif qfilter == "ad_dcs" then - filter = { op=ldap.FILTER.extensibleMatch, obj='userAccountControl', val='1.2.840.113556.1.4.803:=8192' } + elseif qfilter == "ad_dcs" then + filter = { op=ldap.FILTER.extensibleMatch, obj='userAccountControl', val='1.2.840.113556.1.4.803:=8192' } - elseif qfilter == "custom" then - if searchAttrib == nil or searchValue == nil then - return "\n\nERROR: Please specify both ldap.searchAttrib and ldap.searchValue using using the custom qfilter." - end - if string.find(searchValue, '*') == nil then - filter = { op=ldap.FILTER.equalityMatch, obj=searchAttrib, val=searchValue } - else - filter = { op=ldap.FILTER.substrings, obj=searchAttrib, val=searchValue } - end + elseif qfilter == "custom" then + if searchAttrib == nil or searchValue == nil then + return "\n\nERROR: Please specify both ldap.searchAttrib and ldap.searchValue using using the custom qfilter." + end + if string.find(searchValue, '*') == nil then + filter = { op=ldap.FILTER.equalityMatch, obj=searchAttrib, val=searchValue } + else + filter = { op=ldap.FILTER.substrings, obj=searchAttrib, val=searchValue } + end - elseif qfilter == "all" or qfilter == nil then - filter = nil -- { op=ldap.FILTER} - else - return " \n\nERROR: Unsupported Quick Filter: " .. qfilter - end + elseif qfilter == "all" or qfilter == nil then + filter = nil -- { op=ldap.FILTER} + else + return " \n\nERROR: Unsupported Quick Filter: " .. qfilter + end - if type(attribs) == 'string' then - local tmp = attribs - attribs = {} - table.insert(attribs, tmp) - end + if type(attribs) == 'string' then + local tmp = attribs + attribs = {} + table.insert(attribs, tmp) + end - for _, context in ipairs(contexts) do + for _, context in ipairs(contexts) do - req = { - baseObject = context, - scope = ldap.SCOPE.sub, - derefPolicy = ldap.DEREFPOLICY.default, - filter = filter, - attributes = attribs, - ['maxObjects'] = maxObjects } - status, searchResEntries = ldap.searchRequest( socket, req ) + req = { + baseObject = context, + scope = ldap.SCOPE.sub, + derefPolicy = ldap.DEREFPOLICY.default, + filter = filter, + attributes = attribs, + ['maxObjects'] = maxObjects } + status, searchResEntries = ldap.searchRequest( socket, req ) - if not status then - if ( searchResEntries:match("DSID[-]0C090627") and not(username) ) then - return "ERROR: Failed to bind as the anonymous user" - else - stdnse.print_debug( string.format( "ldap.searchRequest returned: %s", searchResEntries ) ) - return - end - end + if not status then + if ( searchResEntries:match("DSID[-]0C090627") and not(username) ) then + return "ERROR: Failed to bind as the anonymous user" + else + stdnse.print_debug( string.format( "ldap.searchRequest returned: %s", searchResEntries ) ) + return + end + end - local result_part = ldap.searchResultToTable( searchResEntries ) + local result_part = ldap.searchResultToTable( searchResEntries ) - if saveFile then - local output_file = saveFile .. "_" .. host.ip .. "_" .. port.number .. ".csv" - local save_status, save_err = ldap.searchResultToFile(searchResEntries,output_file) - if not save_status then - stdnse.print_debug(save_err) - end - end + if saveFile then + local output_file = saveFile .. "_" .. host.ip .. "_" .. port.number .. ".csv" + local save_status, save_err = ldap.searchResultToFile(searchResEntries,output_file) + if not save_status then + stdnse.print_debug(save_err) + end + end - objCount = objCount + (result_part and #result_part or 0) - result_part.name = "" + objCount = objCount + (result_part and #result_part or 0) + result_part.name = "" - if ( context ) then - result_part.name = ("Context: %s"):format(#context > 0 and context or "") - end - if ( qfilter ) then - result_part.name = result_part.name .. ("; QFilter: %s"):format(qfilter) - end - if ( attribs ) then - result_part.name = result_part.name .. ("; Attributes: %s"):format(stdnse.strjoin(",", attribs)) - end + if ( context ) then + result_part.name = ("Context: %s"):format(#context > 0 and context or "") + end + if ( qfilter ) then + result_part.name = result_part.name .. ("; QFilter: %s"):format(qfilter) + end + if ( attribs ) then + result_part.name = result_part.name .. ("; Attributes: %s"):format(stdnse.strjoin(",", attribs)) + end - table.insert( result, result_part ) + table.insert( result, result_part ) - -- catch any softerrors - if searchResEntries.resultCode ~= 0 then - local output = stdnse.format_output(true, result ) - output = output .. string.format("\n\n\n=========== %s ===========", searchResEntries.errorMessage ) + -- catch any softerrors + if searchResEntries.resultCode ~= 0 then + local output = stdnse.format_output(true, result ) + output = output .. string.format("\n\n\n=========== %s ===========", searchResEntries.errorMessage ) - return output - end + return output + end - end + end - -- perform a unbind only if we have valid credentials - if ( username ) then - status = ldap.unbindRequest( socket ) - end + -- perform a unbind only if we have valid credentials + if ( username ) then + status = ldap.unbindRequest( socket ) + end - socket:close() + socket:close() - -- if taken a way and ldap returns a single result, it ain't shown.... - --result.name = "LDAP Results" + -- if taken a way and ldap returns a single result, it ain't shown.... + --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 - output = output .. ("\n\nResult limited to %d objects (see ldap.maxobjects)"):format(maxObjects) - end + if ( maxObjects ~= -1 and objCount == maxObjects ) then + output = output .. ("\n\nResult limited to %d objects (see ldap.maxobjects)"):format(maxObjects) + end - return output + return output end diff --git a/scripts/lltd-discovery.nse b/scripts/lltd-discovery.nse index b9047f6cc..704b4f31f 100644 --- a/scripts/lltd-discovery.nse +++ b/scripts/lltd-discovery.nse @@ -45,296 +45,296 @@ categories = {"broadcast","discovery","safe"} prerule = function() - if not nmap.is_privileged() then - nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} - if not nmap.registry[SCRIPT_NAME].rootfail then - stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) - end - nmap.registry[SCRIPT_NAME].rootfail = true - return nil - end + if not nmap.is_privileged() then + nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} + if not nmap.registry[SCRIPT_NAME].rootfail then + stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) + end + nmap.registry[SCRIPT_NAME].rootfail = true + return nil + end - if nmap.address_family() ~= 'inet' then - stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) - return false - end + if nmap.address_family() ~= 'inet' then + stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) + return false + end - return true + return true end --- Converts a 6 byte string into the familiar MAC address formatting -- @param mac string containing the MAC address -- @return formatted string suitable for printing local function get_mac_addr( mac ) - local catch = function() return end - local try = nmap.new_try(catch) - local mac_prefixes = try(datafiles.parse_mac_prefixes()) + local catch = function() return end + local try = nmap.new_try(catch) + local mac_prefixes = try(datafiles.parse_mac_prefixes()) - if mac:len() ~= 6 then - return "Unknown" - else - 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" - return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf ) - end + if mac:len() ~= 6 then + return "Unknown" + else + 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" + return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf ) + end end --- Gets a raw ethernet buffer with LLTD information and returns the responding host's IP and MAC local parseHello = function(data) --- HelloMsg = [ --- ethernet_hdr = [mac_dst(6), mac_src(6), protocol(2)], --- lltd_demultiplex_hdr = [version(1), type_of_service(1), reserved(1), function(1)], --- 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) ] ---] + -- HelloMsg = [ + -- ethernet_hdr = [mac_dst(6), mac_src(6), protocol(2)], + -- lltd_demultiplex_hdr = [version(1), type_of_service(1), reserved(1), function(1)], + -- 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) ] + --] ---HelloStruct = { --- mac_src, --- sequence_number, --- generation_number, --- tlv_list(dict) ---} - local types = {"Host ID", "Characteristics", "Physical Medium", "Wireless Mode", "802.11 BSSID", - "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", - "Support Information", "Friendly Name", "Device UUID", "Hardware ID", "QoS Characteristics", - "802.11 Physical Medium", "AP Association Table", "Detailed Icon Image", "Sees-List Working Set", - "Component Table", "Repeater AP Lineage", "Repeater AP Table"} - local mac = nil - local ipv4 = nil - local ipv6 = nil - local hostname = nil + --HelloStruct = { + -- mac_src, + -- sequence_number, + -- generation_number, + -- tlv_list(dict) + --} + local types = {"Host ID", "Characteristics", "Physical Medium", "Wireless Mode", "802.11 BSSID", + "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", + "Support Information", "Friendly Name", "Device UUID", "Hardware ID", "QoS Characteristics", + "802.11 Physical Medium", "AP Association Table", "Detailed Icon Image", "Sees-List Working Set", + "Component Table", "Repeater AP Lineage", "Repeater AP Table"} + local mac = nil + local ipv4 = nil + local ipv6 = nil + local hostname = nil - local pos = 1 - pos = pos + 6 - local mac_src = data:sub(pos,pos+5) + local pos = 1 + pos = pos + 6 + local mac_src = data:sub(pos,pos+5) - pos = pos + 24 - local seq_no = data:sub(pos,pos+1) + pos = pos + 24 + local seq_no = data:sub(pos,pos+1) - pos = pos + 2 - local generation_no = data:sub(pos,pos+1) + pos = pos + 2 + local generation_no = data:sub(pos,pos+1) - pos = pos + 14 - local tlv = data:sub(pos) + pos = pos + 14 + local tlv = data:sub(pos) - local tlv_list = {} - local p = 1 - while p < #tlv do - local t = tlv:byte(p) - if t == 0x00 then - break - else - p = p + 1 - local l = tlv:byte(p) + local tlv_list = {} + local p = 1 + while p < #tlv do + local t = tlv:byte(p) + if t == 0x00 then + break + else + p = p + 1 + local l = tlv:byte(p) - p = p + 1 - local v = tlv:sub(p,p+l) + p = p + 1 + local v = tlv:sub(p,p+l) - if t == 0x01 then - -- Host ID (MAC Address) - mac = get_mac_addr(v:sub(1,6)) - elseif t == 0x08 then - ipv6 = string.format( - "%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(5), v:byte(6), v:byte(7), v:byte(8), - v:byte(9), v:byte(10), v:byte(11), v:byte(12), - v:byte(13), v:byte(14), v:byte(15), v:byte(16)) - elseif t == 0x07 then - -- IPv4 address - ipv4 = string.format("%d.%d.%d.%d",v:byte(1),v:byte(2),v:byte(3),v:byte(4)), mac + if t == 0x01 then + -- Host ID (MAC Address) + mac = get_mac_addr(v:sub(1,6)) + elseif t == 0x08 then + ipv6 = string.format( + "%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(5), v:byte(6), v:byte(7), v:byte(8), + v:byte(9), v:byte(10), v:byte(11), v:byte(12), + v:byte(13), v:byte(14), v:byte(15), v:byte(16)) + elseif t == 0x07 then + -- IPv4 address + ipv4 = string.format("%d.%d.%d.%d",v:byte(1),v:byte(2),v:byte(3),v:byte(4)), mac - -- Machine Name (Hostname) - elseif t == 0x0f then - hostname = '' - -- Hostname is returned in unicode, but Lua doesn't support that, - -- so we skip 00 values. - for i=1, #v-1, 2 do - hostname = hostname .. string.char(v:byte(i)) - end - end + -- Machine Name (Hostname) + elseif t == 0x0f then + hostname = '' + -- Hostname is returned in unicode, but Lua doesn't support that, + -- so we skip 00 values. + for i=1, #v-1, 2 do + hostname = hostname .. string.char(v:byte(i)) + end + end - p = p + l + p = p + l - if ipv4 and ipv6 and mac and hostname then - break - end - end - end + if ipv4 and ipv6 and mac and hostname then + break + end + end + end - return ipv4, mac, ipv6, hostname + return ipv4, mac, ipv6, hostname end --- Creates an LLTD Quick Discovery packet with the source MAC address -- @param mac_src - six byte long binary string 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 ] - local mac_dst = "FF FF FF FF FF FF" -- broadcast - local protocol = "88 d9" -- LLTD protocol number + -- set up ethernet header = [ mac_dst, mac_src, protocol ] + local mac_dst = "FF FF FF FF FF FF" -- broadcast + 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 ] - local lltd_version = "01" -- Fixed Value - local lltd_type_of_service = "01" -- Type Of Service = Quick Discovery(0x01) - local lltd_reserved = "00" -- Fixed value - local lltd_function = "00" -- Function = QuickDiscovery->Discover (0x00) + -- set up LLTD demultiplex header = [ version, type_of_service, reserved, function ] + local lltd_version = "01" -- Fixed Value + local lltd_type_of_service = "01" -- Type Of Service = Quick Discovery(0x01) + local lltd_reserved = "00" -- Fixed value + 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) ] - local lltd_seq_num = openssl.rand_bytes(2) + -- set up LLTD base header = [ mac_dst, mac_src, seq_num(xid) ] + 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 ] - local generation_number = openssl.rand_bytes(2) - local number_of_stations = "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 " + -- set up LLTD Upper Level Header = [ generation_number, number_of_stations, station_list ] + local generation_number = openssl.rand_bytes(2) + local number_of_stations = "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 " - 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 - return bin.pack("AAAA", ethernet_hdr, demultiplex_hdr, base_hdr, discover_up_lev_hdr) + -- put them all together and return + return bin.pack("AAAA", ethernet_hdr, demultiplex_hdr, base_hdr, discover_up_lev_hdr) end --- Runs a thread which discovers LLTD Responders on a certain interface local LLTDDiscover = function(if_table, lltd_responders, timeout) - local timeout_s = 3 - local condvar = nmap.condvar(lltd_responders) - local pcap = nmap.new_socket() - pcap:set_timeout(5000) + local timeout_s = 3 + local condvar = nmap.condvar(lltd_responders) + local pcap = nmap.new_socket() + pcap:set_timeout(5000) - local dnet = nmap.new_dnet() - local try = nmap.new_try(function() dnet:ethernet_close() pcap:close() end) + local dnet = nmap.new_dnet() + local try = nmap.new_try(function() dnet:ethernet_close() pcap:close() end) - pcap:pcap_open(if_table.device, 256, false, "") - try(dnet:ethernet_open(if_table.device)) + pcap:pcap_open(if_table.device, 256, false, "") + try(dnet:ethernet_open(if_table.device)) - local packet = QuickDiscoveryPacket(if_table.mac) - try( dnet:ethernet_send(packet) ) - stdnse.sleep(0.5) - try( dnet:ethernet_send(packet) ) + local packet = QuickDiscoveryPacket(if_table.mac) + try( dnet:ethernet_send(packet) ) + stdnse.sleep(0.5) + try( dnet:ethernet_send(packet) ) - local start = os.time() - local start_s = os.time() - while true do - local status, plen, l2, l3, _ = pcap:pcap_receive() - if status then - local packet = l2..l3 - if stdnse.tohex(packet:sub(13,14)) == "88d9" then - start_s = os.time() + local start = os.time() + local start_s = os.time() + while true do + local status, plen, l2, l3, _ = pcap:pcap_receive() + if status then + local packet = l2..l3 + if stdnse.tohex(packet:sub(13,14)) == "88d9" then + start_s = os.time() - local ipv4, mac, ipv6, hostname = parseHello(packet) + local ipv4, mac, ipv6, hostname = parseHello(packet) - if ipv4 then - if not lltd_responders[ipv4] then - lltd_responders[ipv4] = {} - lltd_responders[ipv4].hostname = hostname - lltd_responders[ipv4].mac = mac - lltd_responders[ipv4].ipv6 = ipv6 - end - end - else - if os.time() - start_s > timeout_s then - break - end - end - else - break - end + if ipv4 then + if not lltd_responders[ipv4] then + lltd_responders[ipv4] = {} + lltd_responders[ipv4].hostname = hostname + lltd_responders[ipv4].mac = mac + lltd_responders[ipv4].ipv6 = ipv6 + end + end + else + if os.time() - start_s > timeout_s then + break + end + end + else + break + end - if os.time() - start > timeout then - break - end - end - dnet:ethernet_close() - pcap:close() - condvar("signal") + if os.time() - start > timeout then + break + end + end + dnet:ethernet_close() + pcap:close() + condvar("signal") end action = function() - local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..".timeout")) - timeout = timeout or 30 + local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..".timeout")) + timeout = timeout or 30 - --get interface script-args, if any - local interface_arg = stdnse.get_script_args(SCRIPT_NAME .. ".interface") - local interface_opt = nmap.get_interface() + --get interface script-args, if any + local interface_arg = stdnse.get_script_args(SCRIPT_NAME .. ".interface") + local interface_opt = nmap.get_interface() - -- interfaces list (decide which interfaces to broadcast on) - local interfaces ={} - if interface_opt or interface_arg then - -- single interface defined - local interface = interface_opt or interface_arg - local if_table = nmap.get_interface_info(interface) - 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.") - return false - end - table.insert(interfaces, if_table) - else - local tmp_ifaces = nmap.list_interfaces() - for _, if_table in ipairs(tmp_ifaces) do - if if_table.address and - if_table.link=="ethernet" and - if_table.address:match("%d+%.%d+%.%d+%.%d+") then + -- interfaces list (decide which interfaces to broadcast on) + local interfaces ={} + if interface_opt or interface_arg then + -- single interface defined + local interface = interface_opt or interface_arg + local if_table = nmap.get_interface_info(interface) + 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.") + return false + end + table.insert(interfaces, if_table) + else + local tmp_ifaces = nmap.list_interfaces() + for _, if_table in ipairs(tmp_ifaces) do + if if_table.address and + if_table.link=="ethernet" and + if_table.address:match("%d+%.%d+%.%d+%.%d+") then - table.insert(interfaces, if_table) - end - end - end + table.insert(interfaces, if_table) + end + end + end - if #interfaces == 0 then - stdnse.print_debug("No interfaces found.") - return - end + if #interfaces == 0 then + stdnse.print_debug("No interfaces found.") + return + end - local lltd_responders={} - local threads ={} - local condvar = nmap.condvar(lltd_responders) + local lltd_responders={} + local threads ={} + local condvar = nmap.condvar(lltd_responders) - -- party time - for _, if_table in ipairs(interfaces) do - -- create a thread for each interface - local co = stdnse.new_thread(LLTDDiscover, if_table, lltd_responders, timeout) - threads[co]=true - end + -- party time + for _, if_table in ipairs(interfaces) do + -- create a thread for each interface + local co = stdnse.new_thread(LLTDDiscover, if_table, lltd_responders, timeout) + threads[co]=true + end - repeat - for thread in pairs(threads) do - if coroutine.status(thread) == "dead" then threads[thread] = nil end - end - if ( next(threads) ) then - condvar "wait" - end - until next(threads) == nil + repeat + for thread in pairs(threads) do + if coroutine.status(thread) == "dead" then threads[thread] = nil end + end + if ( next(threads) ) then + condvar "wait" + end + until next(threads) == nil - -- generate output - local output = {} - for ip_addr, info in pairs(lltd_responders) do - if target.ALLOW_NEW_TARGETS then target.add(ip_addr) end + -- generate output + local output = {} + for ip_addr, info in pairs(lltd_responders) do + if target.ALLOW_NEW_TARGETS then target.add(ip_addr) end - local s = {} - s.name = ip_addr - if info.hostname then - table.insert(s, "Hostname: " .. info.hostname) - end - if info.mac then - table.insert(s, "Mac: " .. info.mac) - end - if info.ipv6 then - table.insert(s, "IPv6: " .. info.ipv6) - end - table.insert(output,s) - end + local s = {} + s.name = ip_addr + if info.hostname then + table.insert(s, "Hostname: " .. info.hostname) + end + if info.mac then + table.insert(s, "Mac: " .. info.mac) + end + if info.ipv6 then + table.insert(s, "IPv6: " .. info.ipv6) + end + table.insert(output,s) + 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( (#output>0), output ) + 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( (#output>0), output ) end diff --git a/scripts/metasploit-info.nse b/scripts/metasploit-info.nse index d2def8218..342d59aa6 100644 --- a/scripts/metasploit-info.nse +++ b/scripts/metasploit-info.nse @@ -45,243 +45,243 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"intrusive","safe"} portrule = shortport.port_or_service(55553,"metasploit-msgrpc") -local arg_username = stdnse.get_script_args(SCRIPT_NAME .. ".username") -local arg_password = stdnse.get_script_args(SCRIPT_NAME .. ".password") -local arg_command = stdnse.get_script_args(SCRIPT_NAME .. ".command") +local arg_username = stdnse.get_script_args(SCRIPT_NAME .. ".username") +local arg_password = stdnse.get_script_args(SCRIPT_NAME .. ".password") +local arg_command = stdnse.get_script_args(SCRIPT_NAME .. ".command") local os_type -- returns a "prefix" that msgpack uses for strings local get_prefix = function(data) - if string.len(data) <= 31 then - return bin.pack("C",0xa0 + string.len(data)) - else - return bin.pack("C",0xda) .. bin.pack("s",string.len(data)) - end + if string.len(data) <= 31 then + return bin.pack("C",0xa0 + string.len(data)) + else + return bin.pack("C",0xda) .. bin.pack("s",string.len(data)) + end end -- returns a msgpacked data for console.read 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 -- returns a msgpacked data for console.write 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 -- returns a msgpacked data for auth.login local encode_auth = function(username, password) - local method = "auth.login" - return bin.pack("C",0x93) .. bin.pack("C",0xaa) .. method .. get_prefix(username) .. username .. get_prefix(password) .. password + local method = "auth.login" + return bin.pack("C",0x93) .. bin.pack("C",0xaa) .. method .. get_prefix(username) .. username .. get_prefix(password) .. password end -- returns a msgpacked data for any method without exstra parameters local encode_noparam = function(token,method) - -- token is always the same length - return bin.pack("C",0x92) .. get_prefix(method) .. method .. bin.pack("H","da0020") .. token + -- token is always the same length + return bin.pack("C",0x92) .. get_prefix(method) .. method .. bin.pack("H","da0020") .. token end -- does the actuall call with specified, pre-packed data -- and returns the response local msgrpc_call = function(host, port, msg) - local data - local options = { - header = { - ["Content-Type"] = "binary/message-pack" - } - } - data = http.post(host,port, "/api/",options, nil , msg) - if data and data.status and tostring( data.status ):match( "200" ) then - return data.body - end - return nil + local data + local options = { + header = { + ["Content-Type"] = "binary/message-pack" + } + } + data = http.post(host,port, "/api/",options, nil , msg) + if data and data.status and tostring( data.status ):match( "200" ) then + return data.body + end + return nil end -- auth.login wraper, returns the auth token 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 - local start = string.find(data,"success") - if start > -1 then - -- get token - local token = string.sub(string.sub(data,start),17) -- "manualy" unpack token - return true, token - else - return false, nil - end - end - stdnse.print_debug("something is wrong:" .. data ) - return false, nil + if data then + local start = string.find(data,"success") + if start > -1 then + -- get token + local token = string.sub(string.sub(data,start),17) -- "manualy" unpack token + return true, token + else + return false, nil + end + end + stdnse.print_debug("something is wrong:" .. data ) + return false, nil end -- core.version wraper, returns version info, and sets the OS type -- so we can decide which commands to send later 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) - -- unpack data - if data then - -- get version, ruby version, api version - local start = string.find(data,"version") - local metasploit_version - local ruby_version - local api_version - if start then - metasploit_version = string.sub(string.sub(data,start),9) - start = string.find(metasploit_version,"ruby") - start = start - 2 - metasploit_version = string.sub(metasploit_version,1,start) - start = string.find(data,"ruby") - ruby_version = string.sub(string.sub(data,start),6) - start = string.find(ruby_version,"api") - start = start - 2 - ruby_version = string.sub(ruby_version,1,start) - start = string.find(data,"api") - api_version = string.sub(string.sub(data,start),5) - -- put info in a table and parse for OS detection and other info - port.version.name = "metasploit-msgrpc" - port.version.product = metasploit_version - port.version.name_confidence = 10 - nmap.set_port_version(host,port) - local info = "Metasploit version: " .. metasploit_version .. " Ruby version: " .. ruby_version .. " API version: " .. api_version - if string.find(ruby_version,"mingw") < 0 then - os_type = "linux" -- assume linux for now - else -- mingw compiler means it's a windows build - os_type = "windows" - end - stdnse.print_debug(info) - return info - end - end - return nil + local data = msgrpc_call(host, port, msg) + -- unpack data + if data then + -- get version, ruby version, api version + local start = string.find(data,"version") + local metasploit_version + local ruby_version + local api_version + if start then + metasploit_version = string.sub(string.sub(data,start),9) + start = string.find(metasploit_version,"ruby") + start = start - 2 + metasploit_version = string.sub(metasploit_version,1,start) + start = string.find(data,"ruby") + ruby_version = string.sub(string.sub(data,start),6) + start = string.find(ruby_version,"api") + start = start - 2 + ruby_version = string.sub(ruby_version,1,start) + start = string.find(data,"api") + api_version = string.sub(string.sub(data,start),5) + -- put info in a table and parse for OS detection and other info + port.version.name = "metasploit-msgrpc" + port.version.product = metasploit_version + port.version.name_confidence = 10 + nmap.set_port_version(host,port) + local info = "Metasploit version: " .. metasploit_version .. " Ruby version: " .. ruby_version .. " API version: " .. api_version + if string.find(ruby_version,"mingw") < 0 then + os_type = "linux" -- assume linux for now + else -- mingw compiler means it's a windows build + os_type = "windows" + end + stdnse.print_debug(info) + return info + end + end + return nil end -- console.create wraper, returns console_id -- which we can use to interact with metasploit further local create_console = function(host,port,token) - local msg = encode_noparam(token,"console.create") - local data = msgrpc_call(host, port, msg) - -- unpack data - if data then - --get console id - local start = string.find(data,"id") - local console_id - if start then - console_id = string.sub(string.sub(data,start),4) - local next_token = string.find(console_id,"prompt") - console_id = string.sub(console_id,1,next_token-2) - return console_id - end - end - return nil + local msg = encode_noparam(token,"console.create") + local data = msgrpc_call(host, port, msg) + -- unpack data + if data then + --get console id + local start = string.find(data,"id") + local console_id + if start then + console_id = string.sub(string.sub(data,start),4) + local next_token = string.find(console_id,"prompt") + console_id = string.sub(console_id,1,next_token-2) + return console_id + end + end + return nil end -- console.read wraper local read_console = function(host,port,token,console_id) - local msg = encode_console_read("console.read",token,console_id) - local data = msgrpc_call(host, port, msg) - -- unpack data - if data then - -- check if busy - while string.byte(data,string.len(data)) == 0xc3 do - -- console is busy , let's retry in one second - stdnse.sleep(1) - data = msgrpc_call(host, port, msg) - end - local start = string.find(data,"data") - local read_data - if start then - read_data = string.sub(string.sub(data,start),8) - local next_token = string.find(read_data,"prompt") - read_data = string.sub(read_data,1,next_token-2) - return read_data - end - end + local msg = encode_console_read("console.read",token,console_id) + local data = msgrpc_call(host, port, msg) + -- unpack data + if data then + -- check if busy + while string.byte(data,string.len(data)) == 0xc3 do + -- console is busy , let's retry in one second + stdnse.sleep(1) + data = msgrpc_call(host, port, msg) + end + local start = string.find(data,"data") + local read_data + if start then + read_data = string.sub(string.sub(data,start),8) + local next_token = string.find(read_data,"prompt") + read_data = string.sub(read_data,1,next_token-2) + return read_data + end + end end -- console.write wraper local write_console = function(host,port,token,console_id,command) - local msg = encode_console_write("console.write",token,console_id,command .. "\n") - local data = msgrpc_call(host, port, msg) - -- unpack data - if data then - return true - end - return false + local msg = encode_console_write("console.write",token,console_id,command .. "\n") + local data = msgrpc_call(host, port, msg) + -- unpack data + if data then + return true + end + return false end -- console.destroy wraper, just to be nice, we don't want console to hang ... local destroy_console = function(host,port,token,console_id) - local msg = encode_console_read("console.destroy",token,console_id) - local data = msgrpc_call(host, port, msg) + local msg = encode_console_read("console.destroy",token,console_id) + local data = msgrpc_call(host, port, msg) end -- write command and read result helper local write_read_console = function(host,port,token, console_id,command) - if write_console(host,port,token,console_id, command) then - local read_data = read_console(host,port,token,console_id) - if read_data then - read_data = string.sub(read_data,string.find(read_data,"\n")+1) -- skip command echo - return read_data - end - end - return nil + if write_console(host,port,token,console_id, command) then + local read_data = read_console(host,port,token,console_id) + if read_data then + read_data = string.sub(read_data,string.find(read_data,"\n")+1) -- skip command echo + return read_data + end + end + return nil end action = function( host, port ) - if not arg_username or not arg_password then - stdnse.print_debug("This script requires username and password supplied as arguments") - return false - end + if not arg_username or not arg_password then + stdnse.print_debug("This script requires username and password supplied as arguments") + return false + end - -- authenticate - local status, token = login(arg_username,arg_password,host,port) - if status then - -- get version info - local info = get_version(host,port,token) - local console_id = create_console(host,port,token) - if console_id then - 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 :) - if read_data then - if os_type == "linux" then - read_data = write_read_console(host,port,token,console_id, "uname -a") - if read_data then - info = info .. "\nAdditional info: " .. read_data - end - read_data = write_read_console(host,port,token,console_id, "id") - if read_data then - info = info .. read_data - end - elseif os_type == "windows" then - read_data = write_read_console(host,port,token,console_id, "systeminfo") - if read_data then - 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 - read_data = string.sub(read_data,1,stop-2) - info = info .. "\nAdditional info: \n" .. read_data - end - end - if arg_command then - read_data = write_read_console(host,port,token,console_id, arg_command) - if read_data then - info = info .. "\nCustom command output: " .. read_data - end - end - if read_data then - -- let's be nice and close the console - destroy_console(host,port,token,console_id) - end - end - end - if info then - return stdnse.format_output(true,info) - end - end - return false + -- authenticate + local status, token = login(arg_username,arg_password,host,port) + if status then + -- get version info + local info = get_version(host,port,token) + local console_id = create_console(host,port,token) + if console_id then + 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 :) + if read_data then + if os_type == "linux" then + read_data = write_read_console(host,port,token,console_id, "uname -a") + if read_data then + info = info .. "\nAdditional info: " .. read_data + end + read_data = write_read_console(host,port,token,console_id, "id") + if read_data then + info = info .. read_data + end + elseif os_type == "windows" then + read_data = write_read_console(host,port,token,console_id, "systeminfo") + if read_data then + 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 + read_data = string.sub(read_data,1,stop-2) + info = info .. "\nAdditional info: \n" .. read_data + end + end + if arg_command then + read_data = write_read_console(host,port,token,console_id, arg_command) + if read_data then + info = info .. "\nCustom command output: " .. read_data + end + end + if read_data then + -- let's be nice and close the console + destroy_console(host,port,token,console_id) + end + end + end + if info then + return stdnse.format_output(true,info) + end + end + return false end diff --git a/scripts/mrinfo.nse b/scripts/mrinfo.nse index 54d0163f8..32ce66847 100644 --- a/scripts/mrinfo.nse +++ b/scripts/mrinfo.nse @@ -71,62 +71,62 @@ categories = {"discovery", "safe", "broadcast"} prerule = function() - if nmap.address_family() ~= 'inet' then - stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) - return false - end - if not nmap.is_privileged() then - stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) - return false - end - return true + if nmap.address_family() ~= 'inet' then + stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) + return false + end + if not nmap.is_privileged() then + stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) + return false + end + return true end -- Parses a DVMRP Ask Neighbor 2 raw data and returns -- a structured response. -- @param data raw data. local mrinfoParse = function(data) - local index, address, neighbor - local response = {} + local index, address, neighbor + local response = {} - -- first byte should be IGMP type == 0x13 (DVMRP) - if data:byte(1) ~= 0x13 then return end + -- first byte should be IGMP type == 0x13 (DVMRP) + if data:byte(1) ~= 0x13 then return end - -- DVMRP Code - index, response.code = bin.unpack(">C", data, 2) - -- Checksum - index, response.checksum = bin.unpack(">S", data, index) - -- Capabilities (Skip one reserved byte) - index, response.capabilities = bin.unpack(">C", data, index + 1) - -- Major and minor version - index, response.minver = bin.unpack(">C", data, index) - index, response.majver = bin.unpack(">C", data, index) - response.addresses = {} - -- Iterate over target local addresses (interfaces) - while index < #data do - if data:byte(index) == 0x00 then break end - address = {} - -- Local address - index, address.ip = bin.unpack("C", data, index) - -- Treshold - index, address.treshold= bin.unpack(">C", data, index) - -- Flags - index, address.flags = bin.unpack(">C", data, index) - -- Number of neighbors - index, address.ncount = bin.unpack(">C", data, index) + -- DVMRP Code + index, response.code = bin.unpack(">C", data, 2) + -- Checksum + index, response.checksum = bin.unpack(">S", data, index) + -- Capabilities (Skip one reserved byte) + index, response.capabilities = bin.unpack(">C", data, index + 1) + -- Major and minor version + index, response.minver = bin.unpack(">C", data, index) + index, response.majver = bin.unpack(">C", data, index) + response.addresses = {} + -- Iterate over target local addresses (interfaces) + while index < #data do + if data:byte(index) == 0x00 then break end + address = {} + -- Local address + index, address.ip = bin.unpack("C", data, index) + -- Treshold + index, address.treshold= bin.unpack(">C", data, index) + -- Flags + index, address.flags = bin.unpack(">C", data, index) + -- Number of neighbors + index, address.ncount = bin.unpack(">C", data, index) - address.neighbors = {} - -- Iterate over neighbors - for i = 1, address.ncount do - index, neighbor = bin.unpack("C", 0x13) - -- Code: Ask Neighbor v2 - mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x05) - -- Checksum: Calculated later - mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x0000) - -- Reserved - mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x000a) - -- Version == Cisco IOS 12.4 - -- Minor version: 4 - mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x04) - -- Major version: 12 - mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x0c) - -- Calculate checksum - mrinfo_raw = mrinfo_raw:sub(1,2) .. bin.pack(">S", packet.in_cksum(mrinfo_raw)) .. mrinfo_raw:sub(5) + -- Type: DVMRP + local mrinfo_raw = bin.pack(">C", 0x13) + -- Code: Ask Neighbor v2 + mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x05) + -- Checksum: Calculated later + mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x0000) + -- Reserved + mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x000a) + -- Version == Cisco IOS 12.4 + -- Minor version: 4 + mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x04) + -- Major version: 12 + mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x0c) + -- Calculate checksum + 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 -- Function that sends a DVMRP query. --@param interface Network interface to use. --@param dstip Destination IP to send to. local mrinfoQuery = function(interface, dstip) - local mrinfo_packet, sock, eth_hdr - local srcip = interface.address + local mrinfo_packet, sock, eth_hdr + local srcip = interface.address - local mrinfo_raw = mrinfoRaw() - local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. mrinfo_raw - 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_dst(ipOps.ip_to_str(dstip)) - mrinfo_packet:ip_set_len(ip_raw:len()) - if dstip == "224.0.0.1" then - -- Doesn't affect results, but we should respect RFC 3171 :) - mrinfo_packet:ip_set_ttl(1) - end - mrinfo_packet:ip_count_checksum() + local mrinfo_raw = mrinfoRaw() + local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. mrinfo_raw + 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_dst(ipOps.ip_to_str(dstip)) + mrinfo_packet:ip_set_len(ip_raw:len()) + if dstip == "224.0.0.1" then + -- Doesn't affect results, but we should respect RFC 3171 :) + mrinfo_packet:ip_set_ttl(1) + end + mrinfo_packet:ip_count_checksum() - sock = nmap.new_dnet() - if dstip == "224.0.0.1" then - sock:ethernet_open(interface.device) - -- 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") - sock:ethernet_send(eth_hdr .. mrinfo_packet.buf) - sock:ethernet_close() - else - sock:ip_open() - sock:ip_send(mrinfo_packet.buf, dstip) - sock:ip_close() - end + sock = nmap.new_dnet() + if dstip == "224.0.0.1" then + sock:ethernet_open(interface.device) + -- 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") + sock:ethernet_send(eth_hdr .. mrinfo_packet.buf) + sock:ethernet_close() + else + sock:ip_open() + sock:ip_send(mrinfo_packet.buf, dstip) + sock:ip_close() + end end -- Returns the network interface used to send packets to a target host. --@param target host to which the interface is used. --@return interface Network interface used for target host. local getInterface = function(target) - -- First, create dummy UDP connection to get interface - local sock = nmap.new_socket() - local status, err = sock:connect(target, "12345", "udp") - if not status then - stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) - return - end - local status, address, _, _, _ = sock:get_info() - if not status then - stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) - return - end - for _, interface in pairs(nmap.list_interfaces()) do - if interface.address == address then - return interface - end + -- First, create dummy UDP connection to get interface + local sock = nmap.new_socket() + local status, err = sock:connect(target, "12345", "udp") + if not status then + stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) + return + end + local status, address, _, _, _ = sock:get_info() + if not status then + stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) + return + end + for _, interface in pairs(nmap.list_interfaces()) do + if interface.address == address then + return interface end + end end action = function() - local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) - timeout = (timeout or 5) * 1000 - local target = stdnse.get_script_args(SCRIPT_NAME .. ".target") or "224.0.0.1" - local responses = {} - local interface, result + local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) + timeout = (timeout or 5) * 1000 + local target = stdnse.get_script_args(SCRIPT_NAME .. ".target") or "224.0.0.1" + local responses = {} + local interface, result - interface = nmap.get_interface() - if interface then - interface = nmap.get_interface_info(interface) - else - interface = getInterface(target) + interface = nmap.get_interface() + if interface then + interface = nmap.get_interface_info(interface) + else + 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 - 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 - 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) + 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 diff --git a/scripts/ms-sql-brute.nse b/scripts/ms-sql-brute.nse index e39ea86f1..45c92695d 100644 --- a/scripts/ms-sql-brute.nse +++ b/scripts/ms-sql-brute.nse @@ -55,9 +55,9 @@ be disabled using the mssql.scanned-ports-only script argument. -- ---- -- @args ms-sql-brute.ignore-lockout WARNING! Including this argument will cause --- 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 --- Server logins being locked out! +-- 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 +-- Server logins being locked out! -- -- @args ms-sql-brute.brute-windows-accounts Enable targeting Windows accounts -- as part of the brute force attack. This should be used in conjunction @@ -66,10 +66,10 @@ be disabled using the mssql.scanned-ports-only script argument. -- Created 01/17/2010 - v0.1 - created by Patrik Karlsson -- Revised 02/01/2011 - v0.2 (Chris Woodbury) --- - Added ability to run against all instances on a host; --- - Added recognition of account-locked out and password-expired error codes; --- - Added storage of credentials on a per-instance basis --- - Added compatibility with changes in mssql.lua +-- - Added ability to run against all instances on a host; +-- - Added recognition of account-locked out and password-expired error codes; +-- - Added storage of credentials on a per-instance basis +-- - Added compatibility with changes in mssql.lua author = "Patrik Karlsson" 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 local function create_instance_output_table( instance ) - local instanceOutput = {} - instanceOutput["name"] = string.format( "[%s]", instance:GetName() ) - if ( instance.ms_sql_brute.credentials ) then - local credsOutput = {} - credsOutput["name"] = "Credentials found:" - table.insert( instanceOutput, credsOutput ) + local instanceOutput = {} + instanceOutput["name"] = string.format( "[%s]", instance:GetName() ) + if ( instance.ms_sql_brute.credentials ) then + local credsOutput = {} + credsOutput["name"] = "Credentials found:" + table.insert( instanceOutput, credsOutput ) - for username, result in pairs( instance.ms_sql_brute.credentials ) do - local password = result[1] - local errorCode = result[2] - password = password:len()>0 and password or "" - if errorCode then - local errorMessage = mssql.LoginErrorMessage[ errorCode ] or "unknown error" - table.insert( credsOutput, string.format( "%s:%s => %s", username, password, errorMessage ) ) - else - table.insert( credsOutput, string.format( "%s:%s => Login Success", username, password ) ) - end - end + for username, result in pairs( instance.ms_sql_brute.credentials ) do + local password = result[1] + local errorCode = result[2] + password = password:len()>0 and password or "" + if errorCode then + local errorMessage = mssql.LoginErrorMessage[ errorCode ] or "unknown error" + table.insert( credsOutput, string.format( "%s:%s => %s", username, password, errorMessage ) ) + else + table.insert( credsOutput, string.format( "%s:%s => Login Success", username, password ) ) + end + end - if ( #credsOutput == 0 ) then - table.insert( instanceOutput, "No credentials found" ) - end - end + if ( #credsOutput == 0 ) then + table.insert( instanceOutput, "No credentials found" ) + end + end - if ( instance.ms_sql_brute.warnings ) then - local warningsOutput = {} - warningsOutput["name"] = "Warnings:" - table.insert( instanceOutput, warningsOutput ) + if ( instance.ms_sql_brute.warnings ) then + local warningsOutput = {} + warningsOutput["name"] = "Warnings:" + table.insert( instanceOutput, warningsOutput ) - for _, warning in ipairs( instance.ms_sql_brute.warnings ) do - table.insert( warningsOutput, warning ) - end - end + for _, warning in ipairs( instance.ms_sql_brute.warnings ) do + table.insert( warningsOutput, warning ) + end + end - if ( instance.ms_sql_brute.errors ) then - local errorsOutput = {} - errorsOutput["name"] = "Errors:" - table.insert( instanceOutput, errorsOutput ) + if ( instance.ms_sql_brute.errors ) then + local errorsOutput = {} + errorsOutput["name"] = "Errors:" + table.insert( instanceOutput, errorsOutput ) - for _, error in ipairs( instance.ms_sql_brute.errors ) do - table.insert( errorsOutput, error ) - end - end + for _, error in ipairs( instance.ms_sql_brute.errors ) do + table.insert( errorsOutput, error ) + end + end - return instanceOutput + return instanceOutput end local function test_credentials( instance, helper, username, password ) - local database = "tempdb" - local stopUser, stopInstance = false, false + local database = "tempdb" + local stopUser, stopInstance = false, false - local status, result = helper:ConnectEx( instance ) - local loginErrorCode - if( status ) then - 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 ) - end - helper:Disconnect() + local status, result = helper:ConnectEx( instance ) + local loginErrorCode + if( status ) then + 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 ) + end + helper:Disconnect() - local passwordIsGood, canLogin - if status then - passwordIsGood = true - canLogin = true - elseif ( loginErrorCode ) then - if ( ( loginErrorCode ~= mssql.LoginErrorType.InvalidUsernameOrPassword ) and - ( loginErrorCode ~= mssql.LoginErrorType.NotAssociatedWithTrustedConnection ) ) then - stopUser = true - end + local passwordIsGood, canLogin + if status then + passwordIsGood = true + canLogin = true + elseif ( loginErrorCode ) then + if ( ( loginErrorCode ~= mssql.LoginErrorType.InvalidUsernameOrPassword ) and + ( loginErrorCode ~= mssql.LoginErrorType.NotAssociatedWithTrustedConnection ) ) then + stopUser = true + end - if ( loginErrorCode == mssql.LoginErrorType.PasswordExpired ) then passwordIsGood = true - elseif ( loginErrorCode == mssql.LoginErrorType.PasswordMustChange ) then passwordIsGood = true - elseif ( loginErrorCode == mssql.LoginErrorType.AccountLockedOut ) then - 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 ) ) - if ( not stdnse.get_script_args( "ms-sql-brute.ignore-lockout" ) ) then - stopInstance = true - end - end - if ( mssql.LoginErrorMessage[ loginErrorCode ] == nil ) then - stdnse.print_debug( 2, "%s: Attemping login to %s as (%s/%s): Unknown login error number: %s", - SCRIPT_NAME, instance:GetName(), username, password, loginErrorCode ) - table.insert( instance.ms_sql_brute.warnings, string.format( "Unknown login error number: %s", loginErrorCode ) ) - end - 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 ] ) ) - else - table.insert( instance.ms_sql_brute.errors, string.format("Network error. Skipping instance. Error: %s", result ) ) - stopUser = true - stopInstance = true - end + if ( loginErrorCode == mssql.LoginErrorType.PasswordExpired ) then passwordIsGood = true + elseif ( loginErrorCode == mssql.LoginErrorType.PasswordMustChange ) then passwordIsGood = true + elseif ( loginErrorCode == mssql.LoginErrorType.AccountLockedOut ) then + 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 ) ) + if ( not stdnse.get_script_args( "ms-sql-brute.ignore-lockout" ) ) then + stopInstance = true + end + end + if ( mssql.LoginErrorMessage[ loginErrorCode ] == nil ) then + stdnse.print_debug( 2, "%s: Attemping login to %s as (%s/%s): Unknown login error number: %s", + SCRIPT_NAME, instance:GetName(), username, password, loginErrorCode ) + table.insert( instance.ms_sql_brute.warnings, string.format( "Unknown login error number: %s", loginErrorCode ) ) + end + 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 ] ) ) + else + table.insert( instance.ms_sql_brute.errors, string.format("Network error. Skipping instance. Error: %s", result ) ) + stopUser = true + stopInstance = true + end - if ( passwordIsGood ) then - stopUser = true + if ( passwordIsGood ) then + stopUser = true - instance.ms_sql_brute.credentials[ username ] = { password, loginErrorCode } - -- Add credentials for other ms-sql scripts to use but don't - -- add accounts that need to change passwords - if ( canLogin ) then - instance.credentials[ username ] = password - -- Legacy storage method (does not distinguish between instances) - nmap.registry.mssqlusers = nmap.registry.mssqlusers or {} - nmap.registry.mssqlusers[username]=password - end - end + instance.ms_sql_brute.credentials[ username ] = { password, loginErrorCode } + -- Add credentials for other ms-sql scripts to use but don't + -- add accounts that need to change passwords + if ( canLogin ) then + instance.credentials[ username ] = password + -- Legacy storage method (does not distinguish between instances) + nmap.registry.mssqlusers = nmap.registry.mssqlusers or {} + nmap.registry.mssqlusers[username]=password + end + end - return stopUser, stopInstance + return stopUser, stopInstance end --- Processes a single instance, attempting to detect an empty password for "sa" local function process_instance( instance ) - -- 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 - -- 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 - -- 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 - -- a time. - local mutex = nmap.mutex( instance ) - mutex( "lock" ) + -- 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 + -- 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 + -- 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 + -- a time. + local mutex = nmap.mutex( instance ) + mutex( "lock" ) - -- 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. - if ( instance.tested_brute ~= true ) then - instance.tested_brute = true + -- 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. + if ( instance.tested_brute ~= true ) then + instance.tested_brute = true - instance.credentials = instance.credentials 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.warnings = instance.ms_sql_brute.warnings or {} - instance.ms_sql_brute.errors = instance.ms_sql_brute.errors or {} + instance.credentials = instance.credentials 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.warnings = instance.ms_sql_brute.warnings or {} + instance.ms_sql_brute.errors = instance.ms_sql_brute.errors or {} - local result, status - local stopUser, stopInstance - local usernames, passwords, username, password - local helper = mssql.Helper:new() + local result, status + local stopUser, stopInstance + local usernames, passwords, username, password + local helper = mssql.Helper:new() - if ( not instance:HasNetworkProtocols() ) then - 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." ) - stopInstance = true - end + if ( not instance:HasNetworkProtocols() ) then + 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." ) + stopInstance = true + end - status, usernames = unpwdb.usernames() - if ( not(status) ) then - stdnse.print_debug( 1, "%s: Failed to load usernames list.", SCRIPT_NAME ) - table.insert( instance.ms_sql_brute.errors, "Failed to load usernames list." ) - stopInstance = true - end + status, usernames = unpwdb.usernames() + if ( not(status) ) then + stdnse.print_debug( 1, "%s: Failed to load usernames list.", SCRIPT_NAME ) + table.insert( instance.ms_sql_brute.errors, "Failed to load usernames list." ) + stopInstance = true + end - if ( status ) then - status, passwords = unpwdb.passwords() - if ( not(status) ) then - stdnse.print_debug( 1, "%s: Failed to load passwords list.", SCRIPT_NAME ) - table.insert( instance.ms_sql_brute.errors, "Failed to load passwords list." ) - stopInstance = true - end - end + if ( status ) then + status, passwords = unpwdb.passwords() + if ( not(status) ) then + stdnse.print_debug( 1, "%s: Failed to load passwords list.", SCRIPT_NAME ) + table.insert( instance.ms_sql_brute.errors, "Failed to load passwords list." ) + stopInstance = true + end + end - if ( status ) then - for username in usernames do - if stopInstance then break end + if ( status ) then + for username in usernames do + if stopInstance then break end - -- See if the password is the same as the username (which may not - -- be in the password list) - stopUser, stopInstance = test_credentials( instance, helper, username, username ) + -- See if the password is the same as the username (which may not + -- be in the password list) + stopUser, stopInstance = test_credentials( instance, helper, username, username ) - for password in passwords do - if stopUser then break end + for password in passwords do + if stopUser then break end - stopUser, stopInstance = test_credentials( instance, helper, username, password ) - end + stopUser, stopInstance = test_credentials( instance, helper, username, password ) + end - passwords("reset") - end - end - end + passwords("reset") + end + end + end - -- The password testing has been finished. Unlock the mutex. - mutex( "done" ) + -- The password testing has been finished. Unlock the mutex. + mutex( "done" ) - return create_instance_output_table( instance ) + return create_instance_output_table( instance ) end action = function( host, port ) - local scriptOutput = {} - local status, instanceList = mssql.Helper.GetTargetInstances( host, port ) + local scriptOutput = {} + 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 - local ret = "\n " .. - "Windows authentication was enabled but the argument\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 " .. - "used, make sure that the amount entries in the password list\n " .. - "(passdb argument) are at least 2 entries below the lockout threshold." - return ret - end + if ( domain and not(bruteWindows) ) then + local ret = "\n " .. + "Windows authentication was enabled but the argument\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 " .. + "used, make sure that the amount entries in the password list\n " .. + "(passdb argument) are at least 2 entries below the lockout threshold." + return ret + end - if ( not status ) then - return stdnse.format_output( false, instanceList ) - else - for _, instance in pairs( instanceList ) do - local instanceOutput = process_instance( instance ) - if instanceOutput then - table.insert( scriptOutput, instanceOutput ) - end - end - end + if ( not status ) then + return stdnse.format_output( false, instanceList ) + else + for _, instance in pairs( instanceList ) do + local instanceOutput = process_instance( instance ) + if instanceOutput then + table.insert( scriptOutput, instanceOutput ) + end + end + end - return stdnse.format_output( true, scriptOutput ) + return stdnse.format_output( true, scriptOutput ) end diff --git a/scripts/mtrace.nse b/scripts/mtrace.nse index 8da8dc181..58592c1f5 100644 --- a/scripts/mtrace.nse +++ b/scripts/mtrace.nse @@ -69,47 +69,47 @@ categories = {"discovery", "safe", "broadcast"} -- From: https://tools.ietf.org/id/draft-ietf-idmr-traceroute-ipm-07.txt PROTO = { - [0x01] = "DVMRP", - [0x02] = "MOSPF", - [0x03] = "PIM", - [0x04] = "CBT", - [0x05] = "PIM / Special table", - [0x06] = "PIM / Static", - [0x07] = "DVMRP / Static", - [0x08] = "PIM / MBGP", - [0x09] = "CBT / Special table", - [0x10] = "CBT / Static", - [0x11] = "PIM / state created by Assert processing", + [0x01] = "DVMRP", + [0x02] = "MOSPF", + [0x03] = "PIM", + [0x04] = "CBT", + [0x05] = "PIM / Special table", + [0x06] = "PIM / Static", + [0x07] = "DVMRP / Static", + [0x08] = "PIM / MBGP", + [0x09] = "CBT / Special table", + [0x10] = "CBT / Static", + [0x11] = "PIM / state created by Assert processing", } FWD_CODE = { - [0x00] = "NO_ERROR", - [0x01] = "WRONG_IF", - [0x02] = "PRUNE_SENT", - [0x03] = "PRUNE_RCVD", - [0x04] = "SCOPED", - [0x05] = "NO_ROUTE", - [0x06] = "WRONG_LAST_HOP", - [0x07] = "NOT_FORWARDING", - [0x08] = "REACHED_RP", - [0x09] = "RPF_IF", - [0x0A] = "NO_MULTICAST", - [0x0B] = "INFO_HIDDEN", - [0x81] = "NO_SPACE", - [0x82] = "OLD_ROUTER", - [0x83] = "ADMIN_PROHIB", + [0x00] = "NO_ERROR", + [0x01] = "WRONG_IF", + [0x02] = "PRUNE_SENT", + [0x03] = "PRUNE_RCVD", + [0x04] = "SCOPED", + [0x05] = "NO_ROUTE", + [0x06] = "WRONG_LAST_HOP", + [0x07] = "NOT_FORWARDING", + [0x08] = "REACHED_RP", + [0x09] = "RPF_IF", + [0x0A] = "NO_MULTICAST", + [0x0B] = "INFO_HIDDEN", + [0x81] = "NO_SPACE", + [0x82] = "OLD_ROUTER", + [0x83] = "ADMIN_PROHIB", } prerule = function() - if nmap.address_family() ~= 'inet' then - stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) - return false - end - if not nmap.is_privileged() then - stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) - return false - end - return true + if nmap.address_family() ~= 'inet' then + stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME) + return false + end + if not nmap.is_privileged() then + stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) + return false + end + return true end --- Generates a raw IGMP Traceroute Query. @@ -119,19 +119,19 @@ end --@param receiver Receiver of the response. --@return data Raw Traceroute Query. local traceRaw = function(fromip, toip, group, receiver) - local data = bin.pack(">C", 0x1f) -- Type: Traceroute Query - 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(">I", ipOps.todword(group)) -- Multicast group - 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(receiver)) -- Receiver - local data = data .. bin.pack(">C", 0x40) -- TTL - local data = data .. bin.pack(">CS", 0x00, math.random(123456)) -- Query ID + local data = bin.pack(">C", 0x1f) -- Type: Traceroute Query + 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(">I", ipOps.todword(group)) -- Multicast group + 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(receiver)) -- Receiver + local data = data .. bin.pack(">C", 0x40) -- TTL + local data = data .. bin.pack(">CS", 0x00, math.random(123456)) -- Query ID - -- We calculate checksum - data = data:sub(1,2) .. bin.pack(">S", packet.in_cksum(data)) .. data:sub(5) - return data + -- We calculate checksum + data = data:sub(1,2) .. bin.pack(">S", packet.in_cksum(data)) .. data:sub(5) + return data end --- Sends a raw IGMP Traceroute Query. @@ -139,125 +139,125 @@ end --@param destination Target host to which the packet is sent. --@param trace_raw Traceroute raw Query. local traceSend = function(interface, destination, trace_raw) - local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. trace_raw - 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_dst(ipOps.ip_to_str(destination)) - trace_packet:ip_set_len(#trace_packet.buf) - trace_packet:ip_count_checksum() + local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. trace_raw + 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_dst(ipOps.ip_to_str(destination)) + trace_packet:ip_set_len(#trace_packet.buf) + trace_packet:ip_count_checksum() - if destination == "224.0.0.2" then - -- Doesn't affect results as it is ignored but most routers, but RFC - -- 3171 should be respected. - trace_packet:ip_set_ttl(1) - end - trace_packet:ip_count_checksum() + if destination == "224.0.0.2" then + -- Doesn't affect results as it is ignored but most routers, but RFC + -- 3171 should be respected. + trace_packet:ip_set_ttl(1) + end + trace_packet:ip_count_checksum() - local sock = nmap.new_dnet() - if destination == "224.0.0.2" then - sock:ethernet_open(interface.device) - -- 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") - sock:ethernet_send(eth_hdr .. trace_packet.buf) - sock:ethernet_close() - else - sock:ip_open() - sock:ip_send(trace_packet.buf, destination) - sock:ip_close() - end + local sock = nmap.new_dnet() + if destination == "224.0.0.2" then + sock:ethernet_open(interface.device) + -- 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") + sock:ethernet_send(eth_hdr .. trace_packet.buf) + sock:ethernet_close() + else + sock:ip_open() + sock:ip_send(trace_packet.buf, destination) + sock:ip_close() + end end --- Parses an IGMP Traceroute Response and returns it in structured form. --@param data Raw Traceroute Response. --@return response Structured Traceroute Response. local traceParse = function(data) - local index - local response = {} + local index + local response = {} - -- first byte should be IGMP type == 0x1e (Traceroute Response) - if data:byte(1) ~= 0x1e then return end + -- first byte should be IGMP type == 0x1e (Traceroute Response) + if data:byte(1) ~= 0x1e then return end - -- Hops - index, response.hops = bin.unpack(">C", data, 2) + -- Hops + index, response.hops = bin.unpack(">C", data, 2) - -- Checksum - index, response.checksum = bin.unpack(">S", data, index) + -- Checksum + index, response.checksum = bin.unpack(">S", data, index) - -- Group - index, response.group = bin.unpack("C", data, index) + -- Response TTL + index, response.ttl = bin.unpack(">C", data, index) - -- Query ID - index, response.qid = bin.unpack(">C", data, index) - index, response.qid = response.qid * 2^16 + bin.unpack(">S", data, index) + -- Query ID + index, response.qid = bin.unpack(">C", data, index) + index, response.qid = response.qid * 2^16 + bin.unpack(">S", data, index) - local block - response.blocks = {} - -- Now, parse data blocks - while true do - -- To end parsing and not get stuck in infinite loops. - if index >= #data then - break - elseif #data - index < 31 then - stdnse.print_verbose("%s malformated traceroute response.", SCRIPT_NAME) - return - end - - block = {} - -- Query Arrival - index, block.query = bin.unpack(">I", data, index) - - -- In itf address - index, block.inaddr = 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) + local block + response.blocks = {} + -- Now, parse data blocks + while true do + -- To end parsing and not get stuck in infinite loops. + if index >= #data then + break + elseif #data - index < 31 then + stdnse.print_verbose("%s malformated traceroute response.", SCRIPT_NAME) + return 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) + + -- 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 -- Listens for IGMP Traceroute responses @@ -265,129 +265,129 @@ end --@param timeout Amount of time to listen for in seconds. --@param responses table to insert responses into. local traceListener = function(interface, timeout, responses) - local condvar = nmap.condvar(responses) - local start = nmap.clock_ms() - local listener = nmap.new_socket() - local p, trace_raw, status, l3data, response, _ + local condvar = nmap.condvar(responses) + local start = nmap.clock_ms() + local listener = nmap.new_socket() + local p, trace_raw, status, l3data, response, _ - -- IGMP packets that are sent to our host - local filter = 'ip proto 2 and dst host ' .. interface.address - listener:set_timeout(100) - listener:pcap_open(interface.device, 1024, true, filter) + -- IGMP packets that are sent to our host + local filter = 'ip proto 2 and dst host ' .. interface.address + listener:set_timeout(100) + listener:pcap_open(interface.device, 1024, true, filter) - while (nmap.clock_ms() - start) < timeout do - status, _, _, l3data = listener:pcap_receive() - if status then - p = packet.Packet:new(l3data, #l3data) - trace_raw = string.sub(l3data, p.ip_hl*4 + 1) - if p then - -- Check that IGMP Type == 0x1e (Traceroute Response) - if trace_raw:byte(1) == 0x1e then - response = traceParse(trace_raw) - if response then - response.srcip = p.ip_src - table.insert(responses, response) - end - end - end - end + while (nmap.clock_ms() - start) < timeout do + status, _, _, l3data = listener:pcap_receive() + if status then + p = packet.Packet:new(l3data, #l3data) + trace_raw = string.sub(l3data, p.ip_hl*4 + 1) + if p then + -- Check that IGMP Type == 0x1e (Traceroute Response) + if trace_raw:byte(1) == 0x1e then + response = traceParse(trace_raw) + if response then + response.srcip = p.ip_src + table.insert(responses, response) + end + end + end end - condvar("signal") + end + condvar("signal") end -- Returns the network interface used to send packets to a target host. --@param target host to which the interface is used. --@return interface Network interface used for target host. local getInterface = function(target) - -- First, create dummy UDP connection to get interface - local sock = nmap.new_socket() - local status, err = sock:connect(target, "12345", "udp") - if not status then - stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) - return - end - local status, address, _, _, _ = sock:get_info() - if not status then - stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) - return - end - for _, interface in pairs(nmap.list_interfaces()) do - if interface.address == address then - return interface - end + -- First, create dummy UDP connection to get interface + local sock = nmap.new_socket() + local status, err = sock:connect(target, "12345", "udp") + if not status then + stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) + return + end + local status, address, _, _, _ = sock:get_info() + if not status then + stdnse.print_verbose("%s: %s", SCRIPT_NAME, err) + return + end + for _, interface in pairs(nmap.list_interfaces()) do + if interface.address == address then + return interface end + end end action = function() - local fromip = stdnse.get_script_args(SCRIPT_NAME .. ".fromip") - local toip = stdnse.get_script_args(SCRIPT_NAME .. ".toip") - 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 timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) - local responses = {} - timeout = (timeout or 7) * 1000 + local fromip = stdnse.get_script_args(SCRIPT_NAME .. ".fromip") + local toip = stdnse.get_script_args(SCRIPT_NAME .. ".toip") + 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 timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) + local responses = {} + timeout = (timeout or 7) * 1000 - -- Source address from which to traceroute - if not fromip then - stdnse.print_verbose("%s: A source IP must be provided through fromip argument.", SCRIPT_NAME) - return - end - - -- Get network interface to use - local interface = nmap.get_interface() - if interface then - interface = nmap.get_interface_info(interface) - else - interface = getInterface(firsthop) - end - if not interface then - return ("\n ERROR: Couldn't get interface for %s"):format(firsthop) - end - - -- Destination defaults to our own host - 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: will send to %s via %s interface.", SCRIPT_NAME, firsthop, interface.shortname) - - -- Thread that listens for responses - stdnse.new_thread(traceListener, interface, timeout, responses) - - -- Send request after small wait to let Listener start - stdnse.sleep(0.1) - local trace_raw = traceRaw(fromip, toip, group, interface.address) - traceSend(interface, firsthop, trace_raw) - - local condvar = nmap.condvar(responses) - condvar("wait") - if #responses > 0 then - local outresp - local output, outblock = {} - table.insert(output, ("Group %s from %s to %s"):format(group, fromip, toip)) - for _, response in pairs(responses) do - outresp = {} - outresp.name = "Source: " .. response.srcip - for _, block in pairs(response.blocks) do - outblock = {} - outblock.name = "In address: " .. block.inaddr - table.insert(outblock, "Out address: " .. block.outaddr) - -- Protocol - if PROTO[block.proto] then - table.insert(outblock, "Protocol: " .. PROTO[block.proto]) - else - table.insert(outblock, "Protocol: Unknown") - end - -- Error Code, we ignore NO_ERROR which is the normal case. - if FWD_CODE[block.code] and block.code ~= 0x00 then - table.insert(outblock, "Error code: " .. FWD_CODE[block.code]) - elseif block.code ~= 0x00 then - table.insert(outblock, "Error code: Unknown") - end - table.insert(outresp, outblock) - end - table.insert(output, outresp) - end - return stdnse.format_output(true, output) + -- Source address from which to traceroute + if not fromip then + stdnse.print_verbose("%s: A source IP must be provided through fromip argument.", SCRIPT_NAME) + return + end + + -- Get network interface to use + local interface = nmap.get_interface() + if interface then + interface = nmap.get_interface_info(interface) + else + interface = getInterface(firsthop) + end + if not interface then + return ("\n ERROR: Couldn't get interface for %s"):format(firsthop) + end + + -- Destination defaults to our own host + 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: will send to %s via %s interface.", SCRIPT_NAME, firsthop, interface.shortname) + + -- Thread that listens for responses + stdnse.new_thread(traceListener, interface, timeout, responses) + + -- Send request after small wait to let Listener start + stdnse.sleep(0.1) + local trace_raw = traceRaw(fromip, toip, group, interface.address) + traceSend(interface, firsthop, trace_raw) + + local condvar = nmap.condvar(responses) + condvar("wait") + if #responses > 0 then + local outresp + local output, outblock = {} + table.insert(output, ("Group %s from %s to %s"):format(group, fromip, toip)) + for _, response in pairs(responses) do + outresp = {} + outresp.name = "Source: " .. response.srcip + for _, block in pairs(response.blocks) do + outblock = {} + outblock.name = "In address: " .. block.inaddr + table.insert(outblock, "Out address: " .. block.outaddr) + -- Protocol + if PROTO[block.proto] then + table.insert(outblock, "Protocol: " .. PROTO[block.proto]) + else + table.insert(outblock, "Protocol: Unknown") + end + -- Error Code, we ignore NO_ERROR which is the normal case. + if FWD_CODE[block.code] and block.code ~= 0x00 then + table.insert(outblock, "Error code: " .. FWD_CODE[block.code]) + elseif block.code ~= 0x00 then + table.insert(outblock, "Error code: Unknown") + end + table.insert(outresp, outblock) + end + table.insert(output, outresp) end + return stdnse.format_output(true, output) + end end diff --git a/scripts/p2p-conficker.nse b/scripts/p2p-conficker.nse index b2e5e4d52..fcfa2aef0 100644 --- a/scripts/p2p-conficker.nse +++ b/scripts/p2p-conficker.nse @@ -89,31 +89,31 @@ local MAX_PACKET = 0x2000 -- Flags local mode_flags = { - FLAG_MODE = bit.lshift(1, 0), - FLAG_LOCAL_ACK = bit.lshift(1, 1), - FLAG_IS_TCP = bit.lshift(1, 2), - FLAG_IP_INCLUDED = bit.lshift(1, 3), - FLAG_UNKNOWN0_INCLUDED = bit.lshift(1, 4), - FLAG_UNKNOWN1_INCLUDED = bit.lshift(1, 5), - FLAG_DATA_INCLUDED = bit.lshift(1, 6), - FLAG_SYSINFO_INCLUDED = bit.lshift(1, 7), - FLAG_ENCODED = bit.lshift(1, 15) + FLAG_MODE = bit.lshift(1, 0), + FLAG_LOCAL_ACK = bit.lshift(1, 1), + FLAG_IS_TCP = bit.lshift(1, 2), + FLAG_IP_INCLUDED = bit.lshift(1, 3), + FLAG_UNKNOWN0_INCLUDED = bit.lshift(1, 4), + FLAG_UNKNOWN1_INCLUDED = bit.lshift(1, 5), + FLAG_DATA_INCLUDED = bit.lshift(1, 6), + FLAG_SYSINFO_INCLUDED = bit.lshift(1, 7), + FLAG_ENCODED = bit.lshift(1, 15) } ---For a hostrule, simply use the 'smb' ports as an indicator, unless the user overrides it hostrule = function(host) - if ( nmap.address_family() ~= 'inet' ) then - return false - end - if(smb.get_port(host) ~= nil) then - return true - elseif(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then - return true - elseif(nmap.registry.args.checkconficker == "true" or nmap.registry.args.checkconficker == "1") then - return true - end + if ( nmap.address_family() ~= 'inet' ) then + return false + end + if(smb.get_port(host) ~= nil) then + return true + elseif(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then + return true + elseif(nmap.registry.args.checkconficker == "true" or nmap.registry.args.checkconficker == "1") then + return true + end - return false + return false end -- 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) --@return 64-bit product of u*v, as a pair of 32-bit integers. local function mul64(u, v) - -- 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 - -- chunks, such that - -- u = 2**16 u1 + u0 and v = 2**16 v1 + v0 - -- Then - -- u v = (2**16 u1 + u0) * (2**16 v1 + v0) - -- = 2**32 u1 v1 + 2**16 (u0 v1 + u1 v0) + u0 v0 - assert(0 <= u and u <= 0xFFFFFFFF) - assert(0 <= v and v <= 0xFFFFFFFF) - local u0, u1 = bit.band(u, 0xFFFF), bit.rshift(u, 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 - -- precision of a Lua number. - local t = u0 * v0 + (u0 * v1 + u1 * v0) * 65536 - return bit.band(t, 0xFFFFFFFF), u1 * v1 + bit.rshift(t, 32) + -- 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 + -- chunks, such that + -- u = 2**16 u1 + u0 and v = 2**16 v1 + v0 + -- Then + -- u v = (2**16 u1 + u0) * (2**16 v1 + v0) + -- = 2**32 u1 v1 + 2**16 (u0 v1 + u1 v0) + u0 v0 + assert(0 <= u and u <= 0xFFFFFFFF) + assert(0 <= v and v <= 0xFFFFFFFF) + local u0, u1 = bit.band(u, 0xFFFF), bit.rshift(u, 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 + -- precision of a Lua number. + local t = u0 * v0 + (u0 * v1 + u1 * v0) * 65536 + return bit.band(t, 0xFFFFFFFF), u1 * v1 + bit.rshift(t, 32) end ---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 --@return 64-bit rotated integer, as a pair of 32-bit integers. local function rot64(h, l) - local i + local i - assert(0 <= h and h <= 0xFFFFFFFF) - assert(0 <= l and l <= 0xFFFFFFFF) + assert(0 <= h and h <= 0xFFFFFFFF) + assert(0 <= l and l <= 0xFFFFFFFF) - local tmp = bit.band(h, 0x80000000) -- tmp = h & 0x80000000 - h = bit.lshift(h, 1) -- h = h << 1 - h = bit.bor(h, bit.rshift(l, 31)) -- h = h | (l >> 31) - l = bit.lshift(l, 1) - if(tmp ~= 0) then - l = bit.bor(l, 1) - end + local tmp = bit.band(h, 0x80000000) -- tmp = h & 0x80000000 + h = bit.lshift(h, 1) -- h = h << 1 + h = bit.bor(h, bit.rshift(l, 31)) -- h = h | (l >> 31) + l = bit.lshift(l, 1) + if(tmp ~= 0) then + l = bit.bor(l, 1) + end - h = bit.band(h, 0xFFFFFFFF) - l = bit.band(l, 0xFFFFFFFF) + h = bit.band(h, 0xFFFFFFFF) + l = bit.band(l, 0xFFFFFFFF) - return h, l + return h, l end @@ -177,15 +177,15 @@ end --@param port The port to check --@return true if the port is blacklisted, false otherwise 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) - l = bit.lshift(1, bit.band(r, 0x1f)) - r = bit.rshift(r, 5) + r = bit.rshift(port, 5) + l = bit.lshift(1, bit.band(r, 0x1f)) + r = bit.rshift(r, 5) - return (bit.band(blacklist[r + 1], l) ~= 0) + return (bit.band(blacklist[r + 1], l) ~= 0) end ---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 (floor((time - 345600) / 604800)) --@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 ports = {0, 0, 0, 0} - local v1, v2 - local port1, port2, shift1, shift2 - local i - local magic = 0x015A4E35 + local ports = {0, 0, 0, 0} + local v1, v2 + local port1, port2, shift1, shift2 + local i + 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) - repeat - -- Loop 10 times to generate the first pair of ports - for i = 0, 9, 1 do - v1, v2 = mul64(bit.band(v1, 0xFFFFFFFF), bit.band(magic, 0xFFFFFFFF)) + v1 = -(ip + 1) + repeat + -- Loop 10 times to generate the first pair of ports + for i = 0, 9, 1 do + v1, v2 = mul64(bit.band(v1, 0xFFFFFFFF), bit.band(magic, 0xFFFFFFFF)) - -- Add 1 to v1, handling overflows - if(v1 ~= 0xFFFFFFFF) then - v1 = v1 + 1 - else - v1 = 0 - v2 = v2 + 1 - end + -- Add 1 to v1, handling overflows + if(v1 ~= 0xFFFFFFFF) then + v1 = v1 + 1 + else + v1 = 0 + v2 = v2 + 1 + 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]) - end - until(is_blacklisted_port(ports[1]) == false and is_blacklisted_port(ports[2]) == false and ports[1] ~= ports[2]) + ports[(i % 2) + 1] = bit.bxor(bit.band(v2, 0xFFFF), ports[(i % 2) + 1]) + end + 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 - v1 = bit.bxor(v1, seed) + -- Update the accumlator with the seed + v1 = bit.bxor(v1, seed) - -- Loop 10 more times to generate the second pair of ports - repeat - for i = 0, 9, 1 do - v1, v2 = mul64(bit.band(v1, 0xFFFFFFFF), bit.band(magic, 0xFFFFFFFF)) + -- Loop 10 more times to generate the second pair of ports + repeat + for i = 0, 9, 1 do + v1, v2 = mul64(bit.band(v1, 0xFFFFFFFF), bit.band(magic, 0xFFFFFFFF)) - -- Add 1 to v1, handling overflows - if(v1 ~= 0xFFFFFFFF) then - v1 = v1 + 1 - else - v1 = 0 - v2 = v2 + 1 - end + -- Add 1 to v1, handling overflows + if(v1 ~= 0xFFFFFFFF) then + v1 = v1 + 1 + else + v1 = 0 + v2 = v2 + 1 + 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]) - end - until(is_blacklisted_port(ports[3]) == false and is_blacklisted_port(ports[4]) == false and ports[3] ~= ports[4]) + ports[(i % 2) + 3] = bit.bxor(bit.band(v2, 0xFFFF), ports[(i % 2) + 3]) + end + 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 ---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. --@return An integer representing the checksum. local function p2p_checksum(data) - local pos, i - local hash = #data + local pos, i + 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 - pos, i = bin.unpack(" 0xFFFFFFFF) then - -- Handle overflows - key2 = key2 + (bit.rshift(key1, 32)) - key2 = bit.band(key2, 0xFFFFFFFF) - key1 = bit.band(key1, 0xFFFFFFFF) - end - end + -- Update the key with 'k' + key1 = key1 + k + if(key1 > 0xFFFFFFFF) then + -- Handle overflows + key2 = key2 + (bit.rshift(key1, 32)) + key2 = bit.band(key2, 0xFFFFFFFF) + key1 = bit.band(key1, 0xFFFFFFFF) + end + end - return buf + return buf end ---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 -- is false, result is a string that indicates why the parse failed. function p2p_parse(packet) - local pos = 1 - local data = {} + local pos = 1 + local data = {} - -- Get the key - pos, data['key1'], data['key2'] = bin.unpack("false = no Conficker). If status is true, data is the table of information returned by -- Conficker. local function conficker_check(ip, port, protocol) - local status, packet - local socket - local response + local status, packet + local socket + local response - status, packet = p2p_create_packet(protocol) - if(status == false) then - return false, packet - end + status, packet = p2p_create_packet(protocol) + if(status == false) then + return false, packet + end - -- Try to connect to the first socket - socket = nmap.new_socket() - socket:set_timeout(5000) - status, response = socket:connect(ip, port, protocol) - if(status == false) then - return false, "Couldn't establish connection (" .. response .. ")" - end + -- Try to connect to the first socket + socket = nmap.new_socket() + socket:set_timeout(5000) + status, response = socket:connect(ip, port, protocol) + if(status == false) then + return false, "Couldn't establish connection (" .. response .. ")" + end - -- Send the packet - socket:send(packet) + -- Send the packet + socket:send(packet) - -- Read a response (2 bytes minimum, because that's the TCP length) - status, response = socket:receive_bytes(2) - if(status == false) then - return false, "Couldn't receive bytes: " .. response - elseif(response == "ERROR") then - return false, "Failed to receive data" - elseif(response == "TIMEOUT") then - return false, "Timeout" - elseif(response == "EOF") then - return false, "Couldn't connect" - end + -- Read a response (2 bytes minimum, because that's the TCP length) + status, response = socket:receive_bytes(2) + if(status == false) then + return false, "Couldn't receive bytes: " .. response + elseif(response == "ERROR") then + return false, "Failed to receive data" + elseif(response == "TIMEOUT") then + return false, "Timeout" + elseif(response == "EOF") then + return false, "Couldn't connect" + end - -- If it's TCP, get the length and make sure we have the full packet - if(protocol == "tcp") then - local _, length = bin.unpack(" (#response - 2) do - local response2 + while length > (#response - 2) do + local response2 - status, response2 = socket:receive_bytes(2) - if(status == false) then - return false, "Couldn't receive bytes: " .. response2 - elseif(response2 == "ERROR") then - return false, "Failed to receive data" - elseif(response2 == "TIMEOUT") then - return false, "Timeout" - elseif(response2 == "EOF") then - return false, "Couldn't connect" - end + status, response2 = socket:receive_bytes(2) + if(status == false) then + return false, "Couldn't receive bytes: " .. response2 + elseif(response2 == "ERROR") then + return false, "Failed to receive data" + elseif(response2 == "TIMEOUT") then + return false, "Timeout" + elseif(response2 == "EOF") then + return false, "Couldn't connect" + end - response = response .. response2 - end + response = response .. response2 + end - -- Remove the 'length' bytes - response = string.sub(response, 3) - end + -- Remove the 'length' bytes + response = string.sub(response, 3) + end - -- Close the socket - socket:close() + -- Close the socket + socket:close() - local status, result = p2p_parse(response) + local status, result = p2p_parse(response) - if(status == false) then - return false, "Data received, but wasn't Conficker data: " .. result - end + if(status == false) then + return false, "Data received, but wasn't Conficker data: " .. result + end - if(result['hash'] ~= result['real_hash']) then - return false, "Data received, but checksum was invalid (possibly INFECTED)" - end + if(result['hash'] ~= result['real_hash']) then + return false, "Data received, but checksum was invalid (possibly INFECTED)" + end - return true, "Received valid data", result + return true, "Received valid data", result end action = function(host) - local tcp_ports = {} - local udp_ports = {} - local response = {} - local i - local port, protocol - local count = 0 - local checks = 0 + local tcp_ports = {} + local udp_ports = {} + local response = {} + local i + local port, protocol + local count = 0 + local checks = 0 - -- Generate a complete list of valid ports - if(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then - for i = 1, 65535, 1 do - if(not(is_blacklisted_port(i))) then - local tcp = nmap.get_port_state(host, {number=i, protocol="tcp"}) - if(tcp ~= nil and tcp.state == "open") then - tcp_ports[i] = true - end + -- Generate a complete list of valid ports + if(nmap.registry.args.checkall == "true" or nmap.registry.args.checkall == "1") then + for i = 1, 65535, 1 do + if(not(is_blacklisted_port(i))) then + local tcp = nmap.get_port_state(host, {number=i, protocol="tcp"}) + if(tcp ~= nil and tcp.state == "open") then + tcp_ports[i] = true + end - local udp = nmap.get_port_state(host, {number=i, protocol="udp"}) - if(udp ~= nil and (udp.state == "open" or udp.state == "open|filtered")) then - udp_ports[i] = true - end - end - end - end + local udp = nmap.get_port_state(host, {number=i, protocol="udp"}) + if(udp ~= nil and (udp.state == "open" or udp.state == "open|filtered")) then + udp_ports[i] = true + end + end + end + end - -- Generate ports based on the ip and time - local seed = math.floor((os.time() - 345600) / 604800) - local ip = host.ip + -- Generate ports based on the ip and time + local seed = math.floor((os.time() - 345600) / 604800) + local ip = host.ip - -- Use the provided IP, if it exists - if(nmap.registry.args.realip ~= nil) then - ip = nmap.registry.args.realip - end + -- Use the provided IP, if it exists + if(nmap.registry.args.realip ~= nil) then + ip = nmap.registry.args.realip + end - -- Reverse the IP's endianness - ip = ipOps.todword(ip) - ip = bin.pack(">I", ip) - local _ - _, ip = bin.unpack("I", ip) + local _ + _, ip = bin.unpack(" 1) then - table.insert(response, string.format("%d/%d checks are positive: Host is CLEAN or ports are blocked", count, checks)) - else - response = '' - end - else - table.insert(response, string.format("%d/%d checks are positive: Host is likely INFECTED", count, checks)) - end + -- Check how many INFECTED hits we got + if(count == 0) 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)) + else + response = '' + end + else + table.insert(response, string.format("%d/%d checks are positive: Host is likely INFECTED", count, checks)) + end - return stdnse.format_output(true, response) + return stdnse.format_output(true, response) end diff --git a/scripts/path-mtu.nse b/scripts/path-mtu.nse index d93906d34..9c6cbbfcf 100644 --- a/scripts/path-mtu.nse +++ b/scripts/path-mtu.nse @@ -57,346 +57,346 @@ local RETRIES = 1 -- here since we skip down the list based on the outgoing interface -- so its no harm. local MTUS = { - 65535, - 32000, - 17914, - 8166, - 4352, - 2002, - 1492, - 1006, - 508, - 296, - 68 + 65535, + 32000, + 17914, + 8166, + 4352, + 2002, + 1492, + 1006, + 508, + 296, + 68 } -- 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. local searchmtu = function(cidx, new) - if new == 0 then - return cidx - end + if new == 0 then + return cidx + end - while cidx <= #MTUS do - if new >= MTUS[cidx] then - if new ~= MTUS[cidx] then - table.insert(MTUS, cidx, new) - end - return cidx - end - cidx = cidx + 1 - end - return cidx + while cidx <= #MTUS do + if new >= MTUS[cidx] then + if new ~= MTUS[cidx] then + table.insert(MTUS, cidx, new) + end + return cidx + end + cidx = cidx + 1 + end + return cidx end local dport = function(ip) - if ip.ip_p == IPPROTO_TCP then - return ip.tcp_dport - elseif ip.ip_p == IPPROTO_UDP then - return ip.udp_dport - end + if ip.ip_p == IPPROTO_TCP then + return ip.tcp_dport + elseif ip.ip_p == IPPROTO_UDP then + return ip.udp_dport + end end local sport = function(ip) - if ip.ip_p == IPPROTO_TCP then - return ip.tcp_sport - elseif ip.ip_p == IPPROTO_UDP then - return ip.udp_sport - end + if ip.ip_p == IPPROTO_TCP then + return ip.tcp_sport + elseif ip.ip_p == IPPROTO_UDP then + return ip.udp_sport + end end -- Checks how we should react to this packet 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.icmp_type ~= 3 then - return "recap" - end - -- Port Unreachable - if ip.icmp_code == 3 then - local is = ip.buf:sub(ip.icmp_offset + 9) - local ip2 = packet.Packet:new(is, is:len()) + if ip.ip_p == IPPROTO_ICMP then + if ip.icmp_type ~= 3 then + return "recap" + end + -- Port Unreachable + if ip.icmp_code == 3 then + local is = ip.buf:sub(ip.icmp_offset + 9) + local ip2 = packet.Packet:new(is, is:len()) - -- Check sent packet against ICMP payload - if ip2.ip_p ~= IPPROTO_UDP or - ip2.ip_p ~= orig.ip_p or - ip2.ip_bin_src ~= orig.ip_bin_src or - ip2.ip_bin_dst ~= orig.ip_bin_dst or - sport(ip2) ~= sport(orig) or - dport(ip2) ~= dport(orig) then - return "recap" - end + -- Check sent packet against ICMP payload + if ip2.ip_p ~= IPPROTO_UDP or + ip2.ip_p ~= orig.ip_p or + ip2.ip_bin_src ~= orig.ip_bin_src or + ip2.ip_bin_dst ~= orig.ip_bin_dst or + sport(ip2) ~= sport(orig) or + dport(ip2) ~= dport(orig) then + return "recap" + end - return "gotreply" - end - -- Frag needed, DF set - if ip.icmp_code == 4 then - local val = ip:u16(ip.icmp_offset + 6) - return "nextmtu", val - end - return "recap" - end + return "gotreply" + end + -- Frag needed, DF set + if ip.icmp_code == 4 then + local val = ip:u16(ip.icmp_offset + 6) + return "nextmtu", val + end + return "recap" + end - if ip.ip_p ~= orig.ip_p or - ip.ip_bin_src ~= orig.ip_bin_dst or - ip.ip_bin_dst ~= orig.ip_bin_src or - dport(ip) ~= sport(orig) or - sport(ip) ~= dport(orig) then - return "recap" - end + if ip.ip_p ~= orig.ip_p or + ip.ip_bin_src ~= orig.ip_bin_dst or + ip.ip_bin_dst ~= orig.ip_bin_src or + dport(ip) ~= sport(orig) or + sport(ip) ~= dport(orig) then + return "recap" + end - return "gotreply" + return "gotreply" end -- This is all we can use since we can get various protocols back from -- different hosts local check = function(layer3) - local ip = packet.Packet:new(layer3, layer3:len()) - return bin.pack('A', ip.ip_bin_dst) + local ip = packet.Packet:new(layer3, layer3:len()) + return bin.pack('A', ip.ip_bin_dst) end -- Updates a packet's info and calculates checksum local updatepkt = function(ip) - if ip.ip_p == IPPROTO_TCP then - ip:tcp_set_sport(math.random(0x401, 0xffff)) - ip:tcp_set_seq(math.random(1, 0x7fffffff)) - ip:tcp_count_checksum() - elseif ip.ip_p == IPPROTO_UDP then - ip:udp_set_sport(math.random(0x401, 0xffff)) - ip:udp_set_length(ip.ip_len - ip.ip_hl * 4) - ip:udp_count_checksum() - end - ip:ip_count_checksum() + if ip.ip_p == IPPROTO_TCP then + ip:tcp_set_sport(math.random(0x401, 0xffff)) + ip:tcp_set_seq(math.random(1, 0x7fffffff)) + ip:tcp_count_checksum() + elseif ip.ip_p == IPPROTO_UDP then + ip:udp_set_sport(math.random(0x401, 0xffff)) + ip:udp_set_length(ip.ip_len - ip.ip_hl * 4) + ip:udp_count_checksum() + end + ip:ip_count_checksum() end -- Set up packet header and data to satisfy a certain MTU local setmtu = function(pkt, mtu) - if pkt.ip_len < mtu then - pkt.buf = pkt.buf .. string.rep("\0", mtu - pkt.ip_len) - else - pkt.buf = pkt.buf:sub(1, mtu) - end + if pkt.ip_len < mtu then + pkt.buf = pkt.buf .. string.rep("\0", mtu - pkt.ip_len) + else + pkt.buf = pkt.buf:sub(1, mtu) + end - pkt:ip_set_len(mtu) - pkt.packet_length = mtu - updatepkt(pkt) + pkt:ip_set_len(mtu) + pkt.packet_length = mtu + updatepkt(pkt) end local basepkt = function(proto) - local ibin = bin.pack("H", - "4500 0014 0000 4000 8000 0000 0000 0000 0000 0000" - ) - local tbin = bin.pack("H", - "0000 0000 0000 0000 0000 0000 6002 0c00 0000 0000 0204 05b4" - ) - local ubin = bin.pack("H", - "0000 0000 0800 0000" - ) + local ibin = bin.pack("H", + "4500 0014 0000 4000 8000 0000 0000 0000 0000 0000" + ) + local tbin = bin.pack("H", + "0000 0000 0000 0000 0000 0000 6002 0c00 0000 0000 0204 05b4" + ) + local ubin = bin.pack("H", + "0000 0000 0800 0000" + ) - if proto == IPPROTO_TCP then - return ibin .. tbin - elseif proto == IPPROTO_UDP then - return ibin .. ubin - end + if proto == IPPROTO_TCP then + return ibin .. tbin + elseif proto == IPPROTO_UDP then + return ibin .. ubin + end end -- Creates a Packet object for the given proto and port local genericpkt = function(host, proto, port) - local pkt = basepkt(proto) - local ip = packet.Packet:new(pkt, pkt:len()) + local pkt = basepkt(proto) + local ip = packet.Packet:new(pkt, pkt:len()) - ip:ip_set_bin_src(host.bin_ip_src) - ip:ip_set_bin_dst(host.bin_ip) + ip:ip_set_bin_src(host.bin_ip_src) + ip:ip_set_bin_dst(host.bin_ip) - ip:set_u8(ip.ip_offset + 9, proto) - ip.ip_p = proto + ip:set_u8(ip.ip_offset + 9, proto) + ip.ip_p = proto - ip:ip_set_len(pkt:len()) + ip:ip_set_len(pkt:len()) - if proto == IPPROTO_TCP then - ip:tcp_parse(false) - ip:tcp_set_dport(port) - elseif proto == IPPROTO_UDP then - ip:udp_parse(false) - ip:udp_set_dport(port) - end + if proto == IPPROTO_TCP then + ip:tcp_parse(false) + ip:tcp_set_dport(port) + elseif proto == IPPROTO_UDP then + ip:udp_parse(false) + ip:udp_set_dport(port) + end - updatepkt(ip) + updatepkt(ip) - return ip + return ip end local ipproto = function(p) - if p == "tcp" then - return IPPROTO_TCP - elseif p == "udp" then - return IPPROTO_UDP - end - return -1 + if p == "tcp" then + return IPPROTO_TCP + elseif p == "udp" then + return IPPROTO_UDP + end + return -1 end -- Determines how to probe local getprobe = function(host) - local combos = { - { "tcp", "open" }, - { "tcp", "closed" }, - -- udp/open probably only happens when Nmap sends proper - -- payloads, which doesn't happen in here - { "udp", "closed" } - } - local proto = nil - local port = nil + local combos = { + { "tcp", "open" }, + { "tcp", "closed" }, + -- udp/open probably only happens when Nmap sends proper + -- payloads, which doesn't happen in here + { "udp", "closed" } + } + local proto = nil + local port = nil - for _, c in ipairs(combos) do - port = nmap.get_ports(host, nil, c[1], c[2]) - if port then - proto = c[1] - break - end - end + for _, c in ipairs(combos) do + port = nmap.get_ports(host, nil, c[1], c[2]) + if port then + proto = c[1] + break + end + end - return proto, port + return proto, port end -- Sets necessary probe data in registry local setreg = function(host, proto, port) - host.registry['pathmtuprobe'] = { - ['proto'] = proto, - ['port'] = port - } + host.registry['pathmtuprobe'] = { + ['proto'] = proto, + ['port'] = port + } end hostrule = function(host) - if not nmap.is_privileged() then - nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} - if not nmap.registry[SCRIPT_NAME].rootfail then - stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) - end - nmap.registry[SCRIPT_NAME].rootfail = true - return nil - end + if not nmap.is_privileged() then + nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} + if not nmap.registry[SCRIPT_NAME].rootfail then + stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) + end + nmap.registry[SCRIPT_NAME].rootfail = true + return nil + end - if nmap.address_family() ~= 'inet' then - stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) - return false - end - if not (host.interface and host.interface_mtu) then - return false - end - local proto, port = getprobe(host) - if not (proto and port) then - return false - end - setreg(host, proto, port.number) - return true + if nmap.address_family() ~= 'inet' then + stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) + return false + end + if not (host.interface and host.interface_mtu) then + return false + end + local proto, port = getprobe(host) + if not (proto and port) then + return false + end + setreg(host, proto, port.number) + return true end action = function(host) - local m, r - local gotit = false - local mtuset - local sock = nmap.new_dnet() - local pcap = nmap.new_socket() - local proto = host.registry['pathmtuprobe']['proto'] - local port = host.registry['pathmtuprobe']['port'] - local saddr = packet.toip(host.bin_ip_src) - local daddr = packet.toip(host.bin_ip) - local try = nmap.new_try() - local status, pkt, ip + local m, r + local gotit = false + local mtuset + local sock = nmap.new_dnet() + local pcap = nmap.new_socket() + local proto = host.registry['pathmtuprobe']['proto'] + local port = host.registry['pathmtuprobe']['port'] + local saddr = packet.toip(host.bin_ip_src) + local daddr = packet.toip(host.bin_ip) + local try = nmap.new_try() + 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, - -- simply bump up the host's calculated timeout value. Most replies - -- should come from routers along the path, fragmentation reassembly - -- 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 - -- it 1.5*timeout to play it safer. - pcap:set_timeout(1.5 * host.times.timeout * 1000) + -- Since we're sending potentially large amounts of data per packet, + -- simply bump up the host's calculated timeout value. Most replies + -- should come from routers along the path, fragmentation reassembly + -- 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 + -- it 1.5*timeout to play it safer. + 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 - setmtu(pkt, MTUS[m]) + while m <= #MTUS do + setmtu(pkt, MTUS[m]) - r = 0 - status = false - while true do - if not status then - if not sock:ip_send(pkt.buf, host) then - -- Got a send error, perhaps EMSGSIZE - -- when we don't know our interface's - -- MTU. Drop an MTU and keep trying. - break - end - end + r = 0 + status = false + while true do + if not status then + if not sock:ip_send(pkt.buf, host) then + -- Got a send error, perhaps EMSGSIZE + -- when we don't know our interface's + -- MTU. Drop an MTU and keep trying. + break + end + end - local test = bin.pack('A', pkt.ip_bin_src) - local status, length, _, layer3 = pcap:pcap_receive() - while status and test ~= check(layer3) do - status, length, _, layer3 = pcap:pcap_receive() - end + local test = bin.pack('A', pkt.ip_bin_src) + local status, length, _, layer3 = pcap:pcap_receive() + while status and test ~= check(layer3) do + status, length, _, layer3 = pcap:pcap_receive() + end - if status then - local t, v = checkpkt(layer3, pkt) - if t == "gotreply" then - gotit = true - break - elseif t == "recap" then - elseif t == "nextmtu" then - if v == 0 then - -- Router didn't send its - -- next-hop MTU. Just drop - -- a level. - break - end - -- Lua's lack of a continue statement - -- for loop control sucks, so dec m - -- here as it's inc'd below. Ugh. - m = searchmtu(m, v) - 1 - mtuset = v - break - end - else - if r >= RETRIES then - break - end - r = r + 1 - end - end + if status then + local t, v = checkpkt(layer3, pkt) + if t == "gotreply" then + gotit = true + break + elseif t == "recap" then + elseif t == "nextmtu" then + if v == 0 then + -- Router didn't send its + -- next-hop MTU. Just drop + -- a level. + break + end + -- Lua's lack of a continue statement + -- for loop control sucks, so dec m + -- here as it's inc'd below. Ugh. + m = searchmtu(m, v) - 1 + mtuset = v + break + end + else + if r >= RETRIES then + break + end + r = r + 1 + end + end - if gotit then - break - end + if gotit then + break + end - m = m + 1 - end + m = m + 1 + end - pcap:close() - sock:ip_close() + pcap:close() + sock:ip_close() - if not gotit then - if nmap.debugging() > 0 then - return "Error: Unable to determine PMTU (no replies)" - end - return - end + if not gotit then + if nmap.debugging() > 0 then + return "Error: Unable to determine PMTU (no replies)" + end + return + end - if MTUS[m] == mtuset then - return "PMTU == " .. MTUS[m] - elseif m == 1 then - return "PMTU >= " .. MTUS[m] - else - return "" .. MTUS[m] .. " <= PMTU < " .. MTUS[m - 1] - end + if MTUS[m] == mtuset then + return "PMTU == " .. MTUS[m] + elseif m == 1 then + return "PMTU >= " .. MTUS[m] + else + return "" .. MTUS[m] .. " <= PMTU < " .. MTUS[m - 1] + end end diff --git a/scripts/qscan.nse b/scripts/qscan.nse index 3a05732a2..8e48e72fd 100644 --- a/scripts/qscan.nse +++ b/scripts/qscan.nse @@ -8,22 +8,22 @@ local tab = require "tab" local table = require "table" description = [[ - 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 - group collections of ports which are statistically different from other - groups. Ports being in different groups (or "families") may be due to - network mechanisms such as port forwarding to machines behind a NAT. +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 +group collections of ports which are statistically different from other +groups. Ports being in different groups (or "families") may be due to +network mechanisms such as port forwarding to machines behind a NAT. - In order to group these ports into different families, some statistical - 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 - have been recorded and these values have been computed, the Student's - 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 - statistically the same are grouped together in the same family. +In order to group these ports into different families, some statistical +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 +have been recorded and these values have been computed, the Student's +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 +statistically the same are grouped together in the same family. - This script is based on Doug Hoyte's Qscan documentation and patches - for Nmap. +This script is based on Doug Hoyte's Qscan documentation and patches +for Nmap. ]] -- 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 -- http://www.owlnet.rice.edu/~elec428/projects/tinv.c local tdist = { - -- 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 - { 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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 + -- 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 + { 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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 } -- cache ports to probe between the hostrule and the action function @@ -108,391 +108,391 @@ local qscanports local tinv = function(p, dof) - local din, pin + local din, pin - if dof >= 1 and dof <= 20 then - din = dof - elseif dof < 25 then - din = 20 - elseif dof < 30 then - din = 21 - elseif dof < 35 then - din = 22 - elseif dof < 40 then - din = 23 - elseif dof < 45 then - din = 24 - elseif dof < 50 then - din = 25 - elseif dof < 60 then - din = 26 - elseif dof < 70 then - din = 27 - elseif dof < 80 then - din = 28 - elseif dof < 90 then - din = 29 - elseif dof < 100 then - din = 30 - elseif dof >= 100 then - din = 31 - end + if dof >= 1 and dof <= 20 then + din = dof + elseif dof < 25 then + din = 20 + elseif dof < 30 then + din = 21 + elseif dof < 35 then + din = 22 + elseif dof < 40 then + din = 23 + elseif dof < 45 then + din = 24 + elseif dof < 50 then + din = 25 + elseif dof < 60 then + din = 26 + elseif dof < 70 then + din = 27 + elseif dof < 80 then + din = 28 + elseif dof < 90 then + din = 29 + elseif dof < 100 then + din = 30 + elseif dof >= 100 then + din = 31 + end - if p == 0.75 then - pin = 1 - elseif p == 0.9 then - pin = 2 - elseif p == 0.95 then - pin = 3 - elseif p == 0.975 then - pin = 4 - elseif p == 0.99 then - pin = 5 - elseif p == 0.995 then - pin = 6 - elseif p == 0.9995 then - pin = 7 - end + if p == 0.75 then + pin = 1 + elseif p == 0.9 then + pin = 2 + elseif p == 0.95 then + pin = 3 + elseif p == 0.975 then + pin = 4 + elseif p == 0.99 then + pin = 5 + elseif p == 0.995 then + pin = 6 + elseif p == 0.9995 then + pin = 7 + end - return tdist[din][pin] + return tdist[din][pin] end --- Calculates intermediate t statistic local tstat = function(n1, n2, u1, u2, v1, v2) - local dof = n1 + n2 - 2 - local a = (n1 + n2) / (n1 * n2) - --local b = ((n1 - 1) * (s1 * s1) + (n2 - 1) * (s2 * s2)) - local b = ((n1 - 1) * v1) + ((n2 - 1) * v2) - return math.abs(u1 - u2) / math.sqrt(a * (b / dof)) + local dof = n1 + n2 - 2 + local a = (n1 + n2) / (n1 * n2) + --local b = ((n1 - 1) * (s1 * s1) + (n2 - 1) * (s2 * s2)) + local b = ((n1 - 1) * v1) + ((n2 - 1) * v2) + return math.abs(u1 - u2) / math.sqrt(a * (b / dof)) end --- Pcap check -- @return Destination and source IP addresses and TCP ports local check = function(layer3) - 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) + 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) end --- Updates a TCP Packet object -- @param tcp The TCP object local updatepkt = function(tcp, dport) - tcp:tcp_set_sport(math.random(0x401, 0xffff)) - tcp:tcp_set_dport(dport) - tcp:tcp_set_seq(math.random(1, 0x7fffffff)) - tcp:tcp_count_checksum(tcp.ip_len) - tcp:ip_count_checksum() + tcp:tcp_set_sport(math.random(0x401, 0xffff)) + tcp:tcp_set_dport(dport) + tcp:tcp_set_seq(math.random(1, 0x7fffffff)) + tcp:tcp_count_checksum(tcp.ip_len) + tcp:ip_count_checksum() end --- Create a TCP Packet object -- @param host Host object -- @return TCP Packet object local genericpkt = function(host) - local pkt = bin.pack("H", - "4500 002c 55d1 0000 8006 0000 0000 0000" .. - "0000 0000 0000 0000 0000 0000 0000 0000" .. - "6002 0c00 0000 0000 0204 05b4" - ) + local pkt = bin.pack("H", + "4500 002c 55d1 0000 8006 0000 0000 0000" .. + "0000 0000 0000 0000 0000 0000 0000 0000" .. + "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_dst(host.bin_ip) + tcp:ip_set_bin_src(host.bin_ip_src) + tcp:ip_set_bin_dst(host.bin_ip) - updatepkt(tcp, 0) + updatepkt(tcp, 0) - return tcp + return tcp end --- Calculates "family" values for grouping -- @param stats Statistics table -- @param conf Confidence level local calcfamilies = function(stats, conf) - local i, j - local famidx = 0 - local stat - local crit + local i, j + local famidx = 0 + local stat + local crit - for _, i in pairs(stats) do repeat - if i.fam ~= -1 then - break - end + for _, i in pairs(stats) do repeat + if i.fam ~= -1 then + break + end - i.fam = famidx - famidx = famidx + 1 + i.fam = famidx + famidx = famidx + 1 - for _, j in pairs(stats) do repeat - if j.port == i.port or j.fam ~= -1 then - break - end + for _, j in pairs(stats) do repeat + if j.port == i.port or j.fam ~= -1 then + break + end - 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) + 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) - if stat < crit then - j.fam = i.fam - end - until true end - until true end + if stat < crit then + j.fam = i.fam + end + until true end + until true end end --- Builds report for output -- @param stats Array of port statistics -- @return Output report local report = function(stats) - local j - local outtab = tab.new() + local j + local outtab = tab.new() - tab.add(outtab, 1, "PORT") - tab.add(outtab, 2, "FAMILY") - tab.add(outtab, 3, "MEAN (us)") - tab.add(outtab, 4, "STDDEV") - tab.add(outtab, 5, "LOSS (%)") - tab.nextrow(outtab) + tab.add(outtab, 1, "PORT") + tab.add(outtab, 2, "FAMILY") + tab.add(outtab, 3, "MEAN (us)") + tab.add(outtab, 4, "STDDEV") + tab.add(outtab, 5, "LOSS (%)") + tab.nextrow(outtab) local port, fam, mean, stddev, loss - for _, j in pairs(stats) do - port = tostring(j.port) - fam = tostring(j.fam) - mean = string.format("%.2f", j.mean) - stddev = string.format("%.2f", math.sqrt(j.K / (j.num - 1))) - loss = string.format("%.1f%%", 100 * (1 - j.num / j.sent)) + for _, j in pairs(stats) do + port = tostring(j.port) + fam = tostring(j.fam) + mean = string.format("%.2f", j.mean) + stddev = string.format("%.2f", math.sqrt(j.K / (j.num - 1))) + loss = string.format("%.1f%%", 100 * (1 - j.num / j.sent)) - tab.add(outtab, 1, port) - tab.add(outtab, 2, fam) - tab.add(outtab, 3, mean) - tab.add(outtab, 4, stddev) - tab.add(outtab, 5, loss) - tab.nextrow(outtab) - end + tab.add(outtab, 1, port) + tab.add(outtab, 2, fam) + tab.add(outtab, 3, mean) + tab.add(outtab, 4, stddev) + tab.add(outtab, 5, loss) + tab.nextrow(outtab) + end - return tab.dump(outtab) + return tab.dump(outtab) end --- Returns option values based on script arguments and defaults -- @return Confidence level, delay and number of trips local getopts = function() - local conf, delay, numtrips = CONF, DELAY, NUMTRIPS - local bool, err - local k + local conf, delay, numtrips = CONF, DELAY, NUMTRIPS + local bool, err + local k - for _, k in ipairs({"qscan.confidence", "confidence"}) do - if nmap.registry.args[k] then - conf = tonumber(nmap.registry.args[k]) - break - end - end + for _, k in ipairs({"qscan.confidence", "confidence"}) do + if nmap.registry.args[k] then + conf = tonumber(nmap.registry.args[k]) + break + end + end - for _, k in ipairs({"qscan.delay", "delay"}) do - if nmap.registry.args[k] then - delay = stdnse.parse_timespec(nmap.registry.args[k]) - break - end - end + for _, k in ipairs({"qscan.delay", "delay"}) do + if nmap.registry.args[k] then + delay = stdnse.parse_timespec(nmap.registry.args[k]) + break + end + end - for _, k in ipairs({"qscan.numtrips", "numtrips"}) do - if nmap.registry.args[k] then - numtrips = tonumber(nmap.registry.args[k]) - break - end - end + for _, k in ipairs({"qscan.numtrips", "numtrips"}) do + if nmap.registry.args[k] then + numtrips = tonumber(nmap.registry.args[k]) + break + end + end - bool = true + bool = true - if conf ~= 0.75 and conf ~= 0.9 and - conf ~= 0.95 and conf ~= 0.975 and - conf ~= 0.99 and conf ~= 0.995 and conf ~= 0.9995 then - bool = false - err = "Invalid confidence level" - end + if conf ~= 0.75 and conf ~= 0.9 and + conf ~= 0.95 and conf ~= 0.975 and + conf ~= 0.99 and conf ~= 0.995 and conf ~= 0.9995 then + bool = false + err = "Invalid confidence level" + end - if not delay then - bool = false - err = "Invalid delay" - end + if not delay then + bool = false + err = "Invalid delay" + end - if numtrips < 3 then - bool = false - err = "Invalid number of trips (should be >= 3)" - end + if numtrips < 3 then + bool = false + err = "Invalid number of trips (should be >= 3)" + end - if bool then - return bool, conf, delay, numtrips - else - return bool, err - end + if bool then + return bool, conf, delay, numtrips + else + return bool, err + end end local table_extend = function(a, b) - local t = {} + local t = {} - for _, v in ipairs(a) do - t[#t + 1] = v - end - for _, v in ipairs(b) do - t[#t + 1] = v - end + for _, v in ipairs(a) do + t[#t + 1] = v + end + for _, v in ipairs(b) do + t[#t + 1] = v + end - return t + return t end --- Get ports to probe -- @param host Host object local getports = function(host, numopen, numclosed) - local open = {} - local closed = {} - local port + local open = {} + local closed = {} + local port - port = nil - while numopen < 0 or #open < numopen do - port = nmap.get_ports(host, port, "tcp", "open") - if not port then - break - end - open[#open + 1] = port.number - end - port = nil - while numclosed < 0 or #closed < numclosed do - port = nmap.get_ports(host, port, "tcp", "closed") - if not port then - break - end - closed[#closed + 1] = port.number - end + port = nil + while numopen < 0 or #open < numopen do + port = nmap.get_ports(host, port, "tcp", "open") + if not port then + break + end + open[#open + 1] = port.number + end + port = nil + while numclosed < 0 or #closed < numclosed do + port = nmap.get_ports(host, port, "tcp", "closed") + if not port then + break + end + closed[#closed + 1] = port.number + end - return table_extend(open, closed) + return table_extend(open, closed) end hostrule = function(host) - if not nmap.is_privileged() then - nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} - if not nmap.registry[SCRIPT_NAME].rootfail then - stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) - end - nmap.registry[SCRIPT_NAME].rootfail = true - return nil - end + if not nmap.is_privileged() then + nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} + if not nmap.registry[SCRIPT_NAME].rootfail then + stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME) + end + nmap.registry[SCRIPT_NAME].rootfail = true + return nil + end - local numopen, numclosed = NUMOPEN, NUMCLOSED + local numopen, numclosed = NUMOPEN, NUMCLOSED - if nmap.address_family() ~= 'inet' then - stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) - return false - end - if not host.interface then - return false - end + if nmap.address_family() ~= 'inet' then + stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME) + return false + end + if not host.interface then + return false + end - for _, k in ipairs({"qscan.numopen", "numopen"}) do - if nmap.registry.args[k] then - numopen = tonumber(nmap.registry.args[k]) - break - end - end + for _, k in ipairs({"qscan.numopen", "numopen"}) do + if nmap.registry.args[k] then + numopen = tonumber(nmap.registry.args[k]) + break + end + end - for _, k in ipairs({"qscan.numclosed", "numclosed"}) do - if nmap.registry.args[k] then - numclosed = tonumber(nmap.registry.args[k]) - break - end - end + for _, k in ipairs({"qscan.numclosed", "numclosed"}) do + if nmap.registry.args[k] then + numclosed = tonumber(nmap.registry.args[k]) + break + end + end - qscanports = getports(host, numopen, numclosed) - return (#qscanports > 1) + qscanports = getports(host, numopen, numclosed) + return (#qscanports > 1) end action = function(host) - local sock = nmap.new_dnet() - local pcap = nmap.new_socket() - local saddr = packet.toip(host.bin_ip_src) - local daddr = packet.toip(host.bin_ip) - local start - local rtt - local stats = {} - local try = nmap.new_try() + local sock = nmap.new_dnet() + local pcap = nmap.new_socket() + local saddr = packet.toip(host.bin_ip_src) + local daddr = packet.toip(host.bin_ip) + local start + local rtt + local stats = {} + 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 - -- extra time due to port forwarding or whathaveyou. Nmap has all - -- ready scanned this host, so the timing should have taken into - -- account some of the RTT differences, but I think it really depends - -- on how many ports were scanned and how many were forwarded where. - -- Play it safer here. - pcap:set_timeout(2 * host.times.timeout * 1000) + -- Simply double the calculated host timeout to account for possible + -- extra time due to port forwarding or whathaveyou. Nmap has all + -- ready scanned this host, so the timing should have taken into + -- account some of the RTT differences, but I think it really depends + -- on how many ports were scanned and how many were forwarded where. + -- Play it safer here. + pcap:set_timeout(2 * host.times.timeout * 1000) - local tcp = genericpkt(host) + local tcp = genericpkt(host) - for i = 1, numtrips do - for j, port in ipairs(qscanports) do + for i = 1, numtrips do + for j, port in ipairs(qscanports) do - updatepkt(tcp, port) + updatepkt(tcp, port) - if not stats[j] then - stats[j] = {} - stats[j].port = port - stats[j].num = 0 - stats[j].sent = 0 - stats[j].mean = 0 - stats[j].K = 0 - stats[j].fam = -1 - end + if not stats[j] then + stats[j] = {} + stats[j].port = port + stats[j].num = 0 + stats[j].sent = 0 + stats[j].mean = 0 + stats[j].K = 0 + stats[j].fam = -1 + 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 status, length, _, layer3, stop = pcap:pcap_receive() - while status and test ~= check(layer3) do - status, length, _, layer3, stop = pcap:pcap_receive() - end + 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() + while status and test ~= check(layer3) do + status, length, _, layer3, stop = pcap:pcap_receive() + end - if not stop then - -- probably a timeout, just grab current time - stop = stdnse.clock_us() - else - -- we use usecs - stop = stop * 1000000 - end + if not stop then + -- probably a timeout, just grab current time + stop = stdnse.clock_us() + else + -- we use usecs + stop = stop * 1000000 + end - rtt = stop - start + rtt = stop - start - if status then - -- update more stats on the port, Knuth-style - local delta - stats[j].num = stats[j].num + 1 - delta = rtt - stats[j].mean - stats[j].mean = stats[j].mean + delta / stats[j].num - stats[j].K = stats[j].K + delta * (rtt - stats[j].mean) - end + if status then + -- update more stats on the port, Knuth-style + local delta + stats[j].num = stats[j].num + 1 + delta = rtt - stats[j].mean + stats[j].mean = stats[j].mean + delta / stats[j].num + stats[j].K = stats[j].K + delta * (rtt - stats[j].mean) + end - -- Unlike qscan.cc which loops around while waiting for - -- the delay, I just sleep here (depending on rtt) - if rtt < (3 * delay) / 2 then - if rtt < (delay / 2) then - stdnse.sleep(((delay / 2) + math.random(0, delay) - rtt)) - else - stdnse.sleep(math.random((3 * delay) / 2 - rtt)) - end - end - end - end + -- Unlike qscan.cc which loops around while waiting for + -- the delay, I just sleep here (depending on rtt) + if rtt < (3 * delay) / 2 then + if rtt < (delay / 2) then + stdnse.sleep(((delay / 2) + math.random(0, delay) - rtt)) + else + stdnse.sleep(math.random((3 * delay) / 2 - rtt)) + end + end + end + end - sock:ip_close() - pcap:pcap_close() + sock:ip_close() + pcap:pcap_close() - -- sort by port number - table.sort(stats, function(t1, t2) return t1.port < t2.port end) + -- sort by port number + 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 diff --git a/scripts/smb-brute.nse b/scripts/smb-brute.nse index 0a8b4ae18..e8ff063b5 100644 --- a/scripts/smb-brute.nse +++ b/scripts/smb-brute.nse @@ -115,23 +115,23 @@ categories = {"intrusive", "brute"} local LIMIT = 5000 hostrule = function(host) - return smb.get_port(host) ~= nil + return smb.get_port(host) ~= nil end ---The possible result codes. These are simplified from the actual codes that SMB returns. local results = { - SUCCESS = 1, -- Login was successful - GUEST_ACCESS = 2, -- Login was successful, but was granted guest access - NOT_GRANTED = 3, -- Password was correct, but user wasn't allowed to log in (often happens with blank passwords) - DISABLED = 4, -- Password was correct, but user's account is disabled - EXPIRED = 5, -- Password was correct, but user's account is expired - CHANGE_PASSWORD = 6, -- Password was correct, but user can't log in without changing it - ACCOUNT_LOCKED = 7, -- User's account is locked out (hopefully not by us!) - ACCOUNT_LOCKED_NOW = 8, -- User's account just became locked out (oops!) - FAIL = 9, -- User's password was incorrect - INVALID_LOGON_HOURS = 10, -- Password was correct, but user's account has logon time restrictions in place - INVALID_WORKSTATION = 11 -- Password was correct, but user's account has workstation restrictions in place + SUCCESS = 1, -- Login was successful + GUEST_ACCESS = 2, -- Login was successful, but was granted guest access + NOT_GRANTED = 3, -- Password was correct, but user wasn't allowed to log in (often happens with blank passwords) + DISABLED = 4, -- Password was correct, but user's account is disabled + EXPIRED = 5, -- Password was correct, but user's account is expired + CHANGE_PASSWORD = 6, -- Password was correct, but user can't log in without changing it + ACCOUNT_LOCKED = 7, -- User's account is locked out (hopefully not by us!) + ACCOUNT_LOCKED_NOW = 8, -- User's account just became locked out (oops!) + FAIL = 9, -- User's password was incorrect + INVALID_LOGON_HOURS = 10, -- Password was correct, but user's account has logon time restrictions in place + INVALID_WORKSTATION = 11 -- Password was correct, but user's account has workstation restrictions in place } ---Strings for debugging output @@ -175,22 +175,22 @@ local special_passwords = { USERNAME, USERNAME_REVERSED } --@param set (optional) The set of letters to choose from. Default: upper, lower, numbers, and underscore. --@return The random string. local function get_random_string(length, set) - if(length == nil) then - length = 8 - end + if(length == nil) then + length = 8 + end - if(set == nil) then - set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_" - end + if(set == nil) then + set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_" + end - local str = "" + local str = "" - for i = 1, length, 1 do - local random = math.random(#set) - str = str .. string.sub(set, random, random) - end + for i = 1, length, 1 do + local random = math.random(#set) + str = str .. string.sub(set, random, random) + end - return str + return str end ---Splits a string in the form "domain\user" into domain and user. @@ -198,18 +198,18 @@ end --@return (domain, username) The domain and the username. If no domain was given, nil is returned -- for domain. local function split_domain(str) - local username, domain - local split = stdnse.strsplit("\\", str) + local username, domain + local split = stdnse.strsplit("\\", str) - if(#split > 1) then - domain = split[1] - username = split[2] - else - domain = nil - username = str - end + if(#split > 1) then + domain = split[1] + username = split[2] + else + domain = nil + username = str + end - return domain, username + return domain, username end ---Formats a username/password pair with an optional result. Just a way to keep things consistent @@ -220,43 +220,43 @@ end --@return A string representing the input values. local function format_result(username, password, result) - if(username == "") then - username = "" - end + if(username == "") then + username = "" + end - if(password == nil) then - password = "" - elseif(password == "") then - password = "" - end + if(password == nil) then + password = "" + elseif(password == "") then + password = "" + end - if(result == nil) then - return string.format("%s:%s", username, password) - else - return string.format("%s:%s => %s", username, password, result_strings[result]) - end + if(result == nil) then + return string.format("%s:%s", username, password) + else + return string.format("%s:%s => %s", username, password, result_strings[result]) + end end ---Decides which login type to use (lanman, ntlm, or other). Designed to keep things consistent. --@param hostinfo The hostinfo table. --@return A string representing the login type to use (that can be passed to SMB functions). local function get_type(hostinfo) - -- Check if the user requested a specific type - if(nmap.registry.args.smbtype ~= nil) then - return nmap.registry.args.smbtype - end + -- Check if the user requested a specific type + if(nmap.registry.args.smbtype ~= nil) then + return nmap.registry.args.smbtype + end - -- Otherwise, base the type on the operating system (TODO: other versions of Windows (7, 2008)) - -- 2k8 example: "Windows Server (R) 2008 Datacenter without Hyper-V 6001 Service Pack 1" - if(string.find(string.lower(hostinfo['os']), "vista") ~= nil) then - return "ntlm" - elseif(string.find(string.lower(hostinfo['os']), "2008") ~= nil) then - return "ntlm" - elseif(string.find(string.lower(hostinfo['os']), "Windows 7") ~= nil) then - return "ntlm" - end + -- Otherwise, base the type on the operating system (TODO: other versions of Windows (7, 2008)) + -- 2k8 example: "Windows Server (R) 2008 Datacenter without Hyper-V 6001 Service Pack 1" + if(string.find(string.lower(hostinfo['os']), "vista") ~= nil) then + return "ntlm" + elseif(string.find(string.lower(hostinfo['os']), "2008") ~= nil) then + return "ntlm" + elseif(string.find(string.lower(hostinfo['os']), "Windows 7") ~= nil) then + return "ntlm" + end - return "lm" + return "lm" end ---Stops the session, if one exists. This can be called as frequently as needed, it'll just return if no @@ -264,20 +264,20 @@ end --@param hostinfo The hostinfo table. --@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. local function stop_session(hostinfo) - local status, err + local status, err - if(hostinfo['smbstate'] ~= nil) then - stdnse.print_debug(2, "smb-brute: Stopping the SMB session") - status, err = smb.stop(hostinfo['smbstate']) - if(status == false) then - return false, err - end + if(hostinfo['smbstate'] ~= nil) then + stdnse.print_debug(2, "smb-brute: Stopping the SMB session") + status, err = smb.stop(hostinfo['smbstate']) + if(status == false) then + return false, err + end - hostinfo['smbstate'] = nil - end + hostinfo['smbstate'] = nil + end - return true + return true end ---Starts or restarts a SMB session with the host. Although this will automatically stop a session if @@ -285,20 +285,20 @@ end --@param hostinfo The hostinfo table. --@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. local function restart_session(hostinfo) - local status, err, smbstate + local status, err, smbstate - -- Stop the old session, if it exists - stop_session(hostinfo) + -- Stop the old session, if it exists + stop_session(hostinfo) - stdnse.print_debug(2, "smb-brute: Starting the SMB session") - status, smbstate = smb.start_ex(hostinfo['host'], true, nil, nil, nil, true) - if(status == false) then - return false, smbstate - end + stdnse.print_debug(2, "smb-brute: Starting the SMB session") + status, smbstate = smb.start_ex(hostinfo['host'], true, nil, nil, nil, true) + if(status == false) then + return false, smbstate + end - hostinfo['smbstate'] = smbstate + hostinfo['smbstate'] = smbstate - return true + return true end ---Attempts to log into an account, returning one of the results constants. Will always return to the @@ -314,53 +314,53 @@ end -- is a hash, this is ignored. --@return Result, an integer value from the results constants. local function check_login(hostinfo, username, password, logintype) - local result - local domain = "" - local smbstate = hostinfo['smbstate'] - if(logintype == nil) then - logintype = get_type(hostinfo) - end + local result + local domain = "" + local smbstate = hostinfo['smbstate'] + if(logintype == nil) then + logintype = get_type(hostinfo) + end - -- Determine if we have a password hash or a password - local status, err - if(#password == 32 or #password == 64 or #password == 65) then - -- It's a hash (note: we always use NTLM hashes) - status, err = smb.start_session(smbstate, smb.get_overrides(username, domain, nil, password, "ntlm"), false) - else - status, err = smb.start_session(smbstate, smb.get_overrides(username, domain, password, nil, logintype), false) - end + -- Determine if we have a password hash or a password + local status, err + if(#password == 32 or #password == 64 or #password == 65) then + -- It's a hash (note: we always use NTLM hashes) + status, err = smb.start_session(smbstate, smb.get_overrides(username, domain, nil, password, "ntlm"), false) + else + status, err = smb.start_session(smbstate, smb.get_overrides(username, domain, password, nil, logintype), false) + end - if(status == true) then - if(smbstate['is_guest'] == 1) then - result = results.GUEST_ACCESS - else - result = results.SUCCESS - end + if(status == true) then + if(smbstate['is_guest'] == 1) then + result = results.GUEST_ACCESS + else + result = results.SUCCESS + end - smb.logoff(smbstate) - else - if(err == "NT_STATUS_LOGON_TYPE_NOT_GRANTED") then - result = results.NOT_GRANTED - elseif(err == "NT_STATUS_ACCOUNT_LOCKED_OUT") then - result = results.ACCOUNT_LOCKED - elseif(err == "NT_STATUS_ACCOUNT_DISABLED") then - result = results.DISABLED - elseif(err == "NT_STATUS_PASSWORD_MUST_CHANGE") then - result = results.CHANGE_PASSWORD - elseif(err == "NT_STATUS_INVALID_LOGON_HOURS") then - result = results.INVALID_LOGON_HOURS - elseif(err == "NT_STATUS_INVALID_WORKSTATION") then - result = results.INVALID_WORKSTATION - elseif(err == "NT_STATUS_ACCOUNT_EXPIRED") then - result = results.EXPIRED - else - result = results.FAIL - end - end + smb.logoff(smbstate) + else + if(err == "NT_STATUS_LOGON_TYPE_NOT_GRANTED") then + result = results.NOT_GRANTED + elseif(err == "NT_STATUS_ACCOUNT_LOCKED_OUT") then + result = results.ACCOUNT_LOCKED + elseif(err == "NT_STATUS_ACCOUNT_DISABLED") then + result = results.DISABLED + elseif(err == "NT_STATUS_PASSWORD_MUST_CHANGE") then + result = results.CHANGE_PASSWORD + elseif(err == "NT_STATUS_INVALID_LOGON_HOURS") then + result = results.INVALID_LOGON_HOURS + elseif(err == "NT_STATUS_INVALID_WORKSTATION") then + result = results.INVALID_WORKSTATION + elseif(err == "NT_STATUS_ACCOUNT_EXPIRED") then + result = results.EXPIRED + else + result = results.FAIL + end + end ---io.write(string.format("Result: %s\n\n", result_strings[result])) + --io.write(string.format("Result: %s\n\n", result_strings[result])) - return result + return result end ---Determines whether or not a login was successful, based on what's known about the server's settings. This @@ -373,24 +373,24 @@ end -- that the password was valid. function is_positive_result(hostinfo, result) - -- If result is a FAIL, it's always bad - if(result == results.FAIL) then - return false - end + -- If result is a FAIL, it's always bad + if(result == results.FAIL) then + return false + end - -- If result matches what we discovered for invalid passwords, it's always bad - if(result == hostinfo['invalid_password']) then - return false - end + -- If result matches what we discovered for invalid passwords, it's always bad + if(result == hostinfo['invalid_password']) then + return false + end - -- If result was ACCOUNT_LOCKED, it's always bad (locked accounts should already be taken care of, but this - -- makes the function a bit more generic) - if(result == results.ACCOUNT_LOCKED) then - return false - end + -- If result was ACCOUNT_LOCKED, it's always bad (locked accounts should already be taken care of, but this + -- makes the function a bit more generic) + if(result == results.ACCOUNT_LOCKED) then + return false + end - -- Otherwise, it's good - return true + -- Otherwise, it's good + return true end ---Determines whether or not a login was "bad". A bad login is one where an account becomes locked out. @@ -402,13 +402,13 @@ end -- that the password was valid. function is_bad_result(hostinfo, result) - -- If result is LOCKED, it's always bad. - if(result == results.ACCOUNT_LOCKED or result == results.ACCOUNT_LOCKED_NOW) then - return true - end + -- If result is LOCKED, it's always bad. + if(result == results.ACCOUNT_LOCKED or result == results.ACCOUNT_LOCKED_NOW) then + return true + end - -- Otherwise, it's good - return false + -- Otherwise, it's good + return false end ---Count the number of one bits in a binary representation of the given number. This is used for case-sensitive @@ -417,16 +417,16 @@ end --@param num The number to count the ones for. --@return The number of ones in the number local function count_ones(num) - local count = 0 + local count = 0 - while num ~= 0 do - if(bit.band(num, 1) == 1) then - count = count + 1 - end - num = bit.rshift(num, 1) - end + while num ~= 0 do + if(bit.band(num, 1) == 1) then + count = count + 1 + end + num = bit.rshift(num, 1) + end - return count + return count end ---Converts a string's case based on a binary number. For every '1' bit, the character is uppercased, and for every '0' @@ -437,34 +437,34 @@ end -- too small it's effectively zero-padded. --@return The converted string. local function convert_case(str, num) - local pos = #str + local pos = #str - -- Don't bother with blank strings (we probably won't get here anyway, but it doesn't hurt) - if(str == "") then - return "" - end + -- Don't bother with blank strings (we probably won't get here anyway, but it doesn't hurt) + if(str == "") then + return "" + end - while(num ~= 0) do - -- Check if the bit we're at is '1' - if(bit.band(num, 1) == 1) then - -- Check if we're at the beginning or end (or both) of the string -- those are special cases - if(pos == #str and pos == 1) then - str = string.upper(string.sub(str, pos, pos)) - elseif(pos == #str) then - str = string.sub(str, 1, pos - 1) .. string.upper(string.sub(str, pos, pos)) - elseif(pos == 1) then - str = string.upper(string.sub(str, pos, pos)) .. string.sub(str, pos + 1, #str) - else - str = string.sub(str, 1, pos - 1) .. string.upper(string.sub(str, pos, pos)) .. string.sub(str, pos + 1, #str) - end - end + while(num ~= 0) do + -- Check if the bit we're at is '1' + if(bit.band(num, 1) == 1) then + -- Check if we're at the beginning or end (or both) of the string -- those are special cases + if(pos == #str and pos == 1) then + str = string.upper(string.sub(str, pos, pos)) + elseif(pos == #str) then + str = string.sub(str, 1, pos - 1) .. string.upper(string.sub(str, pos, pos)) + elseif(pos == 1) then + str = string.upper(string.sub(str, pos, pos)) .. string.sub(str, pos + 1, #str) + else + str = string.sub(str, 1, pos - 1) .. string.upper(string.sub(str, pos, pos)) .. string.sub(str, pos + 1, #str) + end + end - num = bit.rshift(num, 1) + num = bit.rshift(num, 1) - pos = pos - 1 - end + pos = pos - 1 + end - return str + return str end ---Attempts to determine the case of a password. This is done by trying every possible combination of upper and lowercase @@ -478,71 +478,71 @@ end --@return The password with the proper case, or the original password if it couldn't be determined (either the proper -- case wasn't found or the login type is incorrect). local function find_password_case(hostinfo, username, password) - -- Only do this if we're using lanman, otherwise we already have the proper password - if(get_type(hostinfo) ~= "lm") then - return password - end + -- Only do this if we're using lanman, otherwise we already have the proper password + if(get_type(hostinfo) ~= "lm") then + return password + end - -- Figure out how many possibilities exist - local max = math.pow(2, #password) - 1 + -- Figure out how many possibilities exist + local max = math.pow(2, #password) - 1 - -- Create an array of them, starting with all the values whose binary representation has no ones, then one one, then two ones, etc. - local ordered = {} + -- Create an array of them, starting with all the values whose binary representation has no ones, then one one, then two ones, etc. + local ordered = {} - -- Cheat a bit, by adding all lower then all upper right at the start - ordered = {0, max} + -- Cheat a bit, by adding all lower then all upper right at the start + ordered = {0, max} - -- Loop backwards from the length of the password to 0. At each spot, put all numbers that have that many '1' bits - for i = 1, #password - 1, 1 do - for j = max, 0, -1 do - if(count_ones(j) == i) then - table.insert(ordered, j) - end - end - end + -- Loop backwards from the length of the password to 0. At each spot, put all numbers that have that many '1' bits + for i = 1, #password - 1, 1 do + for j = max, 0, -1 do + if(count_ones(j) == i) then + table.insert(ordered, j) + end + end + end - -- Create the list of converted passwords - for i = 1, #ordered, 1 do - local thispassword = convert_case(password, ordered[i]) + -- Create the list of converted passwords + for i = 1, #ordered, 1 do + local thispassword = convert_case(password, ordered[i]) - -- We specify "ntlm" for the login type because it's case sensitive - local result = check_login(hostinfo, username, thispassword, 'ntlm') - if(is_positive_result(hostinfo, result)) then - return thispassword - end - end + -- We specify "ntlm" for the login type because it's case sensitive + local result = check_login(hostinfo, username, thispassword, 'ntlm') + if(is_positive_result(hostinfo, result)) then + return thispassword + end + end - -- Print an error message - stdnse.print_debug(1, "ERROR: smb-brute: Was unable to determine case of %s's password", username) + -- Print an error message + stdnse.print_debug(1, "ERROR: smb-brute: Was unable to determine case of %s's password", username) - -- If all else fails, just return the actual password (we probably shouldn't get here) - return password + -- If all else fails, just return the actual password (we probably shouldn't get here) + return password end ---Unless the user is ok with lockouts, check the lockout policy of the host. Take the most restrictive -- portion among the domains. Returns true if lockouts could happen, false otherwise. local function bad_lockout_policy(host) - -- If the user is ok with locking out accounts, just return - if(stdnse.get_script_args( "smblockout" )) then - stdnse.print_debug(1, "smb-brute: Not checking server's lockout policy") - return true, false - end + -- If the user is ok with locking out accounts, just return + if(stdnse.get_script_args( "smblockout" )) then + stdnse.print_debug(1, "smb-brute: Not checking server's lockout policy") + return true, false + end - local status, result = msrpc.get_domains(host) - if(not(status)) then - stdnse.print_debug(1, "smb-brute: Couldn't detect lockout policy: %s", result) - return false, "Couldn't retrieve lockout policy: " .. result - end + local status, result = msrpc.get_domains(host) + if(not(status)) then + stdnse.print_debug(1, "smb-brute: Couldn't detect lockout policy: %s", result) + return false, "Couldn't retrieve lockout policy: " .. result + end - for domain, data in pairs(result) do - if(data and data.lockout_threshold) then - stdnse.print_debug(1, "smb-brute: Server's lockout policy: lock out after %d attempts", data.lockout_threshold) - return true, true - end - end + for domain, data in pairs(result) do + if(data and data.lockout_threshold) then + stdnse.print_debug(1, "smb-brute: Server's lockout policy: lock out after %d attempts", data.lockout_threshold) + return true, true + end + end - stdnse.print_debug(1, "smb-brute: Server has no lockout policy") - return true, false + stdnse.print_debug(1, "smb-brute: Server has no lockout policy") + return true, false end ---Initializes and returns the hostinfo table. This includes queuing up the username and password lists, determining @@ -550,111 +550,111 @@ end -- --@param host The host object. local function initialize(host) - local os, result - local status, bad_lockout_policy_result - local hostinfo = {} + local os, result + local status, bad_lockout_policy_result + local hostinfo = {} - hostinfo['host'] = host - hostinfo['invalid_usernames'] = {} - hostinfo['locked_usernames'] = {} - hostinfo['accounts'] = {} - hostinfo['special_password'] = 1 + hostinfo['host'] = host + hostinfo['invalid_usernames'] = {} + hostinfo['locked_usernames'] = {} + hostinfo['accounts'] = {} + hostinfo['special_password'] = 1 - -- Get the OS (identifying windows versions tells us which hash to use) - result, os = smb.get_os(host) - if(result == false or os['os'] == nil) then - hostinfo['os'] = "" - else - hostinfo['os'] = os['os'] - end - stdnse.print_debug(1, "smb-brute: Remote operating system: %s", hostinfo['os']) + -- Get the OS (identifying windows versions tells us which hash to use) + result, os = smb.get_os(host) + if(result == false or os['os'] == nil) then + hostinfo['os'] = "" + else + hostinfo['os'] = os['os'] + end + stdnse.print_debug(1, "smb-brute: Remote operating system: %s", hostinfo['os']) - -- Check lockout policy - status, bad_lockout_policy_result = bad_lockout_policy(host) - if(not(status)) then - stdnse.print_debug(1, "smb-brute: WARNING: couldn't determine lockout policy: %s", bad_lockout_policy_result) - else - if(bad_lockout_policy_result) then - return false, "Account lockouts are enabled on the host. To continue (and risk lockouts), add --script-args=smblockout=1 -- for more information, run smb-enum-domains." - end - end + -- Check lockout policy + status, bad_lockout_policy_result = bad_lockout_policy(host) + if(not(status)) then + stdnse.print_debug(1, "smb-brute: WARNING: couldn't determine lockout policy: %s", bad_lockout_policy_result) + else + if(bad_lockout_policy_result) then + return false, "Account lockouts are enabled on the host. To continue (and risk lockouts), add --script-args=smblockout=1 -- for more information, run smb-enum-domains." + end + end - -- Attempt to enumerate users - stdnse.print_debug(1, "smb-brute: Trying to get user list from server") - local _ - hostinfo['have_user_list'], _, hostinfo['user_list'] = msrpc.get_user_list(host) - hostinfo['user_list_index'] = 1 - if(hostinfo['have_user_list'] and #hostinfo['user_list'] == 0) then - hostinfo['have_user_list'] = false - end + -- Attempt to enumerate users + stdnse.print_debug(1, "smb-brute: Trying to get user list from server") + local _ + hostinfo['have_user_list'], _, hostinfo['user_list'] = msrpc.get_user_list(host) + hostinfo['user_list_index'] = 1 + if(hostinfo['have_user_list'] and #hostinfo['user_list'] == 0) then + hostinfo['have_user_list'] = false + end - -- If the enumeration failed, try using the built-in list - if(not(hostinfo['have_user_list'])) then - stdnse.print_debug(1, "smb-brute: Couldn't enumerate users (normal for Windows XP and higher), using unpwdb initially") - status, hostinfo['user_list_default'] = unpwdb.usernames() - if(status == false) then - return false, "Couldn't open username file" - end - end + -- If the enumeration failed, try using the built-in list + if(not(hostinfo['have_user_list'])) then + stdnse.print_debug(1, "smb-brute: Couldn't enumerate users (normal for Windows XP and higher), using unpwdb initially") + status, hostinfo['user_list_default'] = unpwdb.usernames() + if(status == false) then + return false, "Couldn't open username file" + end + end - -- Open the password file - stdnse.print_debug(1, "smb-brute: Opening password list") - status, hostinfo['password_list'] = unpwdb.passwords() - if(status == false) then - return false, "Couldn't open password file" - end + -- Open the password file + stdnse.print_debug(1, "smb-brute: Opening password list") + status, hostinfo['password_list'] = unpwdb.passwords() + if(status == false) then + return false, "Couldn't open password file" + end - -- Start the SMB session - stdnse.print_debug(1, "smb-brute: Starting the initial SMB session") - local err - status, err = restart_session(hostinfo) - if(status == false) then - stop_session(hostinfo) - return false, err - end + -- Start the SMB session + stdnse.print_debug(1, "smb-brute: Starting the initial SMB session") + local err + status, err = restart_session(hostinfo) + if(status == false) then + stop_session(hostinfo) + return false, err + end - -- Some hosts will accept any username -- check for this by trying to log in with a totally random name. If the - -- server accepts it, it'll be impossible to bruteforce; if it gives us a weird result code, we have to remember - -- it. - hostinfo['invalid_username'] = check_login(hostinfo, get_random_string(8), get_random_string(8), "ntlm") - hostinfo['invalid_password'] = check_login(hostinfo, "Administrator", get_random_string(8), "ntlm") + -- Some hosts will accept any username -- check for this by trying to log in with a totally random name. If the + -- server accepts it, it'll be impossible to bruteforce; if it gives us a weird result code, we have to remember + -- it. + hostinfo['invalid_username'] = check_login(hostinfo, get_random_string(8), get_random_string(8), "ntlm") + hostinfo['invalid_password'] = check_login(hostinfo, "Administrator", get_random_string(8), "ntlm") - stdnse.print_debug(1, "smb-brute: Server's response to invalid usernames: %s", result_short_strings[hostinfo['invalid_username']]) - stdnse.print_debug(1, "smb-brute: Server's response to invalid passwords: %s", result_short_strings[hostinfo['invalid_password']]) + stdnse.print_debug(1, "smb-brute: Server's response to invalid usernames: %s", result_short_strings[hostinfo['invalid_username']]) + stdnse.print_debug(1, "smb-brute: Server's response to invalid passwords: %s", result_short_strings[hostinfo['invalid_password']]) - -- If either of these comes back as success, there's no way to tell what's valid/invalid - if(hostinfo['invalid_username'] == results.SUCCESS) then - stop_session(hostinfo) - return false, "Invalid username was accepted; unable to bruteforce" - end - if(hostinfo['invalid_password'] == results.SUCCESS) then - stop_session(hostinfo) - return false, "Invalid password was accepted; unable to bruteforce" - end + -- If either of these comes back as success, there's no way to tell what's valid/invalid + if(hostinfo['invalid_username'] == results.SUCCESS) then + stop_session(hostinfo) + return false, "Invalid username was accepted; unable to bruteforce" + end + if(hostinfo['invalid_password'] == results.SUCCESS) then + stop_session(hostinfo) + return false, "Invalid password was accepted; unable to bruteforce" + end - -- Print a message to the user if we can identify passwords - if(hostinfo['invalid_username'] ~= hostinfo['invalid_password']) then - stdnse.print_debug(1, "smb-brute: Invalid username and password response are different, so identifying valid accounts is possible") - end + -- Print a message to the user if we can identify passwords + if(hostinfo['invalid_username'] ~= hostinfo['invalid_password']) then + stdnse.print_debug(1, "smb-brute: Invalid username and password response are different, so identifying valid accounts is possible") + end - -- Print a warning message if invalid_username and invalid_password go to the same thing that isn't FAIL - if(hostinfo['invalid_username'] ~= results.FAIL and hostinfo['invalid_username'] == hostinfo['invalid_password']) then - stdnse.print_debug(1, "smb-brute: WARNING: Difficult to recognize invalid usernames/passwords; may not get good results") - end + -- Print a warning message if invalid_username and invalid_password go to the same thing that isn't FAIL + if(hostinfo['invalid_username'] ~= results.FAIL and hostinfo['invalid_username'] == hostinfo['invalid_password']) then + stdnse.print_debug(1, "smb-brute: WARNING: Difficult to recognize invalid usernames/passwords; may not get good results") + end - -- Restart the SMB connection so we have a clean slate - stdnse.print_debug(1, "smb-brute: Restarting the session before the bruteforce") - status, err = restart_session(hostinfo) - if(status == false) then - stop_session(hostinfo) - return false, err - end + -- Restart the SMB connection so we have a clean slate + stdnse.print_debug(1, "smb-brute: Restarting the session before the bruteforce") + status, err = restart_session(hostinfo) + if(status == false) then + stop_session(hostinfo) + return false, err + end - -- Stop the SMB session (we're going to let the scripts look after their own sessions) - stop_session(hostinfo) + -- Stop the SMB session (we're going to let the scripts look after their own sessions) + stop_session(hostinfo) - -- Return the results - return true, hostinfo + -- Return the results + return true, hostinfo end ---Retrieves the next password in the password database we're using. Will never return the empty string. @@ -663,28 +663,28 @@ end --@param hostinfo The hostinfo table (the password list is stored there). --@return The new password, or nil if the end of the list has been reached. local function get_next_password(hostinfo) - local new_password + local new_password - -- If we're out of special passwords, move onto actual ones - if(hostinfo['special_password'] > #special_passwords) then - -- Pick the next non-blank password from the list - repeat - new_password = hostinfo['password_list']() - until new_password ~= '' - else - -- Get the next non-blank password - new_password = special_passwords[hostinfo['special_password']] - hostinfo['special_password'] = hostinfo['special_password'] + 1 - end + -- If we're out of special passwords, move onto actual ones + if(hostinfo['special_password'] > #special_passwords) then + -- Pick the next non-blank password from the list + repeat + new_password = hostinfo['password_list']() + until new_password ~= '' + else + -- Get the next non-blank password + new_password = special_passwords[hostinfo['special_password']] + hostinfo['special_password'] = hostinfo['special_password'] + 1 + end - return new_password + return new_password end ---Reset to the first password. This is normally done when the user list changes. -- --@param hostinfo The hostinfo table. local function reset_password(hostinfo) - hostinfo['password_list']("reset") + hostinfo['password_list']("reset") end ---Retrieves the next username. This can be from the username database, or from an array stored in the @@ -694,42 +694,42 @@ end --@param hostinfo The hostinfo table --@return The next username, or nil if the end of the list has been reached. local function get_next_username(hostinfo) - local username + local username - repeat - if(hostinfo['have_user_list']) then - local index = hostinfo['user_list_index'] - hostinfo['user_list_index'] = hostinfo['user_list_index'] + 1 + repeat + if(hostinfo['have_user_list']) then + local index = hostinfo['user_list_index'] + hostinfo['user_list_index'] = hostinfo['user_list_index'] + 1 - username = hostinfo['user_list'][index] - if(username ~= nil) then - local _ - _, username = split_domain(username) - end + username = hostinfo['user_list'][index] + if(username ~= nil) then + local _ + _, username = split_domain(username) + end - else - username = hostinfo['user_list_default']() - end + else + username = hostinfo['user_list_default']() + end - -- Make the username lowercase (usernames aren't case sensitive, so making it lower case prevents duplicates) - if(username ~= nil) then - username = string.lower(username) - end + -- Make the username lowercase (usernames aren't case sensitive, so making it lower case prevents duplicates) + if(username ~= nil) then + username = string.lower(username) + end - until username == nil or (hostinfo['invalid_usernames'][username] ~= true and hostinfo['locked_usernames'][username] ~= true and hostinfo['accounts'][username] == nil) + until username == nil or (hostinfo['invalid_usernames'][username] ~= true and hostinfo['locked_usernames'][username] ~= true and hostinfo['accounts'][username] == nil) - return username + return username end ---Reset to the first username. -- --@param hostinfo The hostinfo table. local function reset_username(hostinfo) - if(hostinfo['have_user_list']) then - hostinfo['user_list_index'] = 1 - else - hostinfo['user_list_default']("reset") - end + if(hostinfo['have_user_list']) then + hostinfo['user_list_index'] = 1 + else + hostinfo['user_list_default']("reset") + end end ---Do a little trick to detect account lockouts without bringing every user to the lockout threshold -- bump the lockout counter of @@ -743,63 +743,63 @@ end -- * A valid user list isn't pulled, and we create a canary that doesn't exist (won't be as bad, though, because it means we also -- don't have every account on the server/domain function test_lockouts(hostinfo) - local i - local username = get_next_username(hostinfo) + local i + local username = get_next_username(hostinfo) - -- It's possible that every username was accounted for already, so our list is empty. - if(username == nil) then - return - end + -- It's possible that every username was accounted for already, so our list is empty. + if(username == nil) then + return + end - if(stdnse.get_script_args( "smblockout" )) then - return - end + if(stdnse.get_script_args( "smblockout" )) then + return + end - while(string.lower(username) == "administrator") do - username = get_next_username(hostinfo) - if(username == nil) then - return - end - end + while(string.lower(username) == "administrator") do + username = get_next_username(hostinfo) + if(username == nil) then + return + end + end - if(username ~= nil) then - -- Try logging in as the "canary" account - local canaries = nmap.registry.args.canaries - if(canaries == nil) then - canaries = 3 - else - canaries = tonumber(canaries) - end + if(username ~= nil) then + -- Try logging in as the "canary" account + local canaries = nmap.registry.args.canaries + if(canaries == nil) then + canaries = 3 + else + canaries = tonumber(canaries) + end - if(canaries > 0) then - stdnse.print_debug(1, "smb-brute: Detecting server lockout on '%s' with %d canaries", username, canaries) - end + if(canaries > 0) then + stdnse.print_debug(1, "smb-brute: Detecting server lockout on '%s' with %d canaries", username, canaries) + end local result - for i=1, canaries, 1 do - result = check_login(hostinfo, username, get_random_string(8), "ntlm") - end + for i=1, canaries, 1 do + result = check_login(hostinfo, username, get_random_string(8), "ntlm") + end - -- If the account just became locked (it's already been put on the 'valid' list), we're in trouble - if(result == results.LOCKED) then - -- If the canary just became locked, we're one step from locking out every account. Loop through the usernames and invalidate them to - -- prevent them from being locked out - stdnse.print_debug(1, "smb-brute: Canary (%s) became locked out -- aborting") + -- If the account just became locked (it's already been put on the 'valid' list), we're in trouble + if(result == results.LOCKED) then + -- If the canary just became locked, we're one step from locking out every account. Loop through the usernames and invalidate them to + -- prevent them from being locked out + stdnse.print_debug(1, "smb-brute: Canary (%s) became locked out -- aborting") - -- Add it to the locked username list (so it can be reported) - hostinfo['locked_usernames'][username] = true + -- Add it to the locked username list (so it can be reported) + hostinfo['locked_usernames'][username] = true - -- Mark all the usernames as invalid (a bit of a hack, but it's safer this way) - while(username ~= nil) do - stdnse.print_debug(1, "smb-brute: Marking '%s' as 'invalid'", username) - hostinfo['invalid_usernames'][username] = true - username = get_next_username(hostinfo) - end - end - end + -- Mark all the usernames as invalid (a bit of a hack, but it's safer this way) + while(username ~= nil) do + stdnse.print_debug(1, "smb-brute: Marking '%s' as 'invalid'", username) + hostinfo['invalid_usernames'][username] = true + username = get_next_username(hostinfo) + end + end + end - -- Go back to the beginning of the list - reset_username(hostinfo) + -- Go back to the beginning of the list + reset_username(hostinfo) end ---Attempts to validate the current list of usernames by logging in with a blank password, marking invalid ones (and ones that had @@ -815,81 +815,81 @@ end --@param hostinfo The hostinfo table. --@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. local function validate_usernames(hostinfo) - local status, err - local result - local username, password + local status, err + local result + local username, password - stdnse.print_debug(1, "smb-brute: Checking which account names exist (based on what goes to the 'guest' account)") + stdnse.print_debug(1, "smb-brute: Checking which account names exist (based on what goes to the 'guest' account)") - -- Start a session - status, err = restart_session(hostinfo) - if(status == false) then - return false, err - end + -- Start a session + status, err = restart_session(hostinfo) + if(status == false) then + return false, err + end - -- Make sure we start at the beginning - reset_username(hostinfo) + -- Make sure we start at the beginning + reset_username(hostinfo) - username = get_next_username(hostinfo) - while(username ~= nil) do - result = check_login(hostinfo, username, "", "ntlm") + username = get_next_username(hostinfo) + while(username ~= nil) do + result = check_login(hostinfo, username, "", "ntlm") - if(result ~= hostinfo['invalid_password'] and result == hostinfo['invalid_username']) then - -- If the account matches the value of 'invalid_username', but not the value of 'invalid_password', it's invalid - stdnse.print_debug(1, "smb-brute: Blank password for '%s' -> '%s' (invalid account)", username, result_short_strings[result]) - hostinfo['invalid_usernames'][username] = true + if(result ~= hostinfo['invalid_password'] and result == hostinfo['invalid_username']) then + -- If the account matches the value of 'invalid_username', but not the value of 'invalid_password', it's invalid + stdnse.print_debug(1, "smb-brute: Blank password for '%s' -> '%s' (invalid account)", username, result_short_strings[result]) + hostinfo['invalid_usernames'][username] = true - elseif(result == hostinfo['invalid_password']) then + elseif(result == hostinfo['invalid_password']) then - -- If the account matches the value of 'invalid_password', and 'invalid_password' is reliable, it's probably valid - if(hostinfo['invalid_username'] ~= results.FAIL and hostinfo['invalid_username'] == hostinfo['invalid_password']) then - stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (can't determine validity)", username, result_short_strings[result]) - else - stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (probably valid)", username, result_short_strings[result]) - end + -- If the account matches the value of 'invalid_password', and 'invalid_password' is reliable, it's probably valid + if(hostinfo['invalid_username'] ~= results.FAIL and hostinfo['invalid_username'] == hostinfo['invalid_password']) then + stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (can't determine validity)", username, result_short_strings[result]) + else + stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (probably valid)", username, result_short_strings[result]) + end - elseif(result == results.ACCOUNT_LOCKED) then - -- If the account is locked out, don't try it - hostinfo['locked_usernames'][username] = true - stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (locked out)", username, result_short_strings[result]) + elseif(result == results.ACCOUNT_LOCKED) then + -- If the account is locked out, don't try it + hostinfo['locked_usernames'][username] = true + stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (locked out)", username, result_short_strings[result]) - elseif(result == results.FAIL) then - -- If none of the standard options work, check if it's FAIL. If it's FAIL, there's an error somewhere (probably, the - -- 'administrator' username is changed so we're getting invalid data). - stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (may be valid)", username, result_short_strings[result]) + elseif(result == results.FAIL) then + -- If none of the standard options work, check if it's FAIL. If it's FAIL, there's an error somewhere (probably, the + -- 'administrator' username is changed so we're getting invalid data). + stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (may be valid)", username, result_short_strings[result]) - else - -- If none of those came up, either the password is legitimately blank, or any account works. Figure out what! - local new_result = check_login(hostinfo, username, get_random_string(14), "ntlm") - if(new_result == result) then - -- Any password works (often happens with 'guest' account) - stdnse.print_debug(1, "smb-brute: All passwords accepted for %s (goes to %s)", username, result_short_strings[result]) - status, err = found_account(hostinfo, username, "", result) - if(status == false) then - return false, err - end - else - -- Blank password worked, but not random one - status, err = found_account(hostinfo, username, "", result) - if(status == false) then - return false, err - end - end - end + else + -- If none of those came up, either the password is legitimately blank, or any account works. Figure out what! + local new_result = check_login(hostinfo, username, get_random_string(14), "ntlm") + if(new_result == result) then + -- Any password works (often happens with 'guest' account) + stdnse.print_debug(1, "smb-brute: All passwords accepted for %s (goes to %s)", username, result_short_strings[result]) + status, err = found_account(hostinfo, username, "", result) + if(status == false) then + return false, err + end + else + -- Blank password worked, but not random one + status, err = found_account(hostinfo, username, "", result) + if(status == false) then + return false, err + end + end + end - username = get_next_username(hostinfo) - end + username = get_next_username(hostinfo) + end - -- Start back at the beginning of the list - reset_username(hostinfo) + -- Start back at the beginning of the list + reset_username(hostinfo) - -- Check for lockouts - test_lockouts(hostinfo) + -- Check for lockouts + test_lockouts(hostinfo) - -- Stop the session - stop_session(hostinfo) + -- Stop the session + stop_session(hostinfo) - return true + return true end ---Marks an account as discovered. The login with this account doesn't have to be successful, but is_positive_result should @@ -905,68 +905,68 @@ end --@param result The result, as an integer constant. --@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. function found_account(hostinfo, username, password, result) - local status, err + local status, err - -- Save the username - hostinfo['accounts'][username] = {} - hostinfo['accounts'][username]['password'] = password - hostinfo['accounts'][username]['result'] = result + -- Save the username + hostinfo['accounts'][username] = {} + hostinfo['accounts'][username]['password'] = password + hostinfo['accounts'][username]['result'] = result - -- Save the account (smb will automatically decide if it's better than the account it already has) - if(result == results.SUCCESS) then - -- Stop the connection -- this lets us do some queries - status, err = stop_session(hostinfo) - if(status == false) then - return false, err - end + -- Save the account (smb will automatically decide if it's better than the account it already has) + if(result == results.SUCCESS) then + -- Stop the connection -- this lets us do some queries + status, err = stop_session(hostinfo) + if(status == false) then + return false, err + end - -- Check if we have an 'admin' account - -- Try getting information about "IPC$". This determines whether or not the user is administrator - -- since only admins can get share info. Note that on Vista and up, unless UAC is disabled, all - -- accounts are non-admin. - local is_admin = smb.is_admin(hostinfo['host'], username, '', password, nil, nil) + -- Check if we have an 'admin' account + -- Try getting information about "IPC$". This determines whether or not the user is administrator + -- since only admins can get share info. Note that on Vista and up, unless UAC is disabled, all + -- accounts are non-admin. + local is_admin = smb.is_admin(hostinfo['host'], username, '', password, nil, nil) - -- Add the account - smb.add_account(hostinfo['host'], username, '', password, nil, nil, is_admin) + -- Add the account + smb.add_account(hostinfo['host'], username, '', password, nil, nil, is_admin) - -- Check lockout policy - local status, bad_lockout_policy_result = bad_lockout_policy(hostinfo['host']) - if(not(status)) then - stdnse.print_debug(1, "smb-brute: WARNING: couldn't determine lockout policy: %s", bad_lockout_policy_result) - else - if(bad_lockout_policy_result) then - return false, "Account lockouts are enabled on the host. To continue (and risk lockouts), add --script-args=smblockout=1 -- for more information, run smb-enum-domains." - end - end + -- Check lockout policy + local status, bad_lockout_policy_result = bad_lockout_policy(hostinfo['host']) + if(not(status)) then + stdnse.print_debug(1, "smb-brute: WARNING: couldn't determine lockout policy: %s", bad_lockout_policy_result) + else + if(bad_lockout_policy_result) then + return false, "Account lockouts are enabled on the host. To continue (and risk lockouts), add --script-args=smblockout=1 -- for more information, run smb-enum-domains." + end + end - -- If we haven't retrieved the real user list yet, do so - if(hostinfo['have_user_list'] == false) then - -- Attempt to enumerate users - stdnse.print_debug(1, "smb-brute: Trying to get user list from server using newly discovered account") - local _ - hostinfo['have_user_list'], _, hostinfo['user_list'] = msrpc.get_user_list(hostinfo['host']) - hostinfo['user_list_index'] = 1 - if(hostinfo['have_user_list'] and #hostinfo['user_list'] == 0) then - hostinfo['have_user_list'] = false - end + -- If we haven't retrieved the real user list yet, do so + if(hostinfo['have_user_list'] == false) then + -- Attempt to enumerate users + stdnse.print_debug(1, "smb-brute: Trying to get user list from server using newly discovered account") + local _ + hostinfo['have_user_list'], _, hostinfo['user_list'] = msrpc.get_user_list(hostinfo['host']) + hostinfo['user_list_index'] = 1 + if(hostinfo['have_user_list'] and #hostinfo['user_list'] == 0) then + hostinfo['have_user_list'] = false + end - -- If the list was found, let the user know and reset the password list - if(hostinfo['have_user_list']) then - stdnse.print_debug(1, "smb-brute: Found %d accounts to check!", #hostinfo['user_list']) - reset_password(hostinfo) + -- If the list was found, let the user know and reset the password list + if(hostinfo['have_user_list']) then + stdnse.print_debug(1, "smb-brute: Found %d accounts to check!", #hostinfo['user_list']) + reset_password(hostinfo) - -- Validate them (pick out the ones that can't possibly log in) - validate_usernames(hostinfo) - end - end + -- Validate them (pick out the ones that can't possibly log in) + validate_usernames(hostinfo) + end + end - -- Start the session again - status, err = restart_session(hostinfo) - if(status == false) then - return false, err - end + -- Start the session again + status, err = restart_session(hostinfo) + if(status == false) then + return false, err + end - end + end end ---This is the main function that does all the work (loops through the lists and checks the results). @@ -976,156 +976,156 @@ end -- is a table of passwords/results, indexed by the username and locked_accounts is a table indexed by locked -- usernames. local function go(host) - local status, err - local result, hostinfo - local password, temp_password, username - local response = {} + local status, err + local result, hostinfo + local password, temp_password, username + local response = {} - -- Initialize the hostinfo object, which sets up the initial variables - result, hostinfo = initialize(host) - if(result == false) then - return false, hostinfo - end + -- Initialize the hostinfo object, which sets up the initial variables + result, hostinfo = initialize(host) + if(result == false) then + return false, hostinfo + end - -- If invalid accounts don't give guest, we can determine the existence of users by trying to - -- log in with an invalid password and checking the value - status, err = validate_usernames(hostinfo) - if(status == false) then - return false, err - end + -- If invalid accounts don't give guest, we can determine the existence of users by trying to + -- log in with an invalid password and checking the value + status, err = validate_usernames(hostinfo) + if(status == false) then + return false, err + end - -- Start up the SMB session - status, err = restart_session(hostinfo) - if(status == false) then - return false, err - end + -- Start up the SMB session + status, err = restart_session(hostinfo) + if(status == false) then + return false, err + end - -- Loop through the password list - temp_password = get_next_password(hostinfo) - while(temp_password ~= nil) do - -- Loop through the user list - username = get_next_username(hostinfo) - while(username ~= nil) do - -- Check if it's a special case (we do this every loop because special cases are often - -- based on the username - if(temp_password == USERNAME) then - password = username ---io.write(string.format("Trying matching username/password (%s:%s)\n", username, password)) - elseif(temp_password == USERNAME_REVERSED) then - password = string.reverse(username) ---io.write(string.format("Trying reversed username/password (%s:%s)\n", username, password)) - else - password = temp_password - end + -- Loop through the password list + temp_password = get_next_password(hostinfo) + while(temp_password ~= nil) do + -- Loop through the user list + username = get_next_username(hostinfo) + while(username ~= nil) do + -- Check if it's a special case (we do this every loop because special cases are often + -- based on the username + if(temp_password == USERNAME) then + password = username + --io.write(string.format("Trying matching username/password (%s:%s)\n", username, password)) + elseif(temp_password == USERNAME_REVERSED) then + password = string.reverse(username) + --io.write(string.format("Trying reversed username/password (%s:%s)\n", username, password)) + else + password = temp_password + end ---io.write(string.format("%s:%s\n", username, password)) - local result = check_login(hostinfo, username, password, get_type(hostinfo)) + --io.write(string.format("%s:%s\n", username, password)) + local result = check_login(hostinfo, username, password, get_type(hostinfo)) - -- Check if the username was locked out - if(is_bad_result(hostinfo, result)) then - -- Add it to the list of locked usernames - hostinfo['locked_usernames'][username] = true + -- Check if the username was locked out + if(is_bad_result(hostinfo, result)) then + -- Add it to the list of locked usernames + hostinfo['locked_usernames'][username] = true - -- Unless the user requested to keep going, stop the check - if(not(stdnse.get_script_args( "smblockout" ))) then - -- Mark it as found, which is technically true - status, err = found_account(hostinfo, username, nil, results.ACCOUNT_LOCKED_NOW) - if(status == false) then - return err - end + -- Unless the user requested to keep going, stop the check + if(not(stdnse.get_script_args( "smblockout" ))) then + -- Mark it as found, which is technically true + status, err = found_account(hostinfo, username, nil, results.ACCOUNT_LOCKED_NOW) + if(status == false) then + return err + end - -- Let the user know that it went badly - stdnse.print_debug(1, "smb-brute: '%s' became locked out; stopping", username) + -- Let the user know that it went badly + stdnse.print_debug(1, "smb-brute: '%s' became locked out; stopping", username) - return true, hostinfo['accounts'], hostinfo['locked_usernames'] - else - stdnse.print_debug(1, "smb-brute: '%s' became locked out; continuing", username) - end - end + return true, hostinfo['accounts'], hostinfo['locked_usernames'] + else + stdnse.print_debug(1, "smb-brute: '%s' became locked out; continuing", username) + end + end - if(is_positive_result(hostinfo, result)) then - -- Reset the connection - stdnse.print_debug(2, "smb-brute: Found an account; resetting connection") - status, err = restart_session(hostinfo) - if(status == false) then - return false, err - end + if(is_positive_result(hostinfo, result)) then + -- Reset the connection + stdnse.print_debug(2, "smb-brute: Found an account; resetting connection") + status, err = restart_session(hostinfo) + if(status == false) then + return false, err + end - -- Find the case of the password, unless it's a hash - local case_password - if(not(#password == 32 or #password == 64 or #password == 65)) then - stdnse.print_debug(1, "smb-brute: Determining password's case (%s)", format_result(username, password)) - case_password = find_password_case(hostinfo, username, password, result) - stdnse.print_debug(1, "smb-brute: Result: %s", format_result(username, case_password)) - else - case_password = password - end + -- Find the case of the password, unless it's a hash + local case_password + if(not(#password == 32 or #password == 64 or #password == 65)) then + stdnse.print_debug(1, "smb-brute: Determining password's case (%s)", format_result(username, password)) + case_password = find_password_case(hostinfo, username, password, result) + stdnse.print_debug(1, "smb-brute: Result: %s", format_result(username, case_password)) + else + case_password = password + end - -- Take normal actions for finding an account - status, err = found_account(hostinfo, username, case_password, result) - if(status == false) then - return err - end - end - username = get_next_username(hostinfo) - end + -- Take normal actions for finding an account + status, err = found_account(hostinfo, username, case_password, result) + if(status == false) then + return err + end + end + username = get_next_username(hostinfo) + end - reset_username(hostinfo) - temp_password = get_next_password(hostinfo) - end + reset_username(hostinfo) + temp_password = get_next_password(hostinfo) + end - stop_session(hostinfo) - return true, hostinfo['accounts'], hostinfo['locked_usernames'] + stop_session(hostinfo) + return true, hostinfo['accounts'], hostinfo['locked_usernames'] end --_G.TRACEBACK = TRACEBACK or {} action = function(host) --- TRACEBACK[coroutine.running()] = true; + -- TRACEBACK[coroutine.running()] = true; - local status, result - local response = {} + local status, result + local response = {} - local username - local usernames = {} - local locked = {} - local i - local locked_result + local username + local usernames = {} + local locked = {} + local i + local locked_result - status, result, locked_result = go(host) - if(status == false) then - return stdnse.format_output(false, result) - end + status, result, locked_result = go(host) + if(status == false) then + return stdnse.format_output(false, result) + end - -- Put the usernames in their own table - for username in pairs(result) do - table.insert(usernames, username) - end + -- Put the usernames in their own table + for username in pairs(result) do + table.insert(usernames, username) + end - -- Sort the usernames alphabetically - table.sort(usernames) + -- Sort the usernames alphabetically + table.sort(usernames) - -- Display the usernames - if(#usernames == 0) then - table.insert(response, "No accounts found") - else - for i=1, #usernames, 1 do - local username = usernames[i] - table.insert(response, format_result(username, result[username]['password'], result[username]['result'])) - end - end + -- Display the usernames + if(#usernames == 0) then + table.insert(response, "No accounts found") + else + for i=1, #usernames, 1 do + local username = usernames[i] + table.insert(response, format_result(username, result[username]['password'], result[username]['result'])) + end + end - -- Make a list of locked accounts - for username in pairs(locked_result) do - table.insert(locked, username) - end - if(#locked > 0) then - -- Sort the list - table.sort(locked) + -- Make a list of locked accounts + for username in pairs(locked_result) do + table.insert(locked, username) + end + if(#locked > 0) then + -- Sort the list + table.sort(locked) - -- Display the list - table.insert(response, string.format("Locked accounts found: %s", stdnse.strjoin(", ", locked))) - end + -- Display the list + table.insert(response, string.format("Locked accounts found: %s", stdnse.strjoin(", ", locked))) + end - return stdnse.format_output(true, response) + return stdnse.format_output(true, response) end diff --git a/scripts/smb-check-vulns.nse b/scripts/smb-check-vulns.nse index 732169ca3..28e8499e8 100644 --- a/scripts/smb-check-vulns.nse +++ b/scripts/smb-check-vulns.nse @@ -119,7 +119,7 @@ dependencies = { hostrule = function(host) - return smb.get_port(host) ~= nil + return smb.get_port(host) ~= nil end local VULNERABLE = 1 @@ -149,88 +149,88 @@ local NOTUP = 8 -- UNKNOWN if there was an error (likely vulnerable), NOTRUN -- if this check was disabled, and INFECTED if it was patched by Conficker. function check_ms08_067(host) - if(nmap.registry.args.safe ~= nil) then - return true, NOTRUN - end - if(nmap.registry.args.unsafe == nil) then - return true, NOTRUN - end - local status, smbstate - local bind_result, netpathcompare_result + if(nmap.registry.args.safe ~= nil) then + return true, NOTRUN + end + if(nmap.registry.args.unsafe == nil) then + return true, NOTRUN + end + local status, smbstate + local bind_result, netpathcompare_result - -- Create the SMB session - status, smbstate = msrpc.start_smb(host, "\\\\BROWSER") - if(status == false) then - return false, smbstate - end + -- Create the SMB session + status, smbstate = msrpc.start_smb(host, "\\\\BROWSER") + if(status == false) then + return false, smbstate + end - -- Bind to SRVSVC service - status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, bind_result - end + -- Bind to SRVSVC service + status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, bind_result + end - -- Call netpathcanonicalize --- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\") + -- Call netpathcanonicalize + -- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\") - local path1 = "\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\..\\n" - local path2 = "\\n" - status, netpathcompare_result = msrpc.srvsvc_netpathcompare(smbstate, host.ip, path1, path2, 1, 0) + local path1 = "\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\..\\n" + local path2 = "\\n" + status, netpathcompare_result = msrpc.srvsvc_netpathcompare(smbstate, host.ip, path1, path2, 1, 0) - -- Stop the SMB session - msrpc.stop_smb(smbstate) + -- Stop the SMB session + msrpc.stop_smb(smbstate) - if(status == false) then - if(string.find(netpathcompare_result, "WERR_INVALID_PARAMETER") ~= nil) then - return true, INFECTED - elseif(string.find(netpathcompare_result, "INVALID_NAME") ~= nil) then - return true, PATCHED - else - return true, UNKNOWN, netpathcompare_result - end - end + if(status == false) then + if(string.find(netpathcompare_result, "WERR_INVALID_PARAMETER") ~= nil) then + return true, INFECTED + elseif(string.find(netpathcompare_result, "INVALID_NAME") ~= nil) then + return true, PATCHED + else + return true, UNKNOWN, netpathcompare_result + end + end - return true, VULNERABLE + return true, VULNERABLE end -- Help messages for the more common errors seen by the Conficker check. CONFICKER_ERROR_HELP = { - ["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://www.skullsecurity.org/blog/?p=209#comment-156 - -- "That means either it isn’t a Windows machine, or the service is - -- either crashed or not running. That may indicate a failed (or - -- successful) exploit attempt, or just a locked down system. - -- NT_STATUS_OBJECT_NAME_NOT_FOUND can be returned if the browser - -- service is disabled. There are at least two ways that can happen: - -- 1) The service itself is disabled in the services list. - -- 2) The registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Browser\Parameters\MaintainServerList - -- is set to Off/False/No rather than Auto or yes. - -- On these systems, if you reenable the browser service, then the - -- test will complete." - ["NT_STATUS_OBJECT_NAME_NOT_FOUND"] = -[[UNKNOWN; not Windows, or Windows with disabled browser service (CLEAN); or Windows with crashed browser service (possibly INFECTED). + ["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://www.skullsecurity.org/blog/?p=209#comment-156 + -- "That means either it isn’t a Windows machine, or the service is + -- either crashed or not running. That may indicate a failed (or + -- successful) exploit attempt, or just a locked down system. + -- NT_STATUS_OBJECT_NAME_NOT_FOUND can be returned if the browser + -- service is disabled. There are at least two ways that can happen: + -- 1) The service itself is disabled in the services list. + -- 2) The registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Browser\Parameters\MaintainServerList + -- is set to Off/False/No rather than Auto or yes. + -- On these systems, if you reenable the browser service, then the + -- test will complete." + ["NT_STATUS_OBJECT_NAME_NOT_FOUND"] = + [[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 |_ again. (Error NT_STATUS_OBJECT_NAME_NOT_FOUND)]], - -- http://www.skullsecurity.org/blog/?p=209#comment-100 - -- "That likely means that the server has been locked down, so we - -- don’t have access to the necessary pipe. Fortunately, that means - -- that neither does Conficker — NT_STATUS_ACCESS_DENIED probably - -- means you’re ok." - ["NT_STATUS_ACCESS_DENIED"] = -[[Likely CLEAN; access was denied. + -- http://www.skullsecurity.org/blog/?p=209#comment-100 + -- "That likely means that the server has been locked down, so we + -- don’t have access to the necessary pipe. Fortunately, that means + -- that neither does Conficker — NT_STATUS_ACCESS_DENIED probably + -- means you’re ok." + ["NT_STATUS_ACCESS_DENIED"] = + [[Likely CLEAN; access was denied. | If you have a login, try using --script-args=smbuser=xxx,smbpass=yyy | (replace xxx and yyy with your username and password). Also try |_ smbdomain=zzz if you know the domain. (Error NT_STATUS_ACCESS_DENIED)]], - -- The cause of these two is still unknown. - -- ["NT_STATUS_NOT_SUPPORTED"] = - -- [[]] - -- http://thatsbroken.com/?cat=5 (doesn't seem common) - -- ["NT_STATUS_REQUEST_NOT_ACCEPTED"] = - -- [[]] + -- The cause of these two is still unknown. + -- ["NT_STATUS_NOT_SUPPORTED"] = + -- [[]] + -- http://thatsbroken.com/?cat=5 (doesn't seem common) + -- ["NT_STATUS_REQUEST_NOT_ACCEPTED"] = + -- [[]] } ---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 -- INFECTED for infected or CLEAN for not infected. function check_conficker(host) - local status, smbstate - local bind_result, netpathcompare_result + local status, smbstate + local bind_result, netpathcompare_result - -- Create the SMB session - status, smbstate = msrpc.start_smb(host, "\\\\BROWSER", true) - if(status == false) then - return false, smbstate - end + -- Create the SMB session + status, smbstate = msrpc.start_smb(host, "\\\\BROWSER", true) + if(status == false) then + return false, smbstate + end - -- Bind to SRVSVC service - status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, bind_result - end + -- Bind to SRVSVC service + status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, bind_result + end - -- Try checking a valid string to find Conficker.D - local netpathcanonicalize_result, error_result - status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\") - if(status == true and netpathcanonicalize_result['can_path'] == 0x5c45005c) then - msrpc.stop_smb(smbstate) - return true, INFECTED2 - end + -- Try checking a valid string to find Conficker.D + local netpathcanonicalize_result, error_result + status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\") + if(status == true and netpathcanonicalize_result['can_path'] == 0x5c45005c) then + msrpc.stop_smb(smbstate) + return true, INFECTED2 + end - -- Try checking an illegal string ("\..\") to find Conficker.C and earlier - status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\..\\") + -- Try checking an illegal string ("\..\") to find Conficker.C and earlier + status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\..\\") - if(status == false) then - if(string.find(netpathcanonicalize_result, "INVALID_NAME")) then - msrpc.stop_smb(smbstate) - return true, CLEAN - elseif(string.find(netpathcanonicalize_result, "WERR_INVALID_PARAMETER") ~= nil) then - msrpc.stop_smb(smbstate) - return true, INFECTED - else - msrpc.stop_smb(smbstate) - return false, netpathcanonicalize_result - end - end + if(status == false) then + if(string.find(netpathcanonicalize_result, "INVALID_NAME")) then + msrpc.stop_smb(smbstate) + return true, CLEAN + elseif(string.find(netpathcanonicalize_result, "WERR_INVALID_PARAMETER") ~= nil) then + msrpc.stop_smb(smbstate) + return true, INFECTED + else + msrpc.stop_smb(smbstate) + return false, netpathcanonicalize_result + end + end - -- Stop the SMB session - msrpc.stop_smb(smbstate) + -- Stop the SMB session + msrpc.stop_smb(smbstate) - return true, CLEAN + return true, CLEAN end ---While writing smb-enum-sessions I discovered a repeatable null-pointer dereference @@ -303,130 +303,130 @@ end -- VULNERABLE for vulnerable or PATCHED for not vulnerable. If the check -- was skipped, NOTRUN is returned. function check_winreg_Enum_crash(host) - if(nmap.registry.args.safe ~= nil) then - return true, NOTRUN - end - if(nmap.registry.args.unsafe == nil) then - return true, NOTRUN - end + if(nmap.registry.args.safe ~= nil) then + return true, NOTRUN + end + if(nmap.registry.args.unsafe == nil) then + return true, NOTRUN + end - local i, j - local elements = {} - local status, bind_result, smbstate + local i, j + local elements = {} + local status, bind_result, smbstate - -- Create the SMB session - status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH) - if(status == false) then - return false, smbstate - end + -- Create the SMB session + status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH) + if(status == false) then + return false, smbstate + end - -- Bind to WINREG service - status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, bind_result - end + -- Bind to WINREG service + status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, bind_result + end local openhku_result - status, openhku_result = msrpc.winreg_openhku(smbstate) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, openhku_result - end + status, openhku_result = msrpc.winreg_openhku(smbstate) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, openhku_result + end - -- Loop through the keys under HKEY_USERS and grab the names - local enumkey_result - status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], 0, nil) - msrpc.stop_smb(smbstate) + -- Loop through the keys under HKEY_USERS and grab the names + local enumkey_result + status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], 0, nil) + msrpc.stop_smb(smbstate) - if(status == false) then - return true, VULNERABLE - end + if(status == false) then + return true, VULNERABLE + end - return true, PATCHED + return true, PATCHED end local function check_smbv2_dos(host) - local status, result + local status, result - if(nmap.registry.args.safe ~= nil) then - return true, NOTRUN - end - if(nmap.registry.args.unsafe == nil) then - return true, NOTRUN - end + if(nmap.registry.args.safe ~= nil) then + return true, NOTRUN + end + if(nmap.registry.args.unsafe == nil) then + return true, NOTRUN + end - -- 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 - string.char(0xff, 0x53, 0x4d, 0x42) .. -- Server Component: SMB - string.char(0x72, 0x00, 0x00, 0x00) .. -- Negociate Protocol - 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, 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(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(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(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(0x4d, 0x20, 0x30, 0x2e, 0x31, 0x32, 0x00, 0x02, 0x53, 0x4d, 0x42, 0x20, 0x32, 0x2e) .. - string.char(0x30, 0x30, 0x32, 0x00) + -- 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 + string.char(0xff, 0x53, 0x4d, 0x42) .. -- Server Component: SMB + string.char(0x72, 0x00, 0x00, 0x00) .. -- Negociate Protocol + 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, 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(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(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(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(0x4d, 0x20, 0x30, 0x2e, 0x31, 0x32, 0x00, 0x02, 0x53, 0x4d, 0x42, 0x20, 0x32, 0x2e) .. + string.char(0x30, 0x30, 0x32, 0x00) - local socket = nmap.new_socket() - if(socket == nil) then - return false, "Couldn't create socket" - end + local socket = nmap.new_socket() + if(socket == nil) then + return false, "Couldn't create socket" + end - status, result = socket:connect(host, 445) - if(status == false) then - socket:close() - return false, "Couldn't connect to host: " .. result - end + status, result = socket:connect(host, 445) + if(status == false) then + socket:close() + return false, "Couldn't connect to host: " .. result + end - status, result = socket:send(buf) - if(status == false) then - socket:close() - return false, "Couldn't send the buffer: " .. result - end + status, result = socket:send(buf) + if(status == false) then + socket:close() + return false, "Couldn't send the buffer: " .. result + end - -- Close the socket - socket:close() + -- Close the socket + socket:close() - -- Give it some time to crash - stdnse.print_debug(1, "smb-check-vulns: Waiting 5 seconds to see if Windows crashed") - stdnse.sleep(5) + -- Give it some time to crash + stdnse.print_debug(1, "smb-check-vulns: Waiting 5 seconds to see if Windows crashed") + stdnse.sleep(5) - -- Create a new socket - socket = nmap.new_socket() - if(socket == nil) then - return false, "Couldn't create socket" - end + -- Create a new socket + socket = nmap.new_socket() + if(socket == nil) then + return false, "Couldn't create socket" + end - -- Try and do something simple - stdnse.print_debug(1, "smb-check-vulns: Attempting to connect to the host") - socket:set_timeout(5000) - status, result = socket:connect(host, 445) + -- Try and do something simple + stdnse.print_debug(1, "smb-check-vulns: Attempting to connect to the host") + socket:set_timeout(5000) + status, result = socket:connect(host, 445) - -- Check the result - if(status == false or status == nil) then - stdnse.print_debug(1, "smb-check-vulns: Connect failed, host is likely vulnerable!") - socket:close() - return true, VULNERABLE - end + -- Check the result + if(status == false or status == nil) then + stdnse.print_debug(1, "smb-check-vulns: Connect failed, host is likely vulnerable!") + socket:close() + return true, VULNERABLE + end - -- Try sending something - stdnse.print_debug(1, "smb-check-vulns: Attempting to send data to the host") - status, result = socket:send("AAAA") - if(status == false or status == nil) then - stdnse.print_debug(1, "smb-check-vulns: Send failed, host is likely vulnerable!") - socket:close() - return true, VULNERABLE - end + -- Try sending something + stdnse.print_debug(1, "smb-check-vulns: Attempting to send data to the host") + status, result = socket:send("AAAA") + if(status == false or status == nil) then + stdnse.print_debug(1, "smb-check-vulns: Send failed, host is likely vulnerable!") + socket:close() + return true, VULNERABLE + end - stdnse.print_debug(1, "smb-check-vulns: Checks finished; host is likely not vulnerable.") - socket:close() - return true, PATCHED + stdnse.print_debug(1, "smb-check-vulns: Checks finished; host is likely not vulnerable.") + socket:close() + return true, PATCHED end @@ -442,56 +442,56 @@ end -- ** result == PATCHED for not vulnerable. -- ** result == NOTRUN if check skipped. function check_ms06_025(host) - --check for safety flag - if(nmap.registry.args.safe ~= nil) then - return true, NOTRUN - end - if(nmap.registry.args.unsafe == nil) then - return true, NOTRUN - end - --create the SMB session - --first we try with the "\router" pipe, then the "\srvsvc" pipe. - local status, smb_result, smbstate, err_msg - status, smb_result = msrpc.start_smb(host, msrpc.ROUTER_PATH) + --check for safety flag + if(nmap.registry.args.safe ~= nil) then + return true, NOTRUN + end + if(nmap.registry.args.unsafe == nil) then + return true, NOTRUN + end + --create the SMB session + --first we try with the "\router" pipe, then the "\srvsvc" pipe. + local status, smb_result, smbstate, err_msg + 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 - 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 - return false, NOTUP --if not accessible across both pipes then service is inactive - end + return false, NOTUP --if not accessible across both pipes then service is inactive end - smbstate = smb_result - --bind to RRAS service - local bind_result - status, bind_result = msrpc.bind(smbstate, msrpc.RASRPC_UUID, msrpc.RASRPC_VERSION, nil) - 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 + smbstate = smb_result + --bind to RRAS service + local bind_result + status, bind_result = msrpc.bind(smbstate, msrpc.RASRPC_UUID, msrpc.RASRPC_VERSION, nil) + 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 + 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 - 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 - else - return true, PATCHED - end + else + return true, PATCHED + end end ---Check the existence of ms07_029 vulnerability in Microsoft Dns Server service. @@ -505,47 +505,47 @@ end -- ** result == PATCHED for not vulnerable. -- ** result == NOTRUN if check skipped. function check_ms07_029(host) - --check for safety flag - if(nmap.registry.args.safe ~= nil) then - return true, NOTRUN + --check for safety flag + if(nmap.registry.args.safe ~= nil) then + 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 - 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 - else - return true, PATCHED - end + else + return true, PATCHED + end end ---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). --@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) - if(minimum_debug == nil) then - minimum_debug = 0 - end + if(minimum_debug == nil) then + minimum_debug = 0 + end - -- Check if we have appropriate verbosity/debug - if(nmap.verbosity() >= minimum_verbosity and nmap.debugging() >= minimum_debug) then - if(description == nil or description == '') then - return string.format("%s: %s", check, message) - else - return string.format("%s: %s (%s)", check, message, description) - end - else - return nil - end + -- Check if we have appropriate verbosity/debug + if(nmap.verbosity() >= minimum_verbosity and nmap.debugging() >= minimum_debug) then + if(description == nil or description == '') then + return string.format("%s: %s", check, message) + else + return string.format("%s: %s (%s)", check, message, description) + end + else + return nil + end end action = function(host) - local status, result, message - local response = {} + local status, result, message + local response = {} - -- Check for ms08-067 - status, result, message = check_ms08_067(host) - if(status == false) then - table.insert(response, get_response("MS08-067", "ERROR", result, 0, 1)) - else - if(result == VULNERABLE) then - table.insert(response, get_response("MS08-067", "VULNERABLE", nil, 0)) - elseif(result == UNKNOWN) then - table.insert(response, get_response("MS08-067", "LIKELY VULNERABLE", "host stopped responding", 1)) -- TODO: this isn't very accurate - elseif(result == NOTRUN) then - table.insert(response, get_response("MS08-067", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1)) - elseif(result == INFECTED) then - 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 + -- Check for ms08-067 + status, result, message = check_ms08_067(host) + if(status == false) then + table.insert(response, get_response("MS08-067", "ERROR", result, 0, 1)) + else + if(result == VULNERABLE) then + table.insert(response, get_response("MS08-067", "VULNERABLE", nil, 0)) + elseif(result == UNKNOWN) then + table.insert(response, get_response("MS08-067", "LIKELY VULNERABLE", "host stopped responding", 1)) -- TODO: this isn't very accurate + elseif(result == NOTRUN) then + table.insert(response, get_response("MS08-067", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1)) + elseif(result == INFECTED) then + table.insert(response, get_response("MS08-067", "NOT VULNERABLE", "likely by Conficker", 0)) 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 + table.insert(response, get_response("MS08-067", "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 + -- 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 - 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 + table.insert(response, get_response("Conficker", "UNKNOWN", result, 0, 1)) 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 diff --git a/scripts/smb-enum-processes.nse b/scripts/smb-enum-processes.nse index ea05dcc69..5de1bb70f 100644 --- a/scripts/smb-enum-processes.nse +++ b/scripts/smb-enum-processes.nse @@ -87,206 +87,206 @@ dependencies = {"smb-brute"} function psl_mode (list, i) - local mode + local mode - -- Decide connector for process. - if #list == 1 then - mode = "only" - elseif i == 1 then - mode = "first" - elseif i < #list then - mode = "middle" - else - mode = "last" - end + -- Decide connector for process. + if #list == 1 then + mode = "only" + elseif i == 1 then + mode = "first" + elseif i < #list then + mode = "middle" + else + mode = "last" + end - return mode + return mode end function psl_print (psl, lvl) - -- Print out table header. - local result = "" - if lvl == 2 then - result = result .. " PID PPID Priority Threads Handles\n" - result = result .. "----- ----- -------- ------- -------\n" - end + -- Print out table header. + local result = "" + if lvl == 2 then + result = result .. " PID PPID Priority Threads Handles\n" + result = result .. "----- ----- -------- ------- -------\n" + end - -- Find how many root processes there are. - local roots = {} - for i, ps in pairs(psl) do - if psl[ps.ppid] == nil or ps.ppid == ps.pid then - table.insert(roots, i) - end - end - table.sort(roots) + -- Find how many root processes there are. + local roots = {} + for i, ps in pairs(psl) do + if psl[ps.ppid] == nil or ps.ppid == ps.pid then + table.insert(roots, i) + end + end + table.sort(roots) - -- Create vertical sibling bars. - local bars = {} - if #roots ~= 1 then - table.insert(bars, 2) - end + -- Create vertical sibling bars. + local bars = {} + if #roots ~= 1 then + table.insert(bars, 2) + end - -- Print out each root of the tree. - for i, root in ipairs(roots) do - local mode = psl_mode(roots, i) - result = result .. psl_tree(psl, root, 0, bars, mode, lvl) - end + -- Print out each root of the tree. + for i, root in ipairs(roots) do + local mode = psl_mode(roots, i) + result = result .. psl_tree(psl, root, 0, bars, mode, lvl) + end - return result + return result end function psl_tree (psl, pid, column, bars, mode, lvl) - local ps = psl[pid] + local ps = psl[pid] - -- Delete vertical sibling link. - if mode == "last" then - table.remove(bars) - end + -- Delete vertical sibling link. + if mode == "last" then + table.remove(bars) + end - -- Print information table. - local info = "" - if lvl == 2 then - info = info .. string.format("% 5d ", ps.pid) - info = info .. string.format("% 5d ", ps.ppid) - info = info .. string.format("% 8d ", ps.prio) - info = info .. string.format("% 7d ", ps.thrd) - info = info .. string.format("% 7d ", ps.hndl) - end + -- Print information table. + local info = "" + if lvl == 2 then + info = info .. string.format("% 5d ", ps.pid) + info = info .. string.format("% 5d ", ps.ppid) + info = info .. string.format("% 8d ", ps.prio) + info = info .. string.format("% 7d ", ps.thrd) + info = info .. string.format("% 7d ", ps.hndl) + end - -- Print vertical sibling bars. - local prefix = "" - local i = 1 - for j = 1, column do - if bars[i] == j then - prefix = prefix .. "|" - i = i + 1 - else - prefix = prefix .. " " - end - end + -- Print vertical sibling bars. + local prefix = "" + local i = 1 + for j = 1, column do + if bars[i] == j then + prefix = prefix .. "|" + i = i + 1 + else + prefix = prefix .. " " + end + end - -- Strings used to separate processes from one another. - local separators = { - first = "`+-"; - last = " `-"; - middle = " +-"; - only = "`-"; - } + -- Strings used to separate processes from one another. + local separators = { + first = "`+-"; + last = " `-"; + middle = " +-"; + only = "`-"; + } - -- Format process itself. - local result = "\n" .. info .. prefix .. separators[mode] .. ps.name + -- Format process itself. + local result = "\n" .. info .. prefix .. separators[mode] .. ps.name - -- Find children of the process. - local children = {} - for child_pid, child in pairs(psl) do - if child_pid ~= pid and child.ppid == pid then - table.insert(children, child_pid) - end - end - table.sort(children) + -- Find children of the process. + local children = {} + for child_pid, child in pairs(psl) do + if child_pid ~= pid and child.ppid == pid then + table.insert(children, child_pid) + end + end + table.sort(children) - -- Add vertical sibling link between children. - column = column + #separators[mode] - if #children > 1 then - table.insert(bars, column + 2) - end + -- Add vertical sibling link between children. + column = column + #separators[mode] + if #children > 1 then + table.insert(bars, column + 2) + end - -- Format process's children. - for i, pid in ipairs(children) do - local mode = psl_mode(children, i) - result = result .. psl_tree(psl, pid, column, bars, mode, lvl) - end + -- Format process's children. + for i, pid in ipairs(children) do + local mode = psl_mode(children, i) + result = result .. psl_tree(psl, pid, column, bars, mode, lvl) + end - return result + return result end hostrule = function(host) - return smb.get_port(host) ~= nil + return smb.get_port(host) ~= nil end action = function(host) - -- Get the process list - local status, result = msrpcperformance.get_performance_data(host, "230") - if status == false then - if nmap.debugging() > 0 then - return "ERROR: " .. result - else - return nil - end - end + -- Get the process list + local status, result = msrpcperformance.get_performance_data(host, "230") + if status == false then + if nmap.debugging() > 0 then + return "ERROR: " .. result + else + return nil + end + end - -- Get the process table - local process = result["Process"] + -- Get the process table + local process = result["Process"] - -- Put the processes into an array, and sort them by pid. - local names = {} - for i, v in pairs(process) do - if i ~= "_Total" then - names[#names + 1] = i - end - end - table.sort(names, function (a, b) return process[a]["ID Process"] < process[b]["ID Process"] end) + -- Put the processes into an array, and sort them by pid. + local names = {} + for i, v in pairs(process) do + if i ~= "_Total" then + names[#names + 1] = i + end + 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 - -- to the name (so we can look it up easily when we need to). - local process_id = {} - for i, v in pairs(process) do - process_id[v["ID Process"]] = i - end + -- 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). + local process_id = {} + for i, v in pairs(process) do + process_id[v["ID Process"]] = i + end - -- Fill the process list table. - -- - -- Used fields: - -- Creating Process ID - -- Handle Count - -- ID Process - -- Priority Base - -- Thread Count - -- - -- Unused fields: - -- % Privileged Time - -- % Processor Time - -- % User Time - -- Elapsed Time - -- IO Data Bytes/sec - -- IO Data Operations/sec - -- IO Other Bytes/sec - -- IO Other Operations/sec - -- IO Read Bytes/sec - -- IO Read Operations/sec - -- IO Write Bytes/sec - -- IO Write Operations/sec - -- Page Faults/sec - -- Page File Bytes - -- Page File Bytes Peak - -- Pool Nonpaged Bytes - -- Pool Paged Bytes - -- Private Bytes - -- Virtual Bytes - -- Virtual Bytes Peak - -- Working Set - -- Working Set Peak - local psl = {} - for i, name in ipairs(names) do - if name ~= "_Total" then - psl[process[name]["ID Process"]] = { - name = name; - pid = process[name]["ID Process"]; - ppid = process[name]["Creating Process ID"]; - prio = process[name]["Priority Base"]; - thrd = process[name]["Thread Count"]; - hndl = process[name]["Handle Count"]; - } - end - end + -- Fill the process list table. + -- + -- Used fields: + -- Creating Process ID + -- Handle Count + -- ID Process + -- Priority Base + -- Thread Count + -- + -- Unused fields: + -- % Privileged Time + -- % Processor Time + -- % User Time + -- Elapsed Time + -- IO Data Bytes/sec + -- IO Data Operations/sec + -- IO Other Bytes/sec + -- IO Other Operations/sec + -- IO Read Bytes/sec + -- IO Read Operations/sec + -- IO Write Bytes/sec + -- IO Write Operations/sec + -- Page Faults/sec + -- Page File Bytes + -- Page File Bytes Peak + -- Pool Nonpaged Bytes + -- Pool Paged Bytes + -- Private Bytes + -- Virtual Bytes + -- Virtual Bytes Peak + -- Working Set + -- Working Set Peak + local psl = {} + for i, name in ipairs(names) do + if name ~= "_Total" then + psl[process[name]["ID Process"]] = { + name = name; + pid = process[name]["ID Process"]; + ppid = process[name]["Creating Process ID"]; + prio = process[name]["Priority Base"]; + thrd = process[name]["Thread Count"]; + hndl = process[name]["Handle Count"]; + } + end + end - -- Produce final output. - local response - if nmap.verbosity() == 0 then - response = "|_ " .. stdnse.strjoin(", ", names) - else - response = "\n" .. psl_print(psl, nmap.verbosity()) - end + -- Produce final output. + local response + if nmap.verbosity() == 0 then + response = "|_ " .. stdnse.strjoin(", ", names) + else + response = "\n" .. psl_print(psl, nmap.verbosity()) + end - return response + return response end diff --git a/scripts/smb-enum-sessions.nse b/scripts/smb-enum-sessions.nse index 94649853f..99129578c 100644 --- a/scripts/smb-enum-sessions.nse +++ b/scripts/smb-enum-sessions.nse @@ -68,7 +68,7 @@ dependencies = {"smb-brute"} hostrule = function(host) - return smb.get_port(host) ~= nil + return smb.get_port(host) ~= nil end ---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 List of sessions (if status is true) or an an error string (if status is false). local function srvsvc_enum_sessions(host) - local i - local status, smbstate - local bind_result, netsessenum_result + local i + local status, smbstate + local bind_result, netsessenum_result - -- Create the SMB session - status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH) - if(status == false) then - return false, smbstate - end + -- Create the SMB session + status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH) + if(status == false) then + return false, smbstate + end - -- Bind to SRVSVC service - status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, bind_result - end + -- Bind to SRVSVC service + status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, bind_result + end - -- Call netsessenum - status, netsessenum_result = msrpc.srvsvc_netsessenum(smbstate, host.ip) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, netsessenum_result - end + -- Call netsessenum + status, netsessenum_result = msrpc.srvsvc_netsessenum(smbstate, host.ip) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, netsessenum_result + end - -- Stop the SMB session - msrpc.stop_smb(smbstate) + -- Stop the SMB session + msrpc.stop_smb(smbstate) - return true, netsessenum_result['ctr']['array'] + return true, netsessenum_result['ctr']['array'] end ---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 name, domain, and changed_date (representing -- when they logged in). local function winreg_enum_rids(host) - local i, j - local elements = {} + local i, j + local elements = {} - -- Create the SMB session - local status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH) - if(status == false) then - return false, smbstate - end + -- Create the SMB session + local status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH) + if(status == false) then + return false, smbstate + end - -- Bind to WINREG service - local status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, bind_result - end + -- Bind to WINREG service + local status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, bind_result + end - local status, openhku_result = msrpc.winreg_openhku(smbstate) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, openhku_result - end + local status, openhku_result = msrpc.winreg_openhku(smbstate) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, openhku_result + end - -- Loop through the keys under HKEY_USERS and grab the names - i = 0 - repeat - local status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], i, "") + -- Loop through the keys under HKEY_USERS and grab the names + i = 0 + repeat + local status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], i, "") - if(status == true) then - local status, openkey_result + if(status == true) then + local status, openkey_result - local element = {} - element['name'] = enumkey_result['name'] + local element = {} + element['name'] = enumkey_result['name'] - -- 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 - local status, openkey_result = msrpc.winreg_openkey(smbstate, openhku_result['handle'], element['name'] .. "\\Volatile Environment") - if(status ~= false) then - local queryinfokey_result, closekey_result + -- 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 + local status, openkey_result = msrpc.winreg_openkey(smbstate, openhku_result['handle'], element['name'] .. "\\Volatile Environment") + if(status ~= false) then + local queryinfokey_result, closekey_result - -- 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']) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, queryinfokey_result - end + -- 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']) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, queryinfokey_result + end - local status, closekey_result = msrpc.winreg_closekey(smbstate, openkey_result['handle']) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, closekey_result - end + local status, closekey_result = msrpc.winreg_closekey(smbstate, openkey_result['handle']) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, closekey_result + end - element['changed_date'] = queryinfokey_result['last_changed_date'] - else - -- Getting extra details failed, but we can still handle this - element['changed_date'] = "" - end - elements[#elements + 1] = element - end + element['changed_date'] = queryinfokey_result['last_changed_date'] + else + -- Getting extra details failed, but we can still handle this + element['changed_date'] = "" + end + elements[#elements + 1] = element + end - i = i + 1 - until status ~= true + i = i + 1 + until status ~= true - local status, closekey_result = msrpc.winreg_closekey(smbstate, openhku_result['handle']) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, closekey_result - end + local status, closekey_result = msrpc.winreg_closekey(smbstate, openhku_result['handle']) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, closekey_result + end - msrpc.stop_smb(smbstate) + msrpc.stop_smb(smbstate) - -- Start a new SMB session - local status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH) - if(status == false) then - return false, smbstate - end + -- Start a new SMB session + local status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH) + if(status == false) then + return false, smbstate + end - -- Bind to LSA service - local status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, bind_result - end + -- Bind to LSA service + local status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, bind_result + end - -- Get a policy handle - local status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip) - if(status == false) then - msrpc.stop_smb(smbstate) - return false, openpolicy2_result - end + -- Get a policy handle + local status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip) + if(status == false) then + msrpc.stop_smb(smbstate) + return false, openpolicy2_result + end - -- Convert the SID to the name of the user - local results = {} - stdnse.print_debug(3, "MSRPC: Found %d SIDs that might be logged in", #elements) - for i = 1, #elements, 1 do - if(elements[i]['name'] ~= nil) then - local sid = elements[i]['name'] - 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 - local rid = string.sub(sid, string.find(sid, "%d+$")) + -- Convert the SID to the name of the user + local results = {} + stdnse.print_debug(3, "MSRPC: Found %d SIDs that might be logged in", #elements) + for i = 1, #elements, 1 do + if(elements[i]['name'] ~= nil) then + local sid = elements[i]['name'] + 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 + 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 - -- It may not succeed, if it doesn't that's ok - stdnse.print_debug(3, "MSRPC: Lookup failed") - else - -- Create the result array - local result = {} - result['changed_date'] = elements[i]['changed_date'] - result['rid'] = rid + if(status == false) then + -- It may not succeed, if it doesn't that's ok + stdnse.print_debug(3, "MSRPC: Lookup failed") + else + -- Create the result array + local result = {} + result['changed_date'] = elements[i]['changed_date'] + result['rid'] = rid - -- Fill in the result from the response - if(lookupsids2_result['names']['names'][1] == nil) then - result['name'] = "" - result['type'] = "" - result['domain'] = "" - else - result['name'] = lookupsids2_result['names']['names'][1]['name'] - 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 - result['domain'] = lookupsids2_result['domains']['domains'][1]['name'] - else - result['domain'] = "" - end - end + -- Fill in the result from the response + if(lookupsids2_result['names']['names'][1] == nil) then + result['name'] = "" + result['type'] = "" + result['domain'] = "" + else + result['name'] = lookupsids2_result['names']['names'][1]['name'] + 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 + result['domain'] = lookupsids2_result['domains']['domains'][1]['name'] + else + result['domain'] = "" + end + end - if(result['type'] ~= "SID_NAME_WKN_GRP") then -- Don't show "well known" accounts - -- Add it to the results - results[#results + 1] = result - end - end - end - end - end + if(result['type'] ~= "SID_NAME_WKN_GRP") then -- Don't show "well known" accounts + -- Add it to the results + results[#results + 1] = result + end + end + end + end + end - -- Close the policy - msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle']) + -- Close the policy + msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle']) - -- Stop the session - msrpc.stop_smb(smbstate) + -- Stop the session + msrpc.stop_smb(smbstate) - return true, results + return true, results end --_G.TRACEBACK = TRACEBACK or {} action = function(host) --- TRACEBACK[coroutine.running()] = true; + -- TRACEBACK[coroutine.running()] = true; - local response = {} + local response = {} - -- Enumerate the logged in users - local logged_in = {} - local status1, users = winreg_enum_rids(host) - if(status1 == false) then - logged_in['warning'] = "Couldn't enumerate login sessions: " .. users - else - logged_in['name'] = "Users logged in" - if(#users == 0) then - table.insert(response, "") - else - for i = 1, #users, 1 do - 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'])) - end - end - end - end - table.insert(response, logged_in) + -- Enumerate the logged in users + local logged_in = {} + local status1, users = winreg_enum_rids(host) + if(status1 == false) then + logged_in['warning'] = "Couldn't enumerate login sessions: " .. users + else + logged_in['name'] = "Users logged in" + if(#users == 0) then + table.insert(response, "") + else + for i = 1, #users, 1 do + 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'])) + end + end + end + end + table.insert(response, logged_in) - -- Get the connected sessions - local sessions_output = {} - local status2, sessions = srvsvc_enum_sessions(host) - if(status2 == false) then - sessions_output['warning'] = "Couldn't enumerate SMB sessions: " .. sessions - else - sessions_output['name'] = "Active SMB sessions" - if(#sessions == 0) then - table.insert(sessions_output, "") - else - -- Format the result - for i = 1, #sessions, 1 do - local time = sessions[i]['time'] - if(time == 0) then - time = "[just logged in, it's probably you]" - 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) - elseif(time > 60 * 60) then - time = string.format("%dh%02dm%02ds", time / 3600, (time % 3600) / 60, time % 60) - else - time = string.format("%02dm%02ds", time / 60, time % 60) - end + -- Get the connected sessions + local sessions_output = {} + local status2, sessions = srvsvc_enum_sessions(host) + if(status2 == false) then + sessions_output['warning'] = "Couldn't enumerate SMB sessions: " .. sessions + else + sessions_output['name'] = "Active SMB sessions" + if(#sessions == 0) then + table.insert(sessions_output, "") + else + -- Format the result + for i = 1, #sessions, 1 do + local time = sessions[i]['time'] + if(time == 0) then + time = "[just logged in, it's probably you]" + 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) + elseif(time > 60 * 60) then + time = string.format("%dh%02dm%02ds", time / 3600, (time % 3600) / 60, time % 60) + else + time = string.format("%02dm%02ds", time / 60, time % 60) + end - local idle_time = sessions[i]['idle_time'] - if(idle_time == 0) then - idle_time = "[not idle]" - 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) - elseif(idle_time > 60 * 60) then - idle_time = string.format("%dh%02dm%02ds", idle_time / 3600, (idle_time % 3600) / 60, idle_time % 60) - else - idle_time = string.format("%02dm%02ds", idle_time / 60, idle_time % 60) - end + local idle_time = sessions[i]['idle_time'] + if(idle_time == 0) then + idle_time = "[not idle]" + 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) + elseif(idle_time > 60 * 60) then + idle_time = string.format("%dh%02dm%02ds", idle_time / 3600, (idle_time % 3600) / 60, idle_time % 60) + else + idle_time = string.format("%02dm%02ds", idle_time / 60, idle_time % 60) + 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)) - end - end - end - table.insert(response, sessions_output) + 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 + table.insert(response, sessions_output) - return stdnse.format_output(true, response) + return stdnse.format_output(true, response) end diff --git a/scripts/smb-psexec.nse b/scripts/smb-psexec.nse index 1080de591..bdef45868 100644 --- a/scripts/smb-psexec.nse +++ b/scripts/smb-psexec.nse @@ -36,12 +36,12 @@ to run net localgroup administrators, which returns a list of users group (take a look at the examples.lua configuration file for these examples): - mod = {} - mod.upload = false - mod.name = "Example 1: Membership of 'administrators'" - mod.program = "net.exe" - mod.args = "localgroup administrators" - table.insert(modules, mod) + mod = {} + mod.upload = false + mod.name = "Example 1: Membership of 'administrators'" + mod.program = "net.exe" + mod.args = "localgroup administrators" + table.insert(modules, mod) mod.upload is false, meaning the program should already be @@ -51,19 +51,19 @@ output. mod.program and mod.args obviously define whic is going to be run. The output for this script is this: - | Example 1: Membership of 'administrators' - | | Alias name administrators - | | Comment Administrators have complete and unrestricted access to the computer/domain - | | - | | Members - | | - | | ------------------------------------------------------------------------------- - | | Administrator - | | ron - | | test - | | The command completed successfully. - | | - | |_ + | Example 1: Membership of 'administrators' + | | Alias name administrators + | | Comment Administrators have complete and unrestricted access to the computer/domain + | | + | | Members + | | + | | ------------------------------------------------------------------------------- + | | Administrator + | | ron + | | test + | | The command completed successfully. + | | + | |_ That works, but it's really ugly. In general, we can use mod.find, @@ -73,14 +73,14 @@ of the useless lines, and mod.noblank to get rid of the blank lines don't want: - mod = {} - mod.upload = false - mod.name = "Example 2: Membership of 'administrators', cleaned" - mod.program = "net.exe" - mod.args = "localgroup administrators" - mod.remove = {"The command completed", "%-%-%-%-%-%-%-%-%-%-%-", "Members", "Alias name", "Comment"} - mod.noblank = true - table.insert(modules, mod) + mod = {} + mod.upload = false + mod.name = "Example 2: Membership of 'administrators', cleaned" + mod.program = "net.exe" + mod.args = "localgroup administrators" + mod.remove = {"The command completed", "%-%-%-%-%-%-%-%-%-%-%-", "Members", "Alias name", "Comment"} + mod.noblank = true + table.insert(modules, mod) We can see that the output is now much cleaner: @@ -97,15 +97,15 @@ want is the IP address and MAC address, and we get it using mod.findmod.replace: - mod = {} - mod.upload = false - mod.name = "Example 3: IP Address and MAC Address" - mod.program = "ipconfig.exe" - mod.args = "/all" - mod.maxtime = 1 - mod.find = {"IP Address", "Physical Address", "Ethernet adapter"} - mod.replace = {{"%. ", ""}, {"-", ":"}, {"Physical Address", "MAC Address"}} - table.insert(modules, mod) + mod = {} + mod.upload = false + mod.name = "Example 3: IP Address and MAC Address" + mod.program = "ipconfig.exe" + mod.args = "/all" + mod.maxtime = 1 + mod.find = {"IP Address", "Physical Address", "Ethernet adapter"} + mod.replace = {{"%. ", ""}, {"-", ":"}, {"Physical Address", "MAC Address"}} + table.insert(modules, mod) This module searches for lines that contain "IP Address", "Physical Address", or "Ethernet adapter". @@ -135,15 +135,15 @@ variable, mod.req_args would be set to {'host'}. Here is a module that pings the local ip address: - mod = {} - mod.upload = false - mod.name = "Example 4: Can the host ping our address?" - mod.program = "ping.exe" - mod.args = "$lhost" - mod.remove = {"statistics", "Packet", "Approximate", "Minimum"} - mod.noblank = true - mod.env = "SystemRoot=c:\\WINDOWS" - table.insert(modules, mod) + mod = {} + mod.upload = false + mod.name = "Example 4: Can the host ping our address?" + mod.program = "ping.exe" + mod.args = "$lhost" + mod.remove = {"statistics", "Packet", "Approximate", "Minimum"} + mod.noblank = true + mod.env = "SystemRoot=c:\\WINDOWS" + table.insert(modules, mod) And the output: @@ -158,16 +158,16 @@ And the output: And this module pings an arbitrary address that the user is expected to give: - mod = {} - mod.upload = false - mod.name = "Example 5: Can the host ping $host?" - mod.program = "ping.exe" - mod.args = "$host" - mod.remove = {"statistics", "Packet", "Approximate", "Minimum"} - mod.noblank = true - mod.env = "SystemRoot=c:\\WINDOWS" - mod.req_args = {'host'} - table.insert(modules, mod) + mod = {} + mod.upload = false + mod.name = "Example 5: Can the host ping $host?" + mod.program = "ping.exe" + mod.args = "$host" + mod.remove = {"statistics", "Packet", "Approximate", "Minimum"} + mod.noblank = true + mod.env = "SystemRoot=c:\\WINDOWS" + mod.req_args = {'host'} + table.insert(modules, mod) And the output (note that we had to up the timeout so this would complete; we'll talk about override @@ -187,15 +187,15 @@ For the final example, we'll use the upload command to upload fgdump.exe in the same folder as the script for this to work: - mod = {} - mod.upload = true - mod.name = "Example 6: FgDump" - mod.program = "fgdump.exe" - mod.args = "-c -l fgdump.log" - mod.url = "http://www.foofus.net/fizzgig/fgdump/" - mod.tempfiles = {"fgdump.log"} - mod.outfile = "127.0.0.1.pwdump" - table.insert(modules, mod) + mod = {} + mod.upload = true + mod.name = "Example 6: FgDump" + mod.program = "fgdump.exe" + mod.args = "-c -l fgdump.log" + mod.url = "http://www.foofus.net/fizzgig/fgdump/" + mod.tempfiles = {"fgdump.log"} + mod.outfile = "127.0.0.1.pwdump" + table.insert(modules, mod) The -l argument for fgdump supplies the name of the logfile. That file is listed in the mod.tempfiles field. What, exactly, does mod.tempfiles do? @@ -425,7 +425,7 @@ local NMAP_SERVICE_EXE_DOWNLOAD = "http://nmap.org/psexec/nmap_service.exe" hostrule = function(host) - return smb.get_port(host) ~= nil + return smb.get_port(host) ~= nil end ---Get the random-ish filenames used by the service. @@ -437,38 +437,38 @@ end --@return Name of the temporary output file. --@return Name of the final output file. local function get_service_files(host) - local status, service_name, service_file, temp_output_file, output_file + local status, service_name, service_file, temp_output_file, output_file - -- Get the name of the service - status, service_name = smb.get_uniqueish_name(host) - if(status == false) then - return false, string.format("Error generating service name: %s", service_name) - end - stdnse.print_debug("smb-psexec: Generated static service name: %s", service_name) + -- Get the name of the service + status, service_name = smb.get_uniqueish_name(host) + if(status == false) then + return false, string.format("Error generating service name: %s", service_name) + end + stdnse.print_debug("smb-psexec: Generated static service name: %s", service_name) - -- Get the name and service's executable file (with a .txt extension for fun) - status, service_file = smb.get_uniqueish_name(host, "txt") - if(status == false) then - return false, string.format("Error generating remote filename: %s", service_file) - end - stdnse.print_debug("smb-psexec: Generated static service name: %s", service_name) + -- Get the name and service's executable file (with a .txt extension for fun) + status, service_file = smb.get_uniqueish_name(host, "txt") + if(status == false) then + return false, string.format("Error generating remote filename: %s", service_file) + end + stdnse.print_debug("smb-psexec: Generated static service name: %s", service_name) - -- Get the temporary output file - status, temp_output_file = smb.get_uniqueish_name(host, "out.tmp") - if(status == false) then - return false, string.format("Error generating remote filename: %s", temp_output_file) - end - stdnse.print_debug("smb-psexec: Generated static service filename: %s", temp_output_file) + -- Get the temporary output file + status, temp_output_file = smb.get_uniqueish_name(host, "out.tmp") + if(status == false) then + return false, string.format("Error generating remote filename: %s", temp_output_file) + end + stdnse.print_debug("smb-psexec: Generated static service filename: %s", temp_output_file) - -- Get the actual output file - status, output_file = smb.get_uniqueish_name(host, "out") - if(status == false) then - return false, string.format("Error generating remote output file: %s", output_file) - end - stdnse.print_debug("smb-psexec: Generated static output filename: %s", output_file) + -- Get the actual output file + status, output_file = smb.get_uniqueish_name(host, "out") + if(status == false) then + return false, string.format("Error generating remote output file: %s", output_file) + end + stdnse.print_debug("smb-psexec: Generated static output filename: %s", output_file) - -- Return everything - return true, service_name, service_file, temp_output_file, output_file + -- Return everything + return true, service_name, service_file, temp_output_file, output_file end ---Stop/delete the service and delete the service file. @@ -476,38 +476,38 @@ end --@param host The host object. --@param config The table of configuration values. function cleanup(host, config) - local status, err + local status, err - -- Add a delay here. For some reason, calling this function too quickly causes SMB to close the connection, - -- but even a tiny delay makes that issue go away. - stdnse.sleep(.01) + -- Add a delay here. For some reason, calling this function too quickly causes SMB to close the connection, + -- but even a tiny delay makes that issue go away. + stdnse.sleep(.01) - -- If the user doesn't want to clean up, don't - if(stdnse.get_script_args( "nocleanup" )) then - return - end + -- If the user doesn't want to clean up, don't + if(stdnse.get_script_args( "nocleanup" )) then + return + end - stdnse.print_debug(1, "smb-psexec: Entering cleanup() -- errors here can generally be ignored") - -- Try stopping the service - status, err = msrpc.service_stop(host, config.service_name) - if(status == false) then - stdnse.print_debug(1, "smb-psexec: [cleanup] Couldn't stop service: %s", err) - end + stdnse.print_debug(1, "smb-psexec: Entering cleanup() -- errors here can generally be ignored") + -- Try stopping the service + status, err = msrpc.service_stop(host, config.service_name) + if(status == false) then + stdnse.print_debug(1, "smb-psexec: [cleanup] Couldn't stop service: %s", err) + end - -- Try deleting the service - status, err = msrpc.service_delete(host, config.service_name) - if(status == false) then - stdnse.print_debug(1, "smb-psexec: [cleanup] Couldn't delete service: %s", err) - end + -- Try deleting the service + status, err = msrpc.service_delete(host, config.service_name) + if(status == false) then + stdnse.print_debug(1, "smb-psexec: [cleanup] Couldn't delete service: %s", err) + end - -- Delete the files - for _, share in ipairs(config.all_shares) do - status, err = smb.file_delete(host, share, config.all_files) - end + -- Delete the files + for _, share in ipairs(config.all_shares) do + status, err = smb.file_delete(host, share, config.all_files) + end - stdnse.print_debug(1, "smb-psexec: Leaving cleanup()") + stdnse.print_debug(1, "smb-psexec: Leaving cleanup()") - return true + return true end ---Find the file on the system (checks both Nmap's directories and the current @@ -517,11 +517,11 @@ end --@param extension The extension of the file (filename without the extension is tried first). --@return The full filename, or nil if it couldn't be found. local function locate_file(filename, extension) - stdnse.print_debug(1, "smb-psexec: Attempting to find file: %s", filename) + stdnse.print_debug(1, "smb-psexec: Attempting to find file: %s", filename) - extension = extension or "" + extension = extension or "" - local filename_full = nmap.fetchfile(filename) or nmap.fetchfile(filename .. "." .. extension) + local filename_full = nmap.fetchfile(filename) or nmap.fetchfile(filename .. "." .. extension) if(filename_full == nil) then local psexecfile = "nselib/data/psexec/" .. filename @@ -547,7 +547,7 @@ local function locate_file(filename, extension) end end - return filename_full + return filename_full end ---Generate an array of all files that will be uploaded/created, including @@ -557,32 +557,32 @@ end --@param config The config table. --@return The array of files. local function get_all_files(config) - local files = {config.service_file, config.output_file, config.temp_output_file} - for _, mod in ipairs(config.enabled_modules) do - -- We're going to delete the module itself - table.insert(files, mod.upload_name) + local files = {config.service_file, config.output_file, config.temp_output_file} + for _, mod in ipairs(config.enabled_modules) do + -- We're going to delete the module itself + table.insert(files, mod.upload_name) - -- We're also going to delete any temp files... - if(mod.tempfiles) then - for _, file in ipairs(mod.tempfiles) do - table.insert(files, file) - end - end + -- We're also going to delete any temp files... + if(mod.tempfiles) then + for _, file in ipairs(mod.tempfiles) do + table.insert(files, file) + end + end - -- ... and any extra files we uploaded ,,, - if(mod.extrafiles) then - for _, file in ipairs(mod.extrafiles) do - table.insert(files, file) - end - end + -- ... and any extra files we uploaded ,,, + if(mod.extrafiles) then + for _, file in ipairs(mod.extrafiles) do + table.insert(files, file) + end + end - -- ... not to mention the output file - if(mod.outfile and mod.outfile ~= "") then - table.insert(files, mod.outfile) - end - end + -- ... not to mention the output file + if(mod.outfile and mod.outfile ~= "") then + table.insert(files, mod.outfile) + end + end - return files + return files end ---Decide which share to use. Unless the user overrides it with the 'share' and 'sharepath' @@ -594,31 +594,31 @@ end --@return path The path on the remote system that points to the share. --@return shares A list of all shares on the system (used for cleaning up). local function find_share(host) - local status, share, path, shares + local status, share, path, shares - -- Determine which share to use - if(nmap.registry.args.share ~= nil) then - share = nmap.registry.args.share - shares = {share} - path = nmap.registry.args.sharepath - if(path == nil) then - return false, "Setting the 'share' script-arg requires the 'sharepath' to be set as well." - end + -- Determine which share to use + if(nmap.registry.args.share ~= nil) then + share = nmap.registry.args.share + shares = {share} + path = nmap.registry.args.sharepath + if(path == nil) then + return false, "Setting the 'share' script-arg requires the 'sharepath' to be set as well." + end - stdnse.print_debug(1, "smb-psexec: Using share chosen by the user: %s (%s)", share, path) - else - -- Try and find a share to use. - status, share, path, shares = smb.share_find_writable(host) - if(status == false) then - return false, share .. " (May not have an administrator account)" - end - if(path == nil) then - return false, string.format("Couldn't find path to writable share (we probably don't have admin access): '%s'", share) - end - stdnse.print_debug(1, "smb-psexec: Found usable share %s (%s) (all writable shares: %s)", share, path, stdnse.strjoin(", ", shares)) - end + stdnse.print_debug(1, "smb-psexec: Using share chosen by the user: %s (%s)", share, path) + else + -- Try and find a share to use. + status, share, path, shares = smb.share_find_writable(host) + if(status == false) then + return false, share .. " (May not have an administrator account)" + end + if(path == nil) then + return false, string.format("Couldn't find path to writable share (we probably don't have admin access): '%s'", share) + end + stdnse.print_debug(1, "smb-psexec: Found usable share %s (%s) (all writable shares: %s)", share, path, stdnse.strjoin(", ", shares)) + end - return true, share, path, shares + return true, share, path, shares end ---Recursively replace all variables in the 'setting' field with string variables @@ -628,25 +628,25 @@ end --@param setting The current setting field (generally a string or a table). --@return setting The setting with all values replaced. local function replace_variables(config, setting) - if(type(setting) == "string") then - -- Replace module fields with variables in the script-args argument - for k, v in pairs(nmap.registry.args) do - setting = string.gsub(setting, "$"..k, v) - end + if(type(setting) == "string") then + -- Replace module fields with variables in the script-args argument + for k, v in pairs(nmap.registry.args) do + setting = string.gsub(setting, "$"..k, v) + end - -- Replace module fields with variables in the config file - for k, v in pairs(config) do - if((type(v) == "string" or type(v) == "boolean" or type(v) == "number") and k ~= "key") then - setting = string.gsub(setting, "$"..k, v) - end - end - elseif(type(setting) == "table") then - for k, v in pairs(setting) do - setting[k] = replace_variables(config, v) - end - end + -- Replace module fields with variables in the config file + for k, v in pairs(config) do + if((type(v) == "string" or type(v) == "boolean" or type(v) == "number") and k ~= "key") then + setting = string.gsub(setting, "$"..k, v) + end + end + elseif(type(setting) == "table") then + for k, v in pairs(setting) do + setting[k] = replace_variables(config, v) + end + end - return setting + return setting end ---Takes the 'overrides' field from a module and replace any configuration variables. @@ -655,17 +655,17 @@ end --@param overrides The overrides we're replacing values with. --@return config The new config table. local function do_overrides(config, overrides) - if(overrides) then - if(type(overrides) == 'string') then - overrides = {overrides} - end + if(overrides) then + if(type(overrides) == 'string') then + overrides = {overrides} + end - for i, v in pairs(overrides) do - config[i] = v - end - end + for i, v in pairs(overrides) do + config[i] = v + end + end - return config + return config end ---Reads, prepares, parses, sanity checks, and pre-processes the configuration file (either the @@ -676,245 +676,245 @@ end --@return status true or false --@return config The configuration table or an error message. local function get_config(host, config) - local status - local filename = nmap.registry.args.config - config.enabled_modules = {} - config.disabled_modules = {} + local status + local filename = nmap.registry.args.config + config.enabled_modules = {} + config.disabled_modules = {} - -- Find the config file - filename = locate_file(filename or 'default', 'lua') - if(filename == nil) then - return false, "Couldn't locate config file: file not found (make sure it has a .lua extension and is in nselib/data/psexec/)" - end + -- Find the config file + filename = locate_file(filename or 'default', 'lua') + if(filename == nil) then + return false, "Couldn't locate config file: file not found (make sure it has a .lua extension and is in nselib/data/psexec/)" + end - -- Load the config file - local env = setmetatable({modules = {}; overrides = {}; module = function() stdnse.print_debug(1, "WARNING: Selected config file contains an unnecessary call to module()") end}, {__index = _G}) - stdnse.print_debug(1, "smb-psexec: Attempting to load config file: %s", filename) - local file = loadfile(filename, "t", env) - if(not(file)) then - return false, "Couldn't load module file:\n" .. filename - end + -- Load the config file + local env = setmetatable({modules = {}; overrides = {}; module = function() stdnse.print_debug(1, "WARNING: Selected config file contains an unnecessary call to module()") end}, {__index = _G}) + stdnse.print_debug(1, "smb-psexec: Attempting to load config file: %s", filename) + local file = loadfile(filename, "t", env) + if(not(file)) then + return false, "Couldn't load module file:\n" .. filename + end - -- Run the config file - file() - local modules = env.modules - local overrides = env.overrides + -- Run the config file + file() + local modules = env.modules + local overrides = env.overrides - -- Generate a cipher key - if(stdnse.get_script_args( "nocipher" )) then - config.key = "" - elseif(nmap.registry.args.key) then - config.key = nmap.registry.args.key - else - config.key = "" - for i = 1, 127, 1 do - config.key = config.key .. string.char(math.random(0x20, 0x7F)) - end - config.key_index = 0 - end + -- Generate a cipher key + if(stdnse.get_script_args( "nocipher" )) then + config.key = "" + elseif(nmap.registry.args.key) then + config.key = nmap.registry.args.key + else + config.key = "" + for i = 1, 127, 1 do + config.key = config.key .. string.char(math.random(0x20, 0x7F)) + end + config.key_index = 0 + end - -- Initialize the timeout - config.timeout = 0 + -- Initialize the timeout + config.timeout = 0 - -- Figure out which share we're using (this is the first place in the script where a lot of traffic is generated -- - -- any possible sanity checking should be done before this) - status, config.share, config.path, config.all_shares = find_share(host) - if(not(status)) then - return false, config.share - end + -- Figure out which share we're using (this is the first place in the script where a lot of traffic is generated -- + -- any possible sanity checking should be done before this) + status, config.share, config.path, config.all_shares = find_share(host) + if(not(status)) then + return false, config.share + end - -- Get information about the socket; it's a bit out of place here, but it should go before the mod loop - status, config.lhost, config.lport, config.rhost, config.rport, config.lmac = smb.get_socket_info(host) - if(status == false) then - return false, "Couldn't get socket information: " .. config.lhost - end + -- Get information about the socket; it's a bit out of place here, but it should go before the mod loop + status, config.lhost, config.lport, config.rhost, config.rport, config.lmac = smb.get_socket_info(host) + if(status == false) then + return false, "Couldn't get socket information: " .. config.lhost + end - -- Get the names of the files we're going to need - status, config.service_name, config.service_file, config.temp_output_file, config.output_file = get_service_files(host) - if(not(status)) then - return false, config.service_name - end + -- Get the names of the files we're going to need + status, config.service_name, config.service_file, config.temp_output_file, config.output_file = get_service_files(host) + if(not(status)) then + return false, config.service_name + end - -- Make sure the modules loaded properly - -- NOTE: If you're here because of an error that 'modules' is undefined, it's likely because your configuration file doesn't have a - -- proper modules table, or your configuration file has a module() declaration at the top. - if(not(modules) or #modules == 0) then - return false, string.format("Configuration file (%s) doesn't have a proper 'modules' table.", filename) - end + -- Make sure the modules loaded properly + -- NOTE: If you're here because of an error that 'modules' is undefined, it's likely because your configuration file doesn't have a + -- proper modules table, or your configuration file has a module() declaration at the top. + if(not(modules) or #modules == 0) then + return false, string.format("Configuration file (%s) doesn't have a proper 'modules' table.", filename) + end - -- Make sure we got a proper modules array - if(type(modules) ~= "table") then - return false, string.format("The chosen configuration file, %s.lua, doesn't have a proper 'modules' table. If possible, it should be modified to have a public array called 'modules' that contains a list of all modules that will be run.", filename) - end + -- Make sure we got a proper modules array + if(type(modules) ~= "table") then + return false, string.format("The chosen configuration file, %s.lua, doesn't have a proper 'modules' table. If possible, it should be modified to have a public array called 'modules' that contains a list of all modules that will be run.", filename) + end - -- Loop through the modules for some pre-processing - stdnse.print_debug(1, "smb-psexec: Verifying uploadable executables exist") - for i, mod in ipairs(modules) do - local enabled = true - -- Do some sanity checking - if(mod.program == nil) then - enabled = false - if(mod.name) then - mod.disabled_message = string.format("Configuration error: '%s': module doesn't have a program", mod.name) - else - mod.disabled_message = string.format("Configuration error: Module #%d doesn't have a program", i) - end - end + -- Loop through the modules for some pre-processing + stdnse.print_debug(1, "smb-psexec: Verifying uploadable executables exist") + for i, mod in ipairs(modules) do + local enabled = true + -- Do some sanity checking + if(mod.program == nil) then + enabled = false + if(mod.name) then + mod.disabled_message = string.format("Configuration error: '%s': module doesn't have a program", mod.name) + else + mod.disabled_message = string.format("Configuration error: Module #%d doesn't have a program", i) + end + end - -- Set some defaults, if the user didn't specify - mod.name = mod.name or (string.format("%s %s", mod.program, mod.args or "")) - mod.maxtime = mod.maxtime or 1 + -- Set some defaults, if the user didn't specify + mod.name = mod.name or (string.format("%s %s", mod.program, mod.args or "")) + mod.maxtime = mod.maxtime or 1 - -- Check if they forgot the uploadbility - if(mod.upload == nil) then - enabled = false - mod.disabled_message = string.format("Configuration error: '%s': 'upload' field is required", mod.name) - end + -- Check if they forgot the uploadbility + if(mod.upload == nil) then + enabled = false + mod.disabled_message = string.format("Configuration error: '%s': 'upload' field is required", mod.name) + end - -- Check if the upload field is set wrong - if(mod.upload ~= true and mod.upload ~= false) then - enabled = false - mod.disabled_message = string.format("Configuration error: '%s': 'upload' field has to be true or false", mod.name) - end + -- Check if the upload field is set wrong + if(mod.upload ~= true and mod.upload ~= false) then + enabled = false + mod.disabled_message = string.format("Configuration error: '%s': 'upload' field has to be true or false", mod.name) + end - -- Check for incompatible fields with 'headless' - if(mod.headless) then - if(mod.find or mod.remove or mod.noblank or mod.replace or (mod.maxtime > 1) or mod.outfile) then - enabled = false - mod.disabled_message = string.format("Configuration error: '%s': 'headless' is incompatible with find, remove, noblank, replace, and maxtime", mod.name) - end - end + -- Check for incompatible fields with 'headless' + if(mod.headless) then + if(mod.find or mod.remove or mod.noblank or mod.replace or (mod.maxtime > 1) or mod.outfile) then + enabled = false + mod.disabled_message = string.format("Configuration error: '%s': 'headless' is incompatible with find, remove, noblank, replace, and maxtime", mod.name) + end + end - -- Check for improperly formatted 'replace' - if(mod.replace) then - if(type(mod.replace) ~= "table") then - enabled = false - mod.disabled_message = string.format("Configuration error: '%s': 'replace' has to be a table of one-element tables (eg. replace = {{'a'='b'}, {'c'='d'}})", mod.name) - end + -- Check for improperly formatted 'replace' + if(mod.replace) then + if(type(mod.replace) ~= "table") then + enabled = false + mod.disabled_message = string.format("Configuration error: '%s': 'replace' has to be a table of one-element tables (eg. replace = {{'a'='b'}, {'c'='d'}})", mod.name) + end - for _, v in ipairs(mod.replace) do - if(type(v) ~= 'table') then - enabled = false - mod.disabled_message = string.format("Configuration error: '%s': 'replace' has to be a table of one-element tables (eg. replace = {{'a'='b'}, {'c'='d'}})", mod.name) - end - end - end + for _, v in ipairs(mod.replace) do + if(type(v) ~= 'table') then + enabled = false + mod.disabled_message = string.format("Configuration error: '%s': 'replace' has to be a table of one-element tables (eg. replace = {{'a'='b'}, {'c'='d'}})", mod.name) + end + end + end - -- Set some default values - if(mod.headless == nil) then - mod.headless = false - end - if(mod.include_stderr == nil) then - mod.include_stderr = true - end + -- Set some default values + if(mod.headless == nil) then + mod.headless = false + end + if(mod.include_stderr == nil) then + mod.include_stderr = true + end - -- Make sure required arguments are given - if(mod.req_args) then - if(type(mod.req_args) == 'string') then - mod.req_args = {mod.req_args} - end + -- Make sure required arguments are given + if(mod.req_args) then + if(type(mod.req_args) == 'string') then + mod.req_args = {mod.req_args} + end - -- Keep a table of missing args so we can tell the user all the args they're missing at once - local missing_args = {} - for _, arg in ipairs(mod.req_args) do - if(nmap.registry.args[arg] == nil) then - table.insert(missing_args, arg) - end - end + -- Keep a table of missing args so we can tell the user all the args they're missing at once + local missing_args = {} + for _, arg in ipairs(mod.req_args) do + if(nmap.registry.args[arg] == nil) then + table.insert(missing_args, arg) + end + end - if(#missing_args > 0) then - enabled = false - mod.disabled_message = {} - table.insert(mod.disabled_message, string.format("Configuration error: Required argument(s) ('%s') weren't given.", stdnse.strjoin("', '", missing_args))) - table.insert(mod.disabled_message, string.format("Please add --script-args=[arg]=[value] to your commandline to run this module")) - if(#missing_args == 1) then - table.insert(mod.disabled_message, string.format("For example: --script-args=%s=123", missing_args[1])) - else - table.insert(mod.disabled_message, string.format("For example: --script-args=%s=123,%s=456...", missing_args[1], missing_args[2])) - end - end - end + if(#missing_args > 0) then + enabled = false + mod.disabled_message = {} + table.insert(mod.disabled_message, string.format("Configuration error: Required argument(s) ('%s') weren't given.", stdnse.strjoin("', '", missing_args))) + table.insert(mod.disabled_message, string.format("Please add --script-args=[arg]=[value] to your commandline to run this module")) + if(#missing_args == 1) then + table.insert(mod.disabled_message, string.format("For example: --script-args=%s=123", missing_args[1])) + else + table.insert(mod.disabled_message, string.format("For example: --script-args=%s=123,%s=456...", missing_args[1], missing_args[2])) + end + end + end - -- Checks for the uploadable modules - if(mod.upload) then - -- Check if the module actually exists - stdnse.print_debug(1, "smb-psexec: Looking for uploadable module: %s or %s.exe", mod.program, mod.program) - mod.filename = locate_file(mod.program, "exe") - if(mod.filename == nil) then - enabled = false - stdnse.print_debug(1, "Couldn't find uploadable module %s, disabling", mod.program) - mod.disabled_message = {string.format("Couldn't find uploadable module %s, disabling", mod.program)} - if(mod.url) then - stdnse.print_debug(1, "You can try getting it from: %s", mod.url) - table.insert(mod.disabled_message, string.format("You can try getting it from: %s", mod.url)) - table.insert(mod.disabled_message, "And placing it in Nmap's nselib/data/psexec/ directory") - end - else - -- We found it - stdnse.print_debug(1, "smb-psexec: Found: %s", mod.filename) + -- Checks for the uploadable modules + if(mod.upload) then + -- Check if the module actually exists + stdnse.print_debug(1, "smb-psexec: Looking for uploadable module: %s or %s.exe", mod.program, mod.program) + mod.filename = locate_file(mod.program, "exe") + if(mod.filename == nil) then + enabled = false + stdnse.print_debug(1, "Couldn't find uploadable module %s, disabling", mod.program) + mod.disabled_message = {string.format("Couldn't find uploadable module %s, disabling", mod.program)} + if(mod.url) then + stdnse.print_debug(1, "You can try getting it from: %s", mod.url) + table.insert(mod.disabled_message, string.format("You can try getting it from: %s", mod.url)) + table.insert(mod.disabled_message, "And placing it in Nmap's nselib/data/psexec/ directory") + end + else + -- We found it + stdnse.print_debug(1, "smb-psexec: Found: %s", mod.filename) - -- Generate a name to upload them as (we don't upload with the original names) - status, mod.upload_name = smb.get_uniqueish_name(host, "txt", mod.filename) - if(not(status)) then - return false, "Couldn't generate name for uploaded file: " .. mod.upload_name - end - stdnse.print_debug("smb-psexec: Will upload %s as %s", mod.filename, mod.upload_name) - end - end + -- Generate a name to upload them as (we don't upload with the original names) + status, mod.upload_name = smb.get_uniqueish_name(host, "txt", mod.filename) + if(not(status)) then + return false, "Couldn't generate name for uploaded file: " .. mod.upload_name + end + stdnse.print_debug("smb-psexec: Will upload %s as %s", mod.filename, mod.upload_name) + end + end - -- Prepare extra files - if(enabled and mod.extrafiles) then - -- Make sure we have an array to help save on duplicate code - if(type(mod.extrafiles) == "string") then - mod.extrafiles = {mod.extrafiles} - end + -- Prepare extra files + if(enabled and mod.extrafiles) then + -- Make sure we have an array to help save on duplicate code + if(type(mod.extrafiles) == "string") then + mod.extrafiles = {mod.extrafiles} + end - -- Loop through all of the extra files - mod.extrafiles_paths = {} - for i, extrafile in ipairs(mod.extrafiles) do - stdnse.print_debug(1, "smb-psexec: Looking for extra module: %s", extrafile) - mod.extrafiles_paths[i] = locate_file(extrafile) - if(mod.extrafiles_paths[i] == nil) then - return false, string.format("Couldn't find required file to upload: %s", extrafile) - end - stdnse.print_debug(1, "smb-psexec: Found: %s", mod.extrafiles_paths[i]) - end - end + -- Loop through all of the extra files + mod.extrafiles_paths = {} + for i, extrafile in ipairs(mod.extrafiles) do + stdnse.print_debug(1, "smb-psexec: Looking for extra module: %s", extrafile) + mod.extrafiles_paths[i] = locate_file(extrafile) + if(mod.extrafiles_paths[i] == nil) then + return false, string.format("Couldn't find required file to upload: %s", extrafile) + end + stdnse.print_debug(1, "smb-psexec: Found: %s", mod.extrafiles_paths[i]) + end + end - -- Add the timeout to the total - config.timeout = config.timeout + mod.maxtime + -- Add the timeout to the total + config.timeout = config.timeout + mod.maxtime - -- Add the module to the appropriate list - if(enabled) then - table.insert(config.enabled_modules, mod) - else - table.insert(config.disabled_modules, mod) - end - end + -- Add the module to the appropriate list + if(enabled) then + table.insert(config.enabled_modules, mod) + else + table.insert(config.disabled_modules, mod) + end + end - -- Make a list of *all* files (used for cleaning up) - config.all_files = get_all_files(config) + -- Make a list of *all* files (used for cleaning up) + config.all_files = get_all_files(config) - -- Finalize the timeout - local max_timeout = nmap.registry.args.timeout or 15 - config.timeout = math.max(config.timeout, max_timeout) - stdnse.print_debug(1, "smb-psexec: Timeout waiting for a response is %d seconds", config.timeout) + -- Finalize the timeout + local max_timeout = nmap.registry.args.timeout or 15 + config.timeout = math.max(config.timeout, max_timeout) + stdnse.print_debug(1, "smb-psexec: Timeout waiting for a response is %d seconds", config.timeout) - -- Do config overrides - if(overrides) then - config = do_overrides(config, overrides) - end + -- Do config overrides + if(overrides) then + config = do_overrides(config, overrides) + end - -- Replace variable values in the configuration (this has to go last) - stdnse.print_debug(1, "smb-psexec: Replacing variables in the modules' fields") - for i, mod in ipairs(config.enabled_modules) do - for k, v in pairs(mod) do - mod[k] = replace_variables(config, v) - end - end + -- Replace variable values in the configuration (this has to go last) + stdnse.print_debug(1, "smb-psexec: Replacing variables in the modules' fields") + for i, mod in ipairs(config.enabled_modules) do + for k, v in pairs(mod) do + mod[k] = replace_variables(config, v) + end + end - return true, config + return true, config end ---Cipher (or uncipher) a string with a weak xor-based encryption. @@ -923,41 +923,41 @@ end --@args config The config file for this host (stores the encryption key). --@return The decrypted string. local function cipher(str, config) - local result = "" - if(config.key == "") then - return str - end + local result = "" + if(config.key == "") then + return str + end - for i = 1, #str, 1 do - local c = string.byte(str, i) - c = string.char(bit.bxor(c, string.byte(config.key, config.key_index + 1))) + for i = 1, #str, 1 do + local c = string.byte(str, i) + c = string.char(bit.bxor(c, string.byte(config.key, config.key_index + 1))) - config.key_index = config.key_index + 1 - config.key_index = config.key_index % #config.key + config.key_index = config.key_index + 1 + config.key_index = config.key_index % #config.key - result = result .. c - end + result = result .. c + end - return result + return result end local function get_overrides() - -- Create some overrides: - -- 0x00004000 = Encrypted - -- 0x00002000 = Don't index this file - -- 0x00000100 = Temporary file - -- 0x00000800 = Compressed file - -- 0x00000002 = Hidden file - -- 0x00000004 = System file - local attr = bit.bor(0x00000004,0x00000002,0x00000800,0x00000100,0x00002000,0x00004000) + -- Create some overrides: + -- 0x00004000 = Encrypted + -- 0x00002000 = Don't index this file + -- 0x00000100 = Temporary file + -- 0x00000800 = Compressed file + -- 0x00000002 = Hidden file + -- 0x00000004 = System file + local attr = bit.bor(0x00000004,0x00000002,0x00000800,0x00000100,0x00002000,0x00004000) - -- Let the user override this behaviour - if(stdnse.get_script_args( "nohide" )) then - attr = 0 - end + -- Let the user override this behaviour + if(stdnse.get_script_args( "nohide" )) then + attr = 0 + end - -- Create the overrides - return {file_create_attributes=attr} + -- Create the overrides + return {file_create_attributes=attr} end --- Check if an nmap_service.exe file is the XOR-encoded version from the 5.21 @@ -968,19 +968,19 @@ end -- @return status -- @return error message local function service_file_is_xor_encoded(filename) - local f, bytes, msg + local f, bytes, msg - f, msg = io.open(filename) - if not f then - return nil, msg - end - bytes = f:read(2) - f:close() - if not bytes or #bytes < 2 then - return nil, "Can't read from service file" - end - -- This is the XOR-inverse of "MZ". - return bytes == string.char(0xb2, 0xa5) + f, msg = io.open(filename) + if not f then + return nil, msg + end + bytes = f:read(2) + f:close() + if not bytes or #bytes < 2 then + return nil, "Can't read from service file" + end + -- This is the XOR-inverse of "MZ". + return bytes == string.char(0xb2, 0xa5) end ---Upload all of the uploadable files to the remote system. @@ -990,68 +990,68 @@ end --@return status true or false --@return err An error message if status is false. local function upload_everything(host, config) - local is_xor_encoded, msg - local overrides = get_overrides() + local is_xor_encoded, msg + local overrides = get_overrides() - -- In Nmap 5.20, it was discovered that nmap_service.exe file was - -- causing false positives in antivirus software. In an effort to avoid - -- this, in version 5.21 the file was obfuscated by XORing all its bytes - -- with 0xFF. That didn't work, so now the file is not included in the - -- distribution. But it means we must check if we are dealing with the - -- original or XOR-encoded version of the file. - is_xor_encoded, msg = service_file_is_xor_encoded(config.local_service_file) - if is_xor_encoded == nil then - return nil, msg - elseif is_xor_encoded then - stdnse.print_debug(2, "%s is the XOR-encoded version from the 5.21 release.", config.local_service_file) - end + -- In Nmap 5.20, it was discovered that nmap_service.exe file was + -- causing false positives in antivirus software. In an effort to avoid + -- this, in version 5.21 the file was obfuscated by XORing all its bytes + -- with 0xFF. That didn't work, so now the file is not included in the + -- distribution. But it means we must check if we are dealing with the + -- original or XOR-encoded version of the file. + is_xor_encoded, msg = service_file_is_xor_encoded(config.local_service_file) + if is_xor_encoded == nil then + return nil, msg + elseif is_xor_encoded then + stdnse.print_debug(2, "%s is the XOR-encoded version from the 5.21 release.", config.local_service_file) + end - -- Upload the service file - stdnse.print_debug(1, "smb-psexec: Uploading: %s => \\\\%s\\%s", config.local_service_file, config.share, config.service_file) - local status, err - status, err = smb.file_upload(host, config.local_service_file, config.share, "\\" .. config.service_file, overrides, is_xor_encoded) - if(status == false) then - cleanup(host, config) - return false, string.format("Couldn't upload the service file: %s\n", err) - end - stdnse.print_debug(1, "smb-psexec: Service file successfully uploaded!") + -- Upload the service file + stdnse.print_debug(1, "smb-psexec: Uploading: %s => \\\\%s\\%s", config.local_service_file, config.share, config.service_file) + local status, err + status, err = smb.file_upload(host, config.local_service_file, config.share, "\\" .. config.service_file, overrides, is_xor_encoded) + if(status == false) then + cleanup(host, config) + return false, string.format("Couldn't upload the service file: %s\n", err) + end + stdnse.print_debug(1, "smb-psexec: Service file successfully uploaded!") - -- Upload the modules and all their extras - stdnse.print_debug(1, "smb-psexec: Attempting to upload the modules") - for _, mod in ipairs(config.enabled_modules) do - -- If it's an uploadable module, upload it - if(mod.upload) then - stdnse.print_debug(1, "smb-psexec: Uploading: %s => \\\\%s\\%s", mod.filename, config.share, mod.upload_name) - status, err = smb.file_upload(host, mod.filename, config.share, "\\" .. mod.upload_name, overrides) - if(status == false) then - cleanup(host, config) - return false, string.format("Couldn't upload module %s: %s\n", mod.program, err) - end - end + -- Upload the modules and all their extras + stdnse.print_debug(1, "smb-psexec: Attempting to upload the modules") + for _, mod in ipairs(config.enabled_modules) do + -- If it's an uploadable module, upload it + if(mod.upload) then + stdnse.print_debug(1, "smb-psexec: Uploading: %s => \\\\%s\\%s", mod.filename, config.share, mod.upload_name) + status, err = smb.file_upload(host, mod.filename, config.share, "\\" .. mod.upload_name, overrides) + if(status == false) then + cleanup(host, config) + return false, string.format("Couldn't upload module %s: %s\n", mod.program, err) + end + end - -- If it requires extra files, upload them, too - if(mod.extrafiles) then - -- Convert to a table, if it's a string - if(type(mod.extrafiles) == "string") then - mod.extrafiles = {mod.extrafiles} - end + -- If it requires extra files, upload them, too + if(mod.extrafiles) then + -- Convert to a table, if it's a string + if(type(mod.extrafiles) == "string") then + mod.extrafiles = {mod.extrafiles} + end - -- Loop over the files and upload them - for i, extrafile in ipairs(mod.extrafiles) do - local extrafile_local = mod.extrafiles_paths[i] + -- Loop over the files and upload them + for i, extrafile in ipairs(mod.extrafiles) do + local extrafile_local = mod.extrafiles_paths[i] - stdnse.print_debug(1, "smb-psexec: Uploading extra file: %s => \\\\%s\\%s", extrafile_local, config.share, extrafile) - status, err = smb.file_upload(host, extrafile_local, config.share, extrafile, overrides) - if(status == false) then - cleanup(host, config) - return false, string.format("Couldn't upload extra file %s: %s\n", extrafile_local, err) - end - end - end - end - stdnse.print_debug(1, "smb-psexec: Modules successfully uploaded!") + stdnse.print_debug(1, "smb-psexec: Uploading extra file: %s => \\\\%s\\%s", extrafile_local, config.share, extrafile) + status, err = smb.file_upload(host, extrafile_local, config.share, extrafile, overrides) + if(status == false) then + cleanup(host, config) + return false, string.format("Couldn't upload extra file %s: %s\n", extrafile_local, err) + end + end + end + end + stdnse.print_debug(1, "smb-psexec: Modules successfully uploaded!") - return true + return true end ---Create the service on the remote system. @@ -1060,19 +1060,19 @@ end --@return status true or false --@return err An error message if status is false. local function create_service(host, config) - local status, err = msrpc.service_create(host, config.service_name, config.path .. "\\" .. config.service_file) - if(status == false) then - stdnse.print_debug(1, "smb-psexec: Couldn't create the service: %s", err) - cleanup(host, config) + local status, err = msrpc.service_create(host, config.service_name, config.path .. "\\" .. config.service_file) + if(status == false) then + stdnse.print_debug(1, "smb-psexec: Couldn't create the service: %s", err) + cleanup(host, config) - if(string.find(err, "MARKED_FOR_DELETE")) then - return false, string.format("Service is stuck in 'being deleted' phase on remote machine; try setting script-args=randomseed=abc for now", err) - else - return false, string.format("Couldn't create the service on the remote machine: %s", err) - end - end + if(string.find(err, "MARKED_FOR_DELETE")) then + return false, string.format("Service is stuck in 'being deleted' phase on remote machine; try setting script-args=randomseed=abc for now", err) + else + return false, string.format("Couldn't create the service on the remote machine: %s", err) + end + end - return true + return true end ---Create the list of parameters we're using to start the service. This consists @@ -1083,30 +1083,30 @@ end --@return status true or false --@return params A table of parameters if status is true, or an error message if status is false. local function get_params(config) - local count = 0 + local count = 0 - -- Build the table of parameters to pass to the service - local params = {} - table.insert(params, config.path .. "\\" .. config.output_file) - table.insert(params, config.path .. "\\" .. config.temp_output_file) - table.insert(params, tostring(#config.enabled_modules)) - table.insert(params, "0") - table.insert(params, config.key) - table.insert(params, config.path) - for _, mod in ipairs(config.enabled_modules) do - if(mod.upload) then - table.insert(params, config.path .. "\\" .. mod.upload_name .. " " .. (mod.args or "")) - else - table.insert(params, mod.program .. " " .. (mod.args or "")) - end + -- Build the table of parameters to pass to the service + local params = {} + table.insert(params, config.path .. "\\" .. config.output_file) + table.insert(params, config.path .. "\\" .. config.temp_output_file) + table.insert(params, tostring(#config.enabled_modules)) + table.insert(params, "0") + table.insert(params, config.key) + table.insert(params, config.path) + for _, mod in ipairs(config.enabled_modules) do + if(mod.upload) then + table.insert(params, config.path .. "\\" .. mod.upload_name .. " " .. (mod.args or "")) + else + table.insert(params, mod.program .. " " .. (mod.args or "")) + end - table.insert(params, (mod.env or "")) - table.insert(params, tostring(mod.headless)) - table.insert(params, tostring(mod.include_stderr)) - table.insert(params, mod.outfile or "") - end + table.insert(params, (mod.env or "")) + table.insert(params, tostring(mod.headless)) + table.insert(params, tostring(mod.include_stderr)) + table.insert(params, mod.outfile or "") + end - return true, params + return true, params end ---Start the service on the remote machine. @@ -1117,13 +1117,13 @@ end --@return status true or false --@return err An error message if status is false. local function start_service(host, config, params) - local status, err = msrpc.service_start(host, config.service_name, params) - if(status == false) then - stdnse.print_debug(1, "smb-psexec: Couldn't start the service: %s", err) - return false, string.format("Couldn't start the service on the remote machine: %s", err) - end + local status, err = msrpc.service_start(host, config.service_name, params) + if(status == false) then + stdnse.print_debug(1, "smb-psexec: Couldn't start the service: %s", err) + return false, string.format("Couldn't start the service on the remote machine: %s", err) + end - return true + return true end ---Poll for the output file on the remote machine until either the file is created, or the timeout @@ -1135,329 +1135,329 @@ end --@return result The file if status is true, or an error message if status is false. local function get_output_file(host, config) - stdnse.print_debug(1, "smb-psexec: Waiting for output file to be created (timeout = %d seconds)", config.timeout) - local status, result + stdnse.print_debug(1, "smb-psexec: Waiting for output file to be created (timeout = %d seconds)", config.timeout) + local status, result - local i = config.timeout - while true do - status, result = smb.file_read(host, config.share, "\\" .. config.output_file, nil, {file_create_disposition=1}) + local i = config.timeout + while true do + status, result = smb.file_read(host, config.share, "\\" .. config.output_file, nil, {file_create_disposition=1}) - if(not(status) and result ~= "NT_STATUS_OBJECT_NAME_NOT_FOUND") then - -- An unexpected error occurred - stdnse.print_debug(1, "smb-psexec: Couldn't read the file: %s", result) - cleanup(host, config) + if(not(status) and result ~= "NT_STATUS_OBJECT_NAME_NOT_FOUND") then + -- An unexpected error occurred + stdnse.print_debug(1, "smb-psexec: Couldn't read the file: %s", result) + cleanup(host, config) - return false, string.format("Couldn't read the file from the remote machine: %s", result) - end + return false, string.format("Couldn't read the file from the remote machine: %s", result) + end - if(not(status) and result == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then - -- An expected error occurred; if this happens, we just wait - if(i == 0) then - stdnse.print_debug(1, "smb-psexec: Error in remote service: output file was never created!") - cleanup(host, config) + if(not(status) and result == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then + -- An expected error occurred; if this happens, we just wait + if(i == 0) then + stdnse.print_debug(1, "smb-psexec: Error in remote service: output file was never created!") + cleanup(host, config) - return false, string.format("Error in remote service: output file was never created") - end + return false, string.format("Error in remote service: output file was never created") + end - stdnse.print_debug(1, "smb-psexec: Output file %s doesn't exist yet, waiting for %d more seconds", config.output_file, i) - stdnse.sleep(1) - i = i - 1 - end + stdnse.print_debug(1, "smb-psexec: Output file %s doesn't exist yet, waiting for %d more seconds", config.output_file, i) + stdnse.sleep(1) + i = i - 1 + end - if(status) then - break - end - end + if(status) then + break + end + end - return true, result + return true, result end ---Decide whether or not a line should be included in the output file, based on the module's -- find, remove, and noblank settings. local function should_be_included(mod, line) - local removed, found + local removed, found - -- Remove lines from the output, if the module requested it - removed = false - if(mod.remove and #mod.remove > 0) then - -- Make a single string into a table to save code - if(type(mod.remove) ~= 'table') then - mod.remove = {mod.remove} - end + -- Remove lines from the output, if the module requested it + removed = false + if(mod.remove and #mod.remove > 0) then + -- Make a single string into a table to save code + if(type(mod.remove) ~= 'table') then + mod.remove = {mod.remove} + end - -- Loop through the module's find table to see if any of the lines match - for _, remove in ipairs(mod.remove) do - if(string.match(line, remove)) then - removed = true - break - end - end - end + -- Loop through the module's find table to see if any of the lines match + for _, remove in ipairs(mod.remove) do + if(string.match(line, remove)) then + removed = true + break + end + end + end - -- Remove blank lines if we're supposed to - if(mod.noblank and line == "") then - removed = true - end + -- Remove blank lines if we're supposed to + if(mod.noblank and line == "") then + removed = true + end - -- If the line wasn't removed, and we are searching for specific text, do the search - found = false - if(mod.find and #mod.find > 0 and not(removed)) then - -- Make a single string a table to save duplicate code - if(type(mod.find) ~= 'table') then - mod.find = {mod.find} - end + -- If the line wasn't removed, and we are searching for specific text, do the search + found = false + if(mod.find and #mod.find > 0 and not(removed)) then + -- Make a single string a table to save duplicate code + if(type(mod.find) ~= 'table') then + mod.find = {mod.find} + end - -- Loop through the module's find table to see if any of the lines match - for _, find in ipairs(mod.find) do - if(string.match(line, find)) then - found = true - break - end - end - else - found = true - end + -- Loop through the module's find table to see if any of the lines match + for _, find in ipairs(mod.find) do + if(string.match(line, find)) then + found = true + break + end + end + else + found = true + end - -- Only display the line if it's found and not removed - return (found and not(removed)) + -- Only display the line if it's found and not removed + return (found and not(removed)) end ---Alter a line based on the module's 'replace' setting. local function do_replacements(mod, line) - if(mod.replace) then - for _, v in pairs(mod.replace) do + if(mod.replace) then + for _, v in pairs(mod.replace) do - -- It looks like Lua doesn't like replacing the null character, so have a sidecase for it - if(v[1] == string.char(0)) then - local newline = "" - for i = 1, #line, 1 do - local char = string.sub(line, i, i) - if(string.byte(char) == 0) then - newline = newline .. v[2] - else - newline = newline .. char - end - end - line = newline - else - line = string.gsub(line, v[1], v[2]) - end - end - end + -- It looks like Lua doesn't like replacing the null character, so have a sidecase for it + if(v[1] == string.char(0)) then + local newline = "" + for i = 1, #line, 1 do + local char = string.sub(line, i, i) + if(string.byte(char) == 0) then + newline = newline .. v[2] + else + newline = newline .. char + end + end + line = newline + else + line = string.gsub(line, v[1], v[2]) + end + end + end - return line + return line end ---Parse the output file into a neat array. local function parse_output(config, data) - -- Allow 'data' to be nil. This lets us skip most of the effort when all mods are disabled - data = data or "" + -- Allow 'data' to be nil. This lets us skip most of the effort when all mods are disabled + data = data or "" - -- Split the result at newlines - local lines = stdnse.strsplit("\n", data) + -- Split the result at newlines + local lines = stdnse.strsplit("\n", data) - local module_num = -1 - local mod = nil - local result = nil + local module_num = -1 + local mod = nil + local result = nil - -- Loop through the lines and parse them into the results table - local results = {} - for _, line in ipairs(lines) do - if(line ~= "") then - local this_module_num = tonumber(string.sub(line, 1, 1)) + -- Loop through the lines and parse them into the results table + local results = {} + for _, line in ipairs(lines) do + if(line ~= "") then + local this_module_num = tonumber(string.sub(line, 1, 1)) - -- Get the important part of the line - line = string.sub(line, 2) + -- Get the important part of the line + line = string.sub(line, 2) - -- Remove the Windows endline (0x0a) from the string (these are left in up to this point to maintain - -- the ability to download binary files, if that ever comes up - line = string.gsub(line, "\r", "") + -- Remove the Windows endline (0x0a) from the string (these are left in up to this point to maintain + -- the ability to download binary files, if that ever comes up + line = string.gsub(line, "\r", "") - -- If the module_number has changed, increment to the next module - if(this_module_num ~= (module_num % 10)) then - -- Increment our module number - if(module_num < 0) then - module_num = 0 - else - module_num = module_num + 1 - end + -- If the module_number has changed, increment to the next module + if(this_module_num ~= (module_num % 10)) then + -- Increment our module number + if(module_num < 0) then + module_num = 0 + else + module_num = module_num + 1 + end - -- Go to the next module, and make sure it exists - mod = config.enabled_modules[module_num + 1] - if(mod == nil) then - stdnse.print_debug(1, "Server's response wasn't formatted properly (mod %d); if you can reproduce, place report to dev@nmap.org", module_num) - stdnse.print_debug(1, "--\n" .. string.gsub("%%", "%%", data) .. "\n--") - return false, "Server's response wasn't formatted properly; if you can reproduce, place report to dev@nmap.org" - end + -- Go to the next module, and make sure it exists + mod = config.enabled_modules[module_num + 1] + if(mod == nil) then + stdnse.print_debug(1, "Server's response wasn't formatted properly (mod %d); if you can reproduce, place report to dev@nmap.org", module_num) + stdnse.print_debug(1, "--\n" .. string.gsub("%%", "%%", data) .. "\n--") + return false, "Server's response wasn't formatted properly; if you can reproduce, place report to dev@nmap.org" + end - -- Save this result - if(result ~= nil) then - table.insert(results, result) - end - result = {} - result['name'] = "" - result['lines'] = {} + -- Save this result + if(result ~= nil) then + table.insert(results, result) + end + result = {} + result['name'] = "" + result['lines'] = {} - if(mod.name) then - result['name'] = mod.name - else - result['name'] = string.format("'%s %s;", mod.program, (mod.args or "")) - end - end + if(mod.name) then + result['name'] = mod.name + else + result['name'] = string.format("'%s %s;", mod.program, (mod.args or "")) + end + end - local include = should_be_included(mod, line) + local include = should_be_included(mod, line) - -- If we're including it, do the replacements - if(include) then - line = do_replacements(mod, line) - table.insert(result, line) - end - end - end + -- If we're including it, do the replacements + if(include) then + line = do_replacements(mod, line) + table.insert(result, line) + end + end + end - table.insert(results, result) + table.insert(results, result) - -- Loop through the disabled modules and print them out - for _, mod in ipairs(config.disabled_modules) do - local result = {} - result['name'] = mod.name - if(mod.disabled_message == nil) then - mod.disabled_message = {"No reason for disabling the module was found"} - end + -- Loop through the disabled modules and print them out + for _, mod in ipairs(config.disabled_modules) do + local result = {} + result['name'] = mod.name + if(mod.disabled_message == nil) then + mod.disabled_message = {"No reason for disabling the module was found"} + end - if(type(mod.disabled_message) == 'string') then - mod.disabled_message = {mod.disabled_message} - end + if(type(mod.disabled_message) == 'string') then + mod.disabled_message = {mod.disabled_message} + end - for _, message in ipairs(mod.disabled_message) do - table.insert(result, "WARNING: " .. message) - end + for _, message in ipairs(mod.disabled_message) do + table.insert(result, "WARNING: " .. message) + end - table.insert(results, result) - end + table.insert(results, result) + end - return true, results + return true, results end action = function(host) - local status, result, err - local key + local status, result, err + local key - local i + local i - local params + local params - local config = {} - local files + local config = {} + local files - -- First check for nmap_service.exe; we can't do anything without it. - stdnse.print_debug(1, "smb-psexec: Looking for the service file: nmap_service or nmap_service.exe") - config.local_service_file = locate_file("nmap_service", "exe") - if (config.local_service_file == nil) then - if nmap.verbosity() > 0 then - return string.format([[ + -- First check for nmap_service.exe; we can't do anything without it. + stdnse.print_debug(1, "smb-psexec: Looking for the service file: nmap_service or nmap_service.exe") + config.local_service_file = locate_file("nmap_service", "exe") + if (config.local_service_file == nil) then + if nmap.verbosity() > 0 then + return string.format([[ Can't find the service file: nmap_service.exe (or nmap_service). Due to false positives in antivirus software, this module is no longer included by default. Please download it from %s and place it in nselib/data/psexec/ under the Nmap DATADIR. ]], NMAP_SERVICE_EXE_DOWNLOAD) - else - return - end - end + else + return + end + end - -- Parse the configuration file - status, config = get_config(host, config) - if(not(status)) then - return stdnse.format_output(false, config) - end + -- Parse the configuration file + status, config = get_config(host, config) + if(not(status)) then + return stdnse.format_output(false, config) + end - if(#config.enabled_modules > 0) then - -- Start by cleaning up, just in case. - cleanup(host, config) + if(#config.enabled_modules > 0) then + -- Start by cleaning up, just in case. + cleanup(host, config) - -- If the user just wanted a cleanup, do it - if(stdnse.get_script_args( "cleanup" )) then - return stdnse.format_output(true, "Cleanup complete.") - end + -- If the user just wanted a cleanup, do it + if(stdnse.get_script_args( "cleanup" )) then + return stdnse.format_output(true, "Cleanup complete.") + end - -- Check if any of the files exist - status, result, files = smb.files_exist(host, config.share, config.all_files, {}) - if(not(status)) then - return stdnse.format_output(false, "Couldn't log in to check for remote files: " .. result) - end - if(result > 0) then - local response = {} - table.insert(response, "One or more output files already exist on the host, and couldn't be removed. Try:") - table.insert(response, "* Running the script with --script-args=cleanup=1 to force a cleanup (passing -d and looking for error messages might help),") - table.insert(response, "* Running the script with --script-args=randomseed=ABCD (or something) to change the name of the uploaded files,") - table.insert(response, "* Changing the share and path using, for example, --script-args=share=C$,sharepath=C:, or") - table.insert(response, "* Deleting the affected file(s) off the server manually (\\\\" .. config.share .. "\\" .. stdnse.strjoin(", \\\\" .. config.share .. "\\", files) .. ")") - return stdnse.format_output(false, response) - end + -- Check if any of the files exist + status, result, files = smb.files_exist(host, config.share, config.all_files, {}) + if(not(status)) then + return stdnse.format_output(false, "Couldn't log in to check for remote files: " .. result) + end + if(result > 0) then + local response = {} + table.insert(response, "One or more output files already exist on the host, and couldn't be removed. Try:") + table.insert(response, "* Running the script with --script-args=cleanup=1 to force a cleanup (passing -d and looking for error messages might help),") + table.insert(response, "* Running the script with --script-args=randomseed=ABCD (or something) to change the name of the uploaded files,") + table.insert(response, "* Changing the share and path using, for example, --script-args=share=C$,sharepath=C:, or") + table.insert(response, "* Deleting the affected file(s) off the server manually (\\\\" .. config.share .. "\\" .. stdnse.strjoin(", \\\\" .. config.share .. "\\", files) .. ")") + return stdnse.format_output(false, response) + end - -- Upload the modules - status, err = upload_everything(host, config) - if(not(status)) then - cleanup(host, config) - return stdnse.format_output(false, err) - end + -- Upload the modules + status, err = upload_everything(host, config) + if(not(status)) then + cleanup(host, config) + return stdnse.format_output(false, err) + end - -- Create the service - status, err = create_service(host, config) - if(not(status)) then - cleanup(host, config) - return stdnse.format_output(false, err) - end + -- Create the service + status, err = create_service(host, config) + if(not(status)) then + cleanup(host, config) + return stdnse.format_output(false, err) + end - -- Get the table of parameters to pass to the service when we start it - status, params = get_params(config) - if(not(status)) then - cleanup(host, config) - return stdnse.format_output(false, params) - end + -- Get the table of parameters to pass to the service when we start it + status, params = get_params(config) + if(not(status)) then + cleanup(host, config) + return stdnse.format_output(false, params) + end - -- Start the service - status, params = start_service(host, config, params) - if(not(status)) then - cleanup(host, config) - return stdnse.format_output(false, params) - end + -- Start the service + status, params = start_service(host, config, params) + if(not(status)) then + cleanup(host, config) + return stdnse.format_output(false, params) + end - -- Get the result - status, result = get_output_file(host, config, config.share) - if(not(status)) then - cleanup(host, config) - return stdnse.format_output(false, result) - end + -- Get the result + status, result = get_output_file(host, config, config.share) + if(not(status)) then + cleanup(host, config) + return stdnse.format_output(false, result) + end - -- Do a final cleanup - cleanup(host, config) + -- Do a final cleanup + cleanup(host, config) - -- Uncipher the file - result = cipher(result, config) - end + -- Uncipher the file + result = cipher(result, config) + end - -- Build the output into a nice table - local response - status, response = parse_output(config, result) - if(status == false) then - return stdnse.format_output(false, "Couldn't parse output: " .. response) - end + -- Build the output into a nice table + local response + status, response = parse_output(config, result) + if(status == false) then + return stdnse.format_output(false, "Couldn't parse output: " .. response) + end - -- Add a warning if nothing was enabled - if(#config.enabled_modules == 0) then - if(#response == 0) then - response = {"No modules were enabled! Please check your configuration file."} - else - table.insert(response, "No modules were enabled! Please fix any errors displayed above, or check your configuration file.") - end - end + -- Add a warning if nothing was enabled + if(#config.enabled_modules == 0) then + if(#response == 0) then + response = {"No modules were enabled! Please check your configuration file."} + else + table.insert(response, "No modules were enabled! Please fix any errors displayed above, or check your configuration file.") + end + end - -- Return the string - return stdnse.format_output(true, response) + -- Return the string + return stdnse.format_output(true, response) end diff --git a/scripts/snmp-brute.nse b/scripts/snmp-brute.nse index 042d38ef5..edc033d86 100644 --- a/scripts/snmp-brute.nse +++ b/scripts/snmp-brute.nse @@ -60,234 +60,234 @@ portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) local communitiestable = {} local filltable = function(filename, table) - if #table ~= 0 then - return true - end + if #table ~= 0 then + return true + end - local file = io.open(filename, "r") + local file = io.open(filename, "r") - if not file then - return false - end + if not file then + return false + end - for l in file:lines() do - -- Comments takes up a whole line - if not l:match("#!comment:") then - table[#table + 1] = l - end - end + for l in file:lines() do + -- Comments takes up a whole line + if not l:match("#!comment:") then + table[#table + 1] = l + end + end - file:close() + file:close() - return true + return true end local closure = function(table) - local i = 1 + local i = 1 - return function(cmd) - if cmd == "reset" then - i = 1 - return - end - local elem = table[i] - if elem then i = i + 1 end - return elem - end + return function(cmd) + if cmd == "reset" then + i = 1 + return + end + local elem = table[i] + if elem then i = i + 1 end + return elem + end end local communities_raw = function(path) - if not path then - return false, "Cannot find communities list" - end + if not path then + return false, "Cannot find communities list" + end - if not filltable(path, communitiestable) then - return false, "Error parsing communities list" - end + if not filltable(path, communitiestable) then + return false, "Error parsing communities list" + end - return true, closure(communitiestable) + return true, closure(communitiestable) end local communities = function() - local communities_file = stdnse.get_script_args('snmp-brute.communitiesdb') or - nmap.fetchfile("nselib/data/snmpcommunities.lst") + local communities_file = stdnse.get_script_args('snmp-brute.communitiesdb') or + nmap.fetchfile("nselib/data/snmpcommunities.lst") - if communities_file then - stdnse.print_debug(1, "%s: Using the %s as the communities file", - SCRIPT_NAME, communities_file) + if communities_file then + stdnse.print_debug(1, "%s: Using the %s as the communities file", + SCRIPT_NAME, communities_file) - local status, iterator = communities_raw(communities_file) + local status, iterator = communities_raw(communities_file) - if not status then - return false, iterator - end + if not status then + return false, iterator + end - local time_limit = unpwdb.timelimit() - local count_limit = 0 + local time_limit = unpwdb.timelimit() + local count_limit = 0 - if stdnse.get_script_args("unpwdb.passlimit") then - count_limit = tonumber(stdnse.get_script_args("unpwdb.passlimit")) - end + if stdnse.get_script_args("unpwdb.passlimit") then + count_limit = tonumber(stdnse.get_script_args("unpwdb.passlimit")) + end - return true, unpwdb.limited_iterator(iterator, time_limit, count_limit) - else - stdnse.print_debug(1, "%s: Cannot read the communities file, using the nmap username/password database instead", - SCRIPT_NAME) + return true, unpwdb.limited_iterator(iterator, time_limit, count_limit) + else + stdnse.print_debug(1, "%s: Cannot read the communities file, using the nmap username/password database instead", + SCRIPT_NAME) - return unpwdb.passwords() - end + return unpwdb.passwords() + end end 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 community = nextcommunity() + local payload, status, response, err + local community = nextcommunity() - while community do - if result.status == false then - --in case the sniff_snmp_responses thread was shut down - condvar("signal") - return - end - payload = snmp.encode(snmp.buildPacket(request, 0, community)) - status, err = socket:send(payload) - if not status then - result.status = false - result.msg = "Could not send SNMP probe" - condvar "signal" - return - end + while community do + if result.status == false then + --in case the sniff_snmp_responses thread was shut down + condvar("signal") + return + end + payload = snmp.encode(snmp.buildPacket(request, 0, community)) + status, err = socket:send(payload) + if not status then + result.status = false + result.msg = "Could not send SNMP probe" + condvar "signal" + return + end - community = nextcommunity() - end + community = nextcommunity() + end - result.sent = true - condvar("signal") + result.sent = true + condvar("signal") end local sniff_snmp_responses = function(host, port, lport, result) - local condvar = nmap.condvar(result) + local condvar = nmap.condvar(result) - local pcap = nmap.new_socket() - pcap:set_timeout(host.times.timeout * 1000 * 3) - 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)) - pcap:pcap_open(host.interface, 104, false,"dst host " .. ip .. " and udp and src port 161 and dst port " .. lport) + local pcap = nmap.new_socket() + pcap:set_timeout(host.times.timeout * 1000 * 3) + 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)) + 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 - local last_run = false + -- last_run indicated whether there will be only one more receive + local last_run = false - -- receive even when status=false untill all the probes are sent - while true do - local status, plen, l2, l3, _ = pcap:pcap_receive() + -- receive even when status=false untill all the probes are sent + while true do + local status, plen, l2, l3, _ = pcap:pcap_receive() - if status then - local p = packet.Packet:new(l3,#l3) - if not p:udp_parse() then - --shouldn't happen - result.status = false - result.msg = "Wrong type of packet received" - condvar "signal" - return - end + if status then + local p = packet.Packet:new(l3,#l3) + if not p:udp_parse() then + --shouldn't happen + result.status = false + result.msg = "Wrong type of packet received" + condvar "signal" + return + end - local response = p:raw(28, #p.buf) - local res - _, res = snmp.decode(response) + local response = p:raw(28, #p.buf) + local res + _, res = snmp.decode(response) - if type(res) == "table" then - result.communities[ #(result.communities) + 1 ] = res[2] - else - result.status = false - result.msg = "Wrong type of SNMP response received" - condvar "signal" - return - end - else - if last_run then - condvar "signal" - return - else - if result.sent then - last_run = true - end - end - end - end - pcap:close() - condvar "signal" - return + if type(res) == "table" then + result.communities[ #(result.communities) + 1 ] = res[2] + else + result.status = false + result.msg = "Wrong type of SNMP response received" + condvar "signal" + return + end + else + if last_run then + condvar "signal" + return + else + if result.sent then + last_run = true + end + end + end + end + pcap:close() + condvar "signal" + return end action = function(host, port) - local status, nextcommunity = communities() + local status, nextcommunity = communities() - if not status then - return "\n ERROR: Failed to read the communities database" - end + if not status then + return "\n ERROR: Failed to read the communities database" + end - local result = {} - local threads = {} + local result = {} + local threads = {} - local condvar = nmap.condvar(result) + local condvar = nmap.condvar(result) - result.sent = false --whether the probes are sent - result.communities = {} -- list of valid community strings - result.msg = "" -- Error/Status msg - result.status = true -- Status (is everything ok) + result.sent = false --whether the probes are sent + result.communities = {} -- list of valid community strings + result.msg = "" -- Error/Status msg + result.status = true -- Status (is everything ok) - local socket = nmap.new_socket("udp") - status = socket:connect(host, port) + local socket = nmap.new_socket("udp") + status = socket:connect(host, port) - if ( not(status) ) then - return "\n ERROR: Failed to connect to server" - end + if ( not(status) ) then + return "\n ERROR: Failed to connect to server" + end - local status, _, lport = socket:get_info() - if( not(status) ) then - return "\n ERROR: Failed to retrieve local port" - end + local status, _, lport = socket:get_info() + if( not(status) ) then + return "\n ERROR: Failed to retrieve local port" + end - 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 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 recv_dead, send_dead - while true do - condvar "wait" - recv_dead = (coroutine.status(recv_co) == "dead") - send_dead = (coroutine.status(send_co) == "dead") - if recv_dead then break end - end + local recv_dead, send_dead + while true do + condvar "wait" + recv_dead = (coroutine.status(recv_co) == "dead") + send_dead = (coroutine.status(send_co) == "dead") + if recv_dead then break end + end - socket:close() + socket:close() - if result.status then - -- add the community strings to the creds database - local c = creds.Credentials:new(SCRIPT_NAME, host, port) - for _, community_string in ipairs(result.communities) do - c:add("",community_string, creds.State.VALID) - end + if result.status then + -- add the community strings to the creds database + local c = creds.Credentials:new(SCRIPT_NAME, host, port) + for _, community_string in ipairs(result.communities) do + c:add("",community_string, creds.State.VALID) + end - -- insert the first community string as a snmpcommunity registry field - local creds_iter = c:getCredentials() - if creds_iter then - local account = creds_iter() - if account then - if account.pass == "" then - nmap.registry.snmpcommunity = "" - else - nmap.registry.snmpcommunity = account.pass - end - end - end + -- insert the first community string as a snmpcommunity registry field + local creds_iter = c:getCredentials() + if creds_iter then + local account = creds_iter() + if account then + if account.pass == "" then + nmap.registry.snmpcommunity = "" + else + nmap.registry.snmpcommunity = account.pass + end + end + end - -- return output - return tostring(c) - else - stdnse.print_debug("An error occured: "..result.msg) - end + -- return output + return tostring(c) + else + stdnse.print_debug("An error occured: "..result.msg) + end end diff --git a/scripts/snmp-interfaces.nse b/scripts/snmp-interfaces.nse index 2ba346886..148c04bc6 100644 --- a/scripts/snmp-interfaces.nse +++ b/scripts/snmp-interfaces.nse @@ -51,14 +51,14 @@ dependencies = {"snmp-brute"} prerule = function() - if not stdnse.get_script_args({"snmp-interfaces.host", "host"}) then - stdnse.print_debug(3, - "Skipping '%s' %s, 'snmp-interfaces.host' argument is missing.", - SCRIPT_NAME, SCRIPT_TYPE) - return false - end + if not stdnse.get_script_args({"snmp-interfaces.host", "host"}) then + stdnse.print_debug(3, + "Skipping '%s' %s, 'snmp-interfaces.host' argument is missing.", + SCRIPT_NAME, SCRIPT_TYPE) + return false + end - return true + return true end 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 -- REVISION "201002110000Z" local iana_types = { "other", "regular1822", "hdh1822", "ddnX25", "rfc877x25", "ethernetCsmacd", - "iso88023Csmacd", "iso88024TokenBus", "iso88025TokenRing", "iso88026Man", "starLan", - "proteon10Mbit", "proteon80Mbit", "hyperchannel", "fddi", "lapb", "sdlc", "ds1", "e1", - "basicISDN", "primaryISDN", "propPointToPointSerial", "ppp", "softwareLoopback", "eon", - "ethernet3Mbit", "nsip", "slip", "ultra", "ds3", "sip", "frameRelay", "rs232", "para", - "arcnet", "arcnetPlus", "atm", "miox25", "sonet", "x25ple", "iso88022llc", "localTalk", - "smdsDxi", "frameRelayService", "v35", "hssi", "hippi", "modem", "aal5", "sonetPath", - "sonetVT", "smdsIcip", "propVirtual", "propMultiplexor", "ieee80212", "fibreChannel", - "hippiInterface", "frameRelayInterconnect", "aflane8023", "aflane8025", "cctEmul", - "fastEther", "isdn", "v11", "v36", "g703at64k", "g703at2mb", "qllc", "fastEtherFX", - "channel", "ieee80211", "ibm370parChan", "escon", "dlsw", "isdns", "isdnu", "lapd", - "ipSwitch", "rsrb", "atmLogical", "ds0", "ds0Bundle", "bsc", "async", "cnr", - "iso88025Dtr", "eplrs", "arap", "propCnls", "hostPad", "termPad", "frameRelayMPI", - "x213", "adsl", "radsl", "sdsl", "vdsl", "iso88025CRFPInt", "myrinet", "voiceEM", - "voiceFXO", "voiceFXS", "voiceEncap", "voiceOverIp", "atmDxi", "atmFuni", "atmIma", - "pppMultilinkBundle", "ipOverCdlc", "ipOverClaw", "stackToStack", "virtualIpAddress", - "mpc", "ipOverAtm", "iso88025Fiber", "tdlc", "gigabitEthernet", "hdlc", "lapf", "v37", - "x25mlp", "x25huntGroup", "trasnpHdlc", "interleave", "fast", "ip", "docsCableMaclayer", - "docsCableDownstream", "docsCableUpstream", "a12MppSwitch", "tunnel", "coffee", "ces", - "atmSubInterface", "l2vlan", "l3ipvlan", "l3ipxvlan", "digitalPowerlinev", "mediaMailOverIp", - "dtm", "dcn", "ipForward", "msdsl", "ieee1394", "if-gsn", "dvbRccMacLayer", "dvbRccDownstream", - "dvbRccUpstream", "atmVirtual", "mplsTunnel", "srp", "voiceOverAtm", "voiceOverFrameRelay", - "idsl", "compositeLink", "ss7SigLink", "propWirelessP2P", "frForward", "rfc1483", "usb", - "ieee8023adLag", "bgppolicyaccounting", "frf16MfrBundle", "h323Gatekeeper", "h323Proxy", - "mpls", "mfSigLink", "hdsl2", "shdsl", "ds1FDL", "pos", "dvbAsiIn", "dvbAsiOut", "plc", - "nfas", "tr008", "gr303RDT", "gr303IDT", "isup", "propDocsWirelessMaclayer", - "propDocsWirelessDownstream", "propDocsWirelessUpstream", "hiperlan2", "propBWAp2Mp", - "sonetOverheadChannel", "digitalWrapperOverheadChannel", "aal2", "radioMAC", "atmRadio", - "imt", "mvl", "reachDSL", "frDlciEndPt", "atmVciEndPt", "opticalChannel", "opticalTransport", - "propAtm", "voiceOverCable", "infiniband", "teLink", "q2931", "virtualTg", "sipTg", "sipSig", - "docsCableUpstreamChannel", "econet", "pon155", "pon622", "bridge", "linegroup", "voiceEMFGD", - "voiceFGDEANA", "voiceDID", "mpegTransport", "sixToFour", "gtp", "pdnEtherLoop1", - "pdnEtherLoop2", "opticalChannelGroup", "homepna", "gfp", "ciscoISLvlan", "actelisMetaLOOP", - "fcipLink", "rpr", "qam", "lmp", "cblVectaStar", "docsCableMCmtsDownstream", "adsl2", - "macSecControlledIF", "macSecUncontrolledIF", "aviciOpticalEther", "atmbond", "voiceFGDOS", - "mocaVersion1", "ieee80216WMAN", "adsl2plus", "dvbRcsMacLayer", "dvbTdm", "dvbRcsTdma", - "x86Laps", "wwanPP", "wwanPP2", "voiceEBS", "ifPwType", "ilan", "pip", "aluELP", "gpon", - "vdsl2", "capwapDot11Profile", "capwapDot11Bss", "capwapWtpVirtualRadio" } + "iso88023Csmacd", "iso88024TokenBus", "iso88025TokenRing", "iso88026Man", "starLan", + "proteon10Mbit", "proteon80Mbit", "hyperchannel", "fddi", "lapb", "sdlc", "ds1", "e1", + "basicISDN", "primaryISDN", "propPointToPointSerial", "ppp", "softwareLoopback", "eon", + "ethernet3Mbit", "nsip", "slip", "ultra", "ds3", "sip", "frameRelay", "rs232", "para", + "arcnet", "arcnetPlus", "atm", "miox25", "sonet", "x25ple", "iso88022llc", "localTalk", + "smdsDxi", "frameRelayService", "v35", "hssi", "hippi", "modem", "aal5", "sonetPath", + "sonetVT", "smdsIcip", "propVirtual", "propMultiplexor", "ieee80212", "fibreChannel", + "hippiInterface", "frameRelayInterconnect", "aflane8023", "aflane8025", "cctEmul", + "fastEther", "isdn", "v11", "v36", "g703at64k", "g703at2mb", "qllc", "fastEtherFX", + "channel", "ieee80211", "ibm370parChan", "escon", "dlsw", "isdns", "isdnu", "lapd", + "ipSwitch", "rsrb", "atmLogical", "ds0", "ds0Bundle", "bsc", "async", "cnr", + "iso88025Dtr", "eplrs", "arap", "propCnls", "hostPad", "termPad", "frameRelayMPI", + "x213", "adsl", "radsl", "sdsl", "vdsl", "iso88025CRFPInt", "myrinet", "voiceEM", + "voiceFXO", "voiceFXS", "voiceEncap", "voiceOverIp", "atmDxi", "atmFuni", "atmIma", + "pppMultilinkBundle", "ipOverCdlc", "ipOverClaw", "stackToStack", "virtualIpAddress", + "mpc", "ipOverAtm", "iso88025Fiber", "tdlc", "gigabitEthernet", "hdlc", "lapf", "v37", + "x25mlp", "x25huntGroup", "trasnpHdlc", "interleave", "fast", "ip", "docsCableMaclayer", + "docsCableDownstream", "docsCableUpstream", "a12MppSwitch", "tunnel", "coffee", "ces", + "atmSubInterface", "l2vlan", "l3ipvlan", "l3ipxvlan", "digitalPowerlinev", "mediaMailOverIp", + "dtm", "dcn", "ipForward", "msdsl", "ieee1394", "if-gsn", "dvbRccMacLayer", "dvbRccDownstream", + "dvbRccUpstream", "atmVirtual", "mplsTunnel", "srp", "voiceOverAtm", "voiceOverFrameRelay", + "idsl", "compositeLink", "ss7SigLink", "propWirelessP2P", "frForward", "rfc1483", "usb", + "ieee8023adLag", "bgppolicyaccounting", "frf16MfrBundle", "h323Gatekeeper", "h323Proxy", + "mpls", "mfSigLink", "hdsl2", "shdsl", "ds1FDL", "pos", "dvbAsiIn", "dvbAsiOut", "plc", + "nfas", "tr008", "gr303RDT", "gr303IDT", "isup", "propDocsWirelessMaclayer", + "propDocsWirelessDownstream", "propDocsWirelessUpstream", "hiperlan2", "propBWAp2Mp", + "sonetOverheadChannel", "digitalWrapperOverheadChannel", "aal2", "radioMAC", "atmRadio", + "imt", "mvl", "reachDSL", "frDlciEndPt", "atmVciEndPt", "opticalChannel", "opticalTransport", + "propAtm", "voiceOverCable", "infiniband", "teLink", "q2931", "virtualTg", "sipTg", "sipSig", + "docsCableUpstreamChannel", "econet", "pon155", "pon622", "bridge", "linegroup", "voiceEMFGD", + "voiceFGDEANA", "voiceDID", "mpegTransport", "sixToFour", "gtp", "pdnEtherLoop1", + "pdnEtherLoop2", "opticalChannelGroup", "homepna", "gfp", "ciscoISLvlan", "actelisMetaLOOP", + "fcipLink", "rpr", "qam", "lmp", "cblVectaStar", "docsCableMCmtsDownstream", "adsl2", + "macSecControlledIF", "macSecUncontrolledIF", "aviciOpticalEther", "atmbond", "voiceFGDOS", + "mocaVersion1", "ieee80216WMAN", "adsl2plus", "dvbRcsMacLayer", "dvbTdm", "dvbRcsTdma", + "x86Laps", "wwanPP", "wwanPP2", "voiceEBS", "ifPwType", "ilan", "pip", "aluELP", "gpon", + "vdsl2", "capwapDot11Profile", "capwapDot11Bss", "capwapWtpVirtualRadio" } --- 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 function get_value_from_table( tbl, oid ) - for _, v in ipairs( tbl ) do - if v.oid == oid then - return v.value - end - end + for _, v in ipairs( tbl ) do + if v.oid == oid then + return v.value + end + end - return nil + return nil end --- 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 -- @return string description of interface type, or "Unknown" if type not found function get_iana_type( iana ) - -- 254 types are currently defined - -- if the requested type falls outside that range, reset to "other" - if iana > 254 or iana < 1 then - iana = 1 - end + -- 254 types are currently defined + -- if the requested type falls outside that range, reset to "other" + if iana > 254 or iana < 1 then + iana = 1 + end - return iana_types[iana] + return iana_types[iana] end --- Calculates the speed of the interface based on the snmp value @@ -141,20 +141,20 @@ end -- @param speed value from IF-MIB::ifSpeed -- @return string description of speed function get_if_speed( speed ) - local result + local result - -- GigE or 10GigE speeds - if speed >= 1000000000 then - result = string.format( "%d Gbps", speed / 1000000000) - -- Common for 10 or 100 Mbit ethernet - elseif speed >= 1000000 then - result = string.format( "%d Mbps", speed / 1000000) - -- Anything slower report in Kbps - else - result = string.format( "%d Kbps", speed / 1000) - end + -- GigE or 10GigE speeds + if speed >= 1000000000 then + result = string.format( "%d Gbps", speed / 1000000000) + -- Common for 10 or 100 Mbit ethernet + elseif speed >= 1000000 then + result = string.format( "%d Mbps", speed / 1000000) + -- Anything slower report in Kbps + else + result = string.format( "%d Kbps", speed / 1000) + end - return result + return result end --- 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 -- @return string description of traffic amount function get_traffic( amount ) - local result + local result - -- Gigabytes - if amount >= 1000000000 then - result = string.format( "%.2f Gb", amount / 1000000000) - -- Megabytes - elseif amount >= 1000000 then - result = string.format( "%.2f Mb", amount / 1000000) - -- Anything lower report in kb - else - result = string.format( "%.2f Kb", amount / 1000) - end + -- Gigabytes + if amount >= 1000000000 then + result = string.format( "%.2f Gb", amount / 1000000000) + -- Megabytes + elseif amount >= 1000000 then + result = string.format( "%.2f Mb", amount / 1000000) + -- Anything lower report in kb + else + result = string.format( "%.2f Kb", amount / 1000) + end - return result + return result end --- Converts a 6 byte string into the familiar MAC address formatting @@ -183,17 +183,17 @@ end -- @param mac string containing the MAC address -- @return formatted string suitable for printing function get_mac_addr( mac ) - local catch = function() return end - local try = nmap.new_try(catch) - local mac_prefixes = try(datafiles.parse_mac_prefixes()) + local catch = function() return end + local try = nmap.new_try(catch) + local mac_prefixes = try(datafiles.parse_mac_prefixes()) - if mac:len() ~= 6 then - return "Unknown" - else - 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" - return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf ) - end + if mac:len() ~= 6 then + return "Unknown" + else + 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" + return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf ) + end end --- Processes the list of network interfaces @@ -202,87 +202,87 @@ end -- @return table with network interfaces described in key / value pairs 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." - 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_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_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_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 new_tbl = {} + -- 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_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_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_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_out_octets = "1.3.6.1.2.1.2.2.1.16." + local new_tbl = {} - -- 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 - new_tbl.index_list = {} + -- 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 + new_tbl.index_list = {} - for _, v in ipairs( tbl ) do + for _, v in ipairs( tbl ) do - if ( v.oid:match("^" .. if_index) ) then - local item = {} - item.index = get_value_from_table( tbl, v.oid ) + if ( v.oid:match("^" .. if_index) ) then + local item = {} + item.index = get_value_from_table( tbl, v.oid ) - local objid = v.oid:gsub( "^" .. if_index, if_descr) - local value = get_value_from_table( tbl, objid ) + local objid = v.oid:gsub( "^" .. if_index, if_descr) + local value = get_value_from_table( tbl, objid ) - if value and value:len() > 0 then - item.descr = value - end + if value and value:len() > 0 then + item.descr = value + end - objid = v.oid:gsub( "^" .. if_index, if_type ) - value = get_value_from_table( tbl, objid ) + objid = v.oid:gsub( "^" .. if_index, if_type ) + value = get_value_from_table( tbl, objid ) - if value then - item.type = get_iana_type(value) - end + if value then + item.type = get_iana_type(value) + end - objid = v.oid:gsub( "^" .. if_index, if_speed ) - value = get_value_from_table( tbl, objid ) + objid = v.oid:gsub( "^" .. if_index, if_speed ) + value = get_value_from_table( tbl, objid ) - if value then - item.speed = get_if_speed( value ) - end + if value then + item.speed = get_if_speed( value ) + end - objid = v.oid:gsub( "^" .. if_index, if_phys_addr ) - value = get_value_from_table( tbl, objid ) + objid = v.oid:gsub( "^" .. if_index, if_phys_addr ) + value = get_value_from_table( tbl, objid ) - if value and value:len() > 0 then - item.phys_addr = get_mac_addr( value ) - end + if value and value:len() > 0 then + item.phys_addr = get_mac_addr( value ) + end - objid = v.oid:gsub( "^" .. if_index, if_status ) - value = get_value_from_table( tbl, objid ) + objid = v.oid:gsub( "^" .. if_index, if_status ) + value = get_value_from_table( tbl, objid ) - if value == 1 then - item.status = "up" - elseif value == 2 then - item.status = "down" - end + if value == 1 then + item.status = "up" + elseif value == 2 then + item.status = "down" + end - objid = v.oid:gsub( "^" .. if_index, if_in_octets ) - value = get_value_from_table( tbl, objid ) + objid = v.oid:gsub( "^" .. if_index, if_in_octets ) + value = get_value_from_table( tbl, objid ) - if value then - item.received = get_traffic( value ) - end + if value then + item.received = get_traffic( value ) + end - objid = v.oid:gsub( "^" .. if_index, if_out_octets ) - value = get_value_from_table( tbl, objid ) + objid = v.oid:gsub( "^" .. if_index, if_out_octets ) + value = get_value_from_table( tbl, objid ) - if value then - item.sent = get_traffic( value ) - end + if value then + item.sent = get_traffic( value ) + end - new_tbl[item.index] = item - -- Add this interface index to our master list - table.insert( new_tbl.index_list, item.index ) + new_tbl[item.index] = item + -- Add this interface index to our master list + table.insert( new_tbl.index_list, item.index ) - end + end - end + end - return new_tbl + return new_tbl end @@ -292,34 +292,34 @@ end -- @param ip_tbl table containing oid and value pairs from IP::MIB -- @return table with network interfaces described in key / value pairs function process_ips( if_tbl, ip_tbl ) - 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_netmask = "1.3.6.1.2.1.4.20.1.3." - local index - local item + 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_netmask = "1.3.6.1.2.1.4.20.1.3." + local index + local item - for _, v in ipairs( ip_tbl ) do - if ( v.oid:match("^" .. ip_index) ) then - index = get_value_from_table( ip_tbl, v.oid ) - item = if_tbl[index] + for _, v in ipairs( ip_tbl ) do + if ( v.oid:match("^" .. ip_index) ) then + index = get_value_from_table( ip_tbl, v.oid ) + item = if_tbl[index] - local objid = v.oid:gsub( "^" .. ip_index, ip_addr ) - local value = get_value_from_table( ip_tbl, objid ) + local objid = v.oid:gsub( "^" .. ip_index, ip_addr ) + local value = get_value_from_table( ip_tbl, objid ) - if value then - item.ip_addr = value - end + if value then + item.ip_addr = value + end - objid = v.oid:gsub( "^" .. ip_index, ip_netmask ) - value = get_value_from_table( ip_tbl, objid ) + objid = v.oid:gsub( "^" .. ip_index, ip_netmask ) + value = get_value_from_table( ip_tbl, objid ) - if value then - item.netmask = value - end - end - end + if value then + item.netmask = value + end + end + end - return if_tbl + return if_tbl end --- Creates a table of IP addresses from the table of network interfaces @@ -327,16 +327,16 @@ end -- @param tbl table containing network interfaces -- @return table containing only IP addresses function list_addrs( tbl ) - local new_tbl = {} + local new_tbl = {} - for _, index in ipairs( tbl.index_list ) do - local interface = tbl[index] - if interface.ip_addr then - table.insert( new_tbl, interface.ip_addr ) - end - end + for _, index in ipairs( tbl.index_list ) do + local interface = tbl[index] + if interface.ip_addr then + table.insert( new_tbl, interface.ip_addr ) + end + end - return new_tbl + return new_tbl end --- Process the table of network interfaces for reporting @@ -344,132 +344,132 @@ end -- @param tbl table containing network interfaces -- @return table suitable for stdnse.format_output function build_results( tbl ) - local new_tbl = {} - local verbose = nmap.verbosity() + local new_tbl = {} + local verbose = nmap.verbosity() - -- For each interface index previously discovered, format the relevant information for output - for _, index in ipairs( tbl.index_list ) do - local interface = tbl[index] - local item = {} - local status = interface.status - local if_type = interface.type + -- For each interface index previously discovered, format the relevant information for output + for _, index in ipairs( tbl.index_list ) do + local interface = tbl[index] + local item = {} + local status = interface.status + local if_type = interface.type - if interface.descr then - item.name = interface.descr - else - item.name = string.format("Interface %d", index) - end + if interface.descr then + item.name = interface.descr + else + item.name = string.format("Interface %d", index) + end - if interface.ip_addr and interface.netmask then - table.insert( item, ("IP address: %s Netmask: %s"):format( interface.ip_addr, interface.netmask ) ) - end + if interface.ip_addr and interface.netmask then + table.insert( item, ("IP address: %s Netmask: %s"):format( interface.ip_addr, interface.netmask ) ) + end - if interface.phys_addr then - table.insert( item, ("MAC address: %s"):format( interface.phys_addr ) ) - end + if interface.phys_addr then + table.insert( item, ("MAC address: %s"):format( interface.phys_addr ) ) + end - if interface.type and interface.speed then - table.insert( item, ("Type: %s Speed: %s"):format( interface.type, interface.speed ) ) - end + if interface.type and interface.speed then + table.insert( item, ("Type: %s Speed: %s"):format( interface.type, interface.speed ) ) + end - if ( verbose > 0 ) and interface.status then - table.insert( item, ("Status: %s"):format( interface.status ) ) - end + if ( verbose > 0 ) and interface.status then + table.insert( item, ("Status: %s"):format( interface.status ) ) + end - if interface.sent and interface.received then - table.insert( item, ("Traffic stats: %s sent, %s received"):format( interface.sent, interface.received ) ) - end + if interface.sent and interface.received then + table.insert( item, ("Traffic stats: %s sent, %s received"):format( interface.sent, interface.received ) ) + end - if ( verbose > 0 ) or status == "up" then - table.insert( new_tbl, item ) - end - end + if ( verbose > 0 ) or status == "up" then + table.insert( new_tbl, item ) + end + end - return new_tbl + return new_tbl end action = function(host, port) - local socket = nmap.new_socket() - local catch = function() socket:close() end - local try = nmap.new_try(catch) - -- IF-MIB - used to look up network interfaces - local if_oid = "1.3.6.1.2.1.2.2.1" - -- IP-MIB - used to determine IP address information - local ip_oid = "1.3.6.1.2.1.4.20" - local interfaces = {} - local ips = {} - local status - local srvhost, srvport + local socket = nmap.new_socket() + local catch = function() socket:close() end + local try = nmap.new_try(catch) + -- IF-MIB - used to look up network interfaces + local if_oid = "1.3.6.1.2.1.2.2.1" + -- IP-MIB - used to determine IP address information + local ip_oid = "1.3.6.1.2.1.4.20" + local interfaces = {} + local ips = {} + local status + local srvhost, srvport - if SCRIPT_TYPE == "prerule" then - srvhost = stdnse.get_script_args({"snmp-interfaces.host", "host"}) - if not srvhost then - -- Shouldn't happen; checked in prerule. - return - end + if SCRIPT_TYPE == "prerule" then + srvhost = stdnse.get_script_args({"snmp-interfaces.host", "host"}) + if not srvhost then + -- Shouldn't happen; checked in prerule. + return + end - srvport = stdnse.get_script_args({"snmp-interfaces.port", "port"}) - if srvport then - srvport = tonumber(srvport) - else - srvport = 161 - end - else - srvhost = host.ip - srvport = port.number - end + srvport = stdnse.get_script_args({"snmp-interfaces.port", "port"}) + if srvport then + srvport = tonumber(srvport) + else + srvport = 161 + end + else + srvhost = host.ip + srvport = port.number + end - socket:set_timeout(5000) - try(socket:connect(srvhost, srvport, "udp")) + socket:set_timeout(5000) + try(socket:connect(srvhost, srvport, "udp")) - -- retreive network interface information from IF-MIB - status, interfaces = snmp.snmpWalk( socket, if_oid ) - socket:close() + -- retreive network interface information from IF-MIB + status, interfaces = snmp.snmpWalk( socket, if_oid ) + socket:close() - if (not(status)) or ( interfaces == nil ) or ( #interfaces == 0 ) then - return - end + if (not(status)) or ( interfaces == nil ) or ( #interfaces == 0 ) then + return + 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 - interfaces = process_interfaces( interfaces ) + -- build a table of network interfaces from the IF-MIB table + interfaces = process_interfaces( interfaces ) - -- retreive IP address information from IP-MIB - try(socket:connect(srvhost, srvport, "udp")) - status, ips = snmp.snmpWalk( socket, ip_oid ) + -- retreive IP address information from IP-MIB + try(socket:connect(srvhost, srvport, "udp")) + status, ips = snmp.snmpWalk( socket, ip_oid ) - -- associate that IP address information with the correct interface - if (not(status)) or ( ips ~= nil ) and ( #ips ~= 0 ) then - interfaces = process_ips( interfaces, ips ) - end + -- associate that IP address information with the correct interface + if (not(status)) or ( ips ~= nil ) and ( #ips ~= 0 ) then + interfaces = process_ips( interfaces, ips ) + 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 - local sum = 0 + if SCRIPT_TYPE == "prerule" and target.ALLOW_NEW_TARGETS then + local sum = 0 - ips = list_addrs(interfaces) + ips = list_addrs(interfaces) - -- Could add all of the addresses at once, but count - -- successful additions instead for script output - for _, i in ipairs(ips) do - local st, err = target.add(i) - if st then - sum = sum + 1 - else - stdnse.print_debug("Couldn't add target " .. i .. ": " .. err) - end - end + -- Could add all of the addresses at once, but count + -- successful additions instead for script output + for _, i in ipairs(ips) do + local st, err = target.add(i) + if st then + sum = sum + 1 + else + stdnse.print_debug("Couldn't add target " .. i .. ": " .. err) + end + end - if sum ~= 0 then - output = output .. "\nSuccessfully added " .. tostring(sum) .. " new targets" - end - elseif SCRIPT_TYPE == "portrule" then - nmap.set_port_state(host, port, "open") - end + if sum ~= 0 then + output = output .. "\nSuccessfully added " .. tostring(sum) .. " new targets" + end + elseif SCRIPT_TYPE == "portrule" then + nmap.set_port_state(host, port, "open") + end - return output + return output end diff --git a/scripts/telnet-brute.nse b/scripts/telnet-brute.nse index 047bf097e..2df08a3e6 100644 --- a/scripts/telnet-brute.nse +++ b/scripts/telnet-brute.nse @@ -62,7 +62,7 @@ local pcreptn = {} -- cache of compiled PCRE patterns -- @param fmt Format string. -- @param ... Arguments to format. local print_debug = function (level, fmt, ...) - stdnse.print_debug(level, "%s: " .. fmt, SCRIPT_NAME, ...) + stdnse.print_debug(level, "%s: " .. fmt, SCRIPT_NAME, ...) end @@ -73,10 +73,10 @@ end -- @param str The string to analyze -- @return Verdict (true or false) local is_username_prompt = function (str) - pcreptn.username_prompt = pcreptn.username_prompt - or pcre.new("\\b(?:username|login)\\s*:\\s*$", - pcre.flags().CASELESS, "C") - return pcreptn.username_prompt:match(str) + pcreptn.username_prompt = pcreptn.username_prompt + or pcre.new("\\b(?:username|login)\\s*:\\s*$", + pcre.flags().CASELESS, "C") + return pcreptn.username_prompt:match(str) end @@ -87,10 +87,10 @@ end -- @param str The string to analyze -- @return Verdict (true or false) local is_password_prompt = function (str) - pcreptn.password_prompt = pcreptn.password_prompt - or pcre.new("\\bpass(?:word|code)\\s*:\\s*$", - pcre.flags().CASELESS, "C") - return pcreptn.password_prompt:match(str) + pcreptn.password_prompt = pcreptn.password_prompt + or pcre.new("\\bpass(?:word|code)\\s*:\\s*$", + pcre.flags().CASELESS, "C") + return pcreptn.password_prompt:match(str) end @@ -101,14 +101,14 @@ end -- @param str The string to analyze -- @return Verdict (true or false) local is_login_success = function (str) - pcreptn.login_success = pcreptn.login_success - or pcre.new("[/>%$#]\\s*$" -- general prompt - .. "|^Last login\\s*:" -- linux telnetd - .. "|^(?-i:[A-Z]):\\\\" -- Windows telnet - .. "|Main(?:\\s|\\x1B\\[\\d+;\\d+H)Menu\\b" -- Netgear RM356 - .. "|^Enter Terminal Emulation:\\s*$", -- Hummingbird telnetd - pcre.flags().CASELESS, "C") - return pcreptn.login_success:match(str) + pcreptn.login_success = pcreptn.login_success + or pcre.new("[/>%$#]\\s*$" -- general prompt + .. "|^Last login\\s*:" -- linux telnetd + .. "|^(?-i:[A-Z]):\\\\" -- Windows telnet + .. "|Main(?:\\s|\\x1B\\[\\d+;\\d+H)Menu\\b" -- Netgear RM356 + .. "|^Enter Terminal Emulation:\\s*$", -- Hummingbird telnetd + pcre.flags().CASELESS, "C") + return pcreptn.login_success:match(str) end @@ -119,10 +119,10 @@ end -- @param str The string to analyze -- @return Verdict (true or false) local is_login_failure = function (str) - pcreptn.login_failure = pcreptn.login_failure - or pcre.new("\\b(?:incorrect|failed|denied|invalid|bad)\\b", - pcre.flags().CASELESS, "C") - return pcreptn.login_failure:match(str) + pcreptn.login_failure = pcreptn.login_failure + or pcre.new("\\b(?:incorrect|failed|denied|invalid|bad)\\b", + pcre.flags().CASELESS, "C") + return pcreptn.login_failure:match(str) end @@ -138,21 +138,21 @@ local Connection = { methods = {} } -- @param port Telnet port -- @return Connection object or nil (if the operation failed) Connection.new = function (host, port, proto) - local soc = nmap.new_socket(proto) - if not soc then return nil end - return setmetatable( { - socket = soc, - isopen = false, - buffer = nil, - error = nil, - host = host, - port = port, - proto = proto - }, - { - __index = Connection.methods, - __gc = Connection.methods.close - } ) + local soc = nmap.new_socket(proto) + if not soc then return nil end + return setmetatable( { + socket = soc, + isopen = false, + buffer = nil, + error = nil, + host = host, + port = port, + proto = proto + }, + { + __index = Connection.methods, + __gc = Connection.methods.close + } ) end @@ -163,21 +163,21 @@ end -- @return Status (true or false) -- @return nil if the operation was successful; error code otherwise Connection.methods.connect = function (self) - local status - local wait = 1 + local status + local wait = 1 - self.buffer = "" + self.buffer = "" - for tries = 0, conn_retries do - self.socket:set_timeout(telnet_timeout) - status, self.error = self.socket:connect(self.host, self.port, self.proto) - if status then break end - stdnse.sleep(wait) - wait = 2 * wait - end + for tries = 0, conn_retries do + self.socket:set_timeout(telnet_timeout) + status, self.error = self.socket:connect(self.host, self.port, self.proto) + if status then break end + stdnse.sleep(wait) + wait = 2 * wait + end - self.isopen = status - return status, self.error + self.isopen = status + return status, self.error end @@ -188,12 +188,12 @@ end -- @return Status (true or false) -- @return nil if the operation was successful; error code otherwise Connection.methods.close = function (self) - if not self.isopen then return true, nil end - local status - self.isopen = false - self.buffer = nil - status, self.error = self.socket:close() - return status, self.error + if not self.isopen then return true, nil end + local status + self.isopen = false + self.buffer = nil + status, self.error = self.socket:close() + return status, self.error end @@ -205,9 +205,9 @@ end -- @return Status (true or false) -- @return nil if the operation was successful; error code otherwise Connection.methods.send_line = function (self, line) - local status - status, self.error = self.socket:send(line .. telnet_eol) - return status, self.error + local status + status, self.error = self.socket:send(line .. telnet_eol) + return status, self.error end @@ -219,40 +219,40 @@ end -- @param data Data string to add to the buffer -- @return Number of characters in the connection buffer Connection.methods.fill_buffer = function (self, data) - local outbuf = strbuf.new(self.buffer) - local optbuf = strbuf.new() - local oldpos = 0 + local outbuf = strbuf.new(self.buffer) + local optbuf = strbuf.new() + local oldpos = 0 - while true do - -- look for IAC (Interpret As Command) - local newpos = data:find('\255', oldpos) - if not newpos then break end + while true do + -- look for IAC (Interpret As Command) + local newpos = data:find('\255', oldpos) + if not newpos then break end - outbuf = outbuf .. data:sub(oldpos, newpos - 1) - local opttype = data:byte(newpos + 1) - local opt = data:byte(newpos + 2) + outbuf = outbuf .. data:sub(oldpos, newpos - 1) + local opttype = data:byte(newpos + 1) + local opt = data:byte(newpos + 2) - if opttype == 251 or opttype == 252 then - -- Telnet Will / Will Not - -- regarding ECHO or GO-AHEAD, agree with whatever the - -- server wants (or not) to do; otherwise respond with - -- "don't" - opttype = (opt == 1 or opt == 3) and opttype + 2 or 254 - elseif opttype == 253 or opttype == 254 then - -- Telnet Do / Do not - -- I will not do whatever the server wants me to - opttype = 252 - end + if opttype == 251 or opttype == 252 then + -- Telnet Will / Will Not + -- regarding ECHO or GO-AHEAD, agree with whatever the + -- server wants (or not) to do; otherwise respond with + -- "don't" + opttype = (opt == 1 or opt == 3) and opttype + 2 or 254 + elseif opttype == 253 or opttype == 254 then + -- Telnet Do / Do not + -- I will not do whatever the server wants me to + opttype = 252 + end - optbuf = optbuf .. string.char(255) - .. string.char(opttype) - .. string.char(opt) - oldpos = newpos + 3 - end + optbuf = optbuf .. string.char(255) + .. string.char(opttype) + .. string.char(opt) + oldpos = newpos + 3 + end - self.buffer = strbuf.dump(outbuf) .. data:sub(oldpos) - self.socket:send(strbuf.dump(optbuf)) - return self.buffer:len() + self.buffer = strbuf.dump(outbuf) .. data:sub(oldpos) + self.socket:send(strbuf.dump(optbuf)) + return self.buffer:len() end @@ -264,18 +264,18 @@ end -- @param normalize whether the returned line is normalized (default: false) -- @return String representing the first line in the buffer Connection.methods.get_line = function (self) - if self.buffer:len() == 0 then - -- refill the buffer - local status, data = self.socket:receive_buf("[\r\n:>%%%$#\255].*", true) - if not status then - -- connection error - self.error = data - return nil - end + if self.buffer:len() == 0 then + -- refill the buffer + local status, data = self.socket:receive_buf("[\r\n:>%%%$#\255].*", true) + if not status then + -- connection error + self.error = data + return nil + end - self:fill_buffer(data) - end - return self.buffer:match('^[^\r\n]*') + self:fill_buffer(data) + end + return self.buffer:match('^[^\r\n]*') end @@ -286,8 +286,8 @@ end -- @param self Connection object -- @return Number of characters remaining in the connection buffer Connection.methods.discard_line = function (self) - self.buffer = self.buffer:gsub('^[^\r\n]*[\r\n]*', '', 1) - return self.buffer:len() + self.buffer = self.buffer:gsub('^[^\r\n]*[\r\n]*', '', 1) + return self.buffer:len() end @@ -309,16 +309,16 @@ local Target = { methods = {} } -- @param port Telnet port -- @return Target object or nil (if the operation failed) Target.new = function (host, port) - local soc, _, proto = comm.tryssl(host, port, "\n", {timeout=telnet_timeout}) - if not soc then return nil end - soc:close() - return setmetatable({ - host = host, - port = port, - proto = proto, - workers = setmetatable({}, { __mode = "k" }) - }, - { __index = Target.methods } ) + local soc, _, proto = comm.tryssl(host, port, "\n", {timeout=telnet_timeout}) + if not soc then return nil end + soc:close() + return setmetatable({ + host = host, + port = port, + proto = proto, + workers = setmetatable({}, { __mode = "k" }) + }, + { __index = Target.methods } ) end @@ -327,8 +327,8 @@ end -- -- @param self Target object Target.methods.worker = function (self) - local thread = coroutine.running() - self.workers[thread] = self.workers[thread] or {} + local thread = coroutine.running() + self.workers[thread] = self.workers[thread] or {} end @@ -340,19 +340,19 @@ end -- @return Status (true or false) -- @return Connection if the operation was successful; error code otherwise Target.methods.attach = function (self) - local worker = self.workers[coroutine.running()] - local conn = worker.conn - or Connection.new(self.host, self.port, self.proto) - if not conn then return false, "Unable to allocate connection" end - worker.conn = conn + local worker = self.workers[coroutine.running()] + local conn = worker.conn + or Connection.new(self.host, self.port, self.proto) + if not conn then return false, "Unable to allocate connection" end + worker.conn = conn - if conn.error then conn:close() end - if not conn.isopen then - local status, err = conn:connect() - if not status then return false, err end - end + if conn.error then conn:close() end + if not conn.isopen then + local status, err = conn:connect() + if not status then return false, err end + end - return true, conn + return true, conn end @@ -363,10 +363,10 @@ end -- @return Status (true or false) -- @return nil if the operation was successful; error code otherwise Target.methods.detach = function (self) - local conn = self.workers[coroutine.running()].conn - local status, response = true, nil - if conn and conn.error then status, response = conn:close() end - return status, response + local conn = self.workers[coroutine.running()].conn + local status, response = true, nil + if conn and conn.error then status, response = conn:close() end + return status, response end @@ -377,8 +377,8 @@ end -- @param inuse Whether the worker is in use (true or false) -- @return inuse Target.methods.inuse = function (self, inuse) - self.workers[coroutine.running()].inuse = inuse - return inuse + self.workers[coroutine.running()].inuse = inuse + return inuse end @@ -388,11 +388,11 @@ end -- @param self Target object -- @return Verdict (true or false) Target.methods.idle = function (self) - local idle = true - for t, w in pairs(self.workers) do - idle = idle and (not w.inuse or coroutine.status(t) == "dead") - end - return idle + local idle = true + for t, w in pairs(self.workers) do + idle = idle and (not w.inuse or coroutine.status(t) == "dead") + end + return idle end @@ -409,16 +409,16 @@ local Driver = { methods = {} } -- @param target instance of a Target class -- @return Driver object or nil (if the operation failed) Driver.new = function (self, host, port, target) - assert(host == target.host and port == target.port, "Target mismatch") - target:worker() - return setmetatable({ - target = target, - connect = telnet_autosize - and Driver.methods.connect_autosize - or Driver.methods.connect_simple, - thread_exit = nmap.condvar(target) - }, - { __index = Driver.methods } ) + assert(host == target.host and port == target.port, "Target mismatch") + target:worker() + return setmetatable({ + target = target, + connect = telnet_autosize + and Driver.methods.connect_autosize + or Driver.methods.connect_simple, + thread_exit = nmap.condvar(target) + }, + { __index = Driver.methods } ) end @@ -429,13 +429,13 @@ end -- @return Status (true or false) -- @return nil if the operation was successful; error code otherwise Driver.methods.connect_simple = function (self) - assert(not self.conn, "Multiple connections attempted") - local status, response = self.target:attach() - if status then - self.conn = response - response = nil - end - return status, response + assert(not self.conn, "Multiple connections attempted") + local status, response = self.target:attach() + if status then + self.conn = response + response = nil + end + return status, response end @@ -446,26 +446,26 @@ end -- @return Status (true or false) -- @return nil if the operation was successful; error code otherwise Driver.methods.connect_autosize = function (self) - assert(not self.conn, "Multiple connections attempted") - self.target:inuse(true) - local status, response = self.target:attach() - if status then - -- connected to the target - self.conn = response - if self:prompt() then - -- successfully reached login prompt - return true, nil - end - -- connected but turned away - self.target:detach() - end - -- let's park the thread here till all the functioning threads finish - self.target:inuse(false) - print_debug(detail_debug, "Retiring %s", tostring(coroutine.running())) - while not self.target:idle() do self.thread_exit("wait") end - -- pretend that it connected - self.conn = Connection.GHOST - return true, nil + assert(not self.conn, "Multiple connections attempted") + self.target:inuse(true) + local status, response = self.target:attach() + if status then + -- connected to the target + self.conn = response + if self:prompt() then + -- successfully reached login prompt + return true, nil + end + -- connected but turned away + self.target:detach() + end + -- let's park the thread here till all the functioning threads finish + self.target:inuse(false) + print_debug(detail_debug, "Retiring %s", tostring(coroutine.running())) + while not self.target:idle() do self.thread_exit("wait") end + -- pretend that it connected + self.conn = Connection.GHOST + return true, nil end @@ -476,13 +476,13 @@ end -- @return Status (true or false) -- @return nil if the operation was successful; error code otherwise Driver.methods.disconnect = function (self) - assert(self.conn, "Attempt to disconnect non-existing connection") - if self.conn.isopen and not self.conn.error then - -- try to reach new login prompt - self:prompt() - end - self.conn = nil - return self.target:detach() + assert(self.conn, "Attempt to disconnect non-existing connection") + if self.conn.isopen and not self.conn.error then + -- try to reach new login prompt + self:prompt() + end + self.conn = nil + return self.target:detach() end @@ -492,16 +492,16 @@ end -- @param self Driver object -- @return line Reached prompt or nil Driver.methods.prompt = function (self) - assert(self.conn, "Attempt to use disconnected driver") - local conn = self.conn - local line - repeat - line = conn:get_line() - until not line - or is_username_prompt(line) - or is_password_prompt(line) - or not conn:discard_line() - return line + assert(self.conn, "Attempt to use disconnected driver") + local conn = self.conn + local line + repeat + line = conn:get_line() + until not line + or is_username_prompt(line) + or is_password_prompt(line) + or not conn:discard_line() + return line end @@ -513,181 +513,181 @@ end -- @return instance of brute.Account if the operation was successful; -- instance of brute.Error otherwise Driver.methods.login = function (self, username, password) - assert(self.conn, "Attempt to use disconnected driver") - local sent_username = self.target.passonly - local sent_password = false - local conn = self.conn + assert(self.conn, "Attempt to use disconnected driver") + local sent_username = self.target.passonly + local sent_password = false + local conn = self.conn - local loc = " in " .. tostring(coroutine.running()) + local loc = " in " .. tostring(coroutine.running()) - local connection_error = function (msg) - print_debug(detail_debug, msg .. loc) - local err = brute.Error:new(msg) - err:setRetry(true) - return false, err - end + local connection_error = function (msg) + print_debug(detail_debug, msg .. loc) + local err = brute.Error:new(msg) + err:setRetry(true) + return false, err + end - local passonly_error = function () - local msg = "Password prompt encountered" - print_debug(critical_debug, msg .. loc) - local err = brute.Error:new(msg) - err:setAbort(true) - return false, err - end + local passonly_error = function () + local msg = "Password prompt encountered" + print_debug(critical_debug, msg .. loc) + local err = brute.Error:new(msg) + err:setAbort(true) + return false, err + end - local username_error = function () - local msg = "Invalid username encountered" - print_debug(detail_debug, msg .. loc) - local err = brute.Error:new(msg) - err:setInvalidAccount(username) - return false, err - end + local username_error = function () + local msg = "Invalid username encountered" + print_debug(detail_debug, msg .. loc) + local err = brute.Error:new(msg) + err:setInvalidAccount(username) + return false, err + end - local login_error = function () - local msg = "Login failed" - print_debug(detail_debug, msg .. loc) - return false, brute.Error:new(msg) - end + local login_error = function () + local msg = "Login failed" + print_debug(detail_debug, msg .. loc) + return false, brute.Error:new(msg) + end - local login_success = function () - local msg = "Login succeeded" - print_debug(detail_debug, msg .. loc) - return true, brute.Account:new(username, password, "OPEN") - end + local login_success = function () + local msg = "Login succeeded" + print_debug(detail_debug, msg .. loc) + return true, brute.Account:new(username, password, "OPEN") + end - local login_no_password = function () - local msg = "Login succeeded without password" - print_debug(detail_debug, msg .. loc) - return true, brute.Account:new(username, "", "OPEN") - end + local login_no_password = function () + local msg = "Login succeeded without password" + print_debug(detail_debug, msg .. loc) + return true, brute.Account:new(username, "", "OPEN") + 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 - -- reached when auto-sizing is enabled and all worker threads - -- failed - return connection_error("Service unreachable") - end + if conn == Connection.GHOST then + -- reached when auto-sizing is enabled and all worker threads + -- failed + return connection_error("Service unreachable") + end - -- username has not yet been sent - while not sent_username do - local line = conn:get_line() - if not line then - -- stopped receiving data - return connection_error("Login prompt not reached") - end + -- username has not yet been sent + while not sent_username do + local line = conn:get_line() + if not line then + -- stopped receiving data + return connection_error("Login prompt not reached") + end - if is_username_prompt(line) then - -- being prompted for a username - conn:discard_line() - print_debug(detail_debug, "Sending username" .. loc) - if not conn:send_line(username) then - return connection_error(conn.error) - end - sent_username = true - if conn:get_line() == username then - -- ignore; remote echo of the username in effect - conn:discard_line() - end + if is_username_prompt(line) then + -- being prompted for a username + conn:discard_line() + print_debug(detail_debug, "Sending username" .. loc) + if not conn:send_line(username) then + return connection_error(conn.error) + end + sent_username = true + if conn:get_line() == username then + -- ignore; remote echo of the username in effect + conn:discard_line() + end - elseif is_password_prompt(line) then - -- looks like 'password only' support - return passonly_error() + elseif is_password_prompt(line) then + -- looks like 'password only' support + return passonly_error() - else - -- ignore; insignificant response line - conn:discard_line() - end - end + else + -- ignore; insignificant response line + conn:discard_line() + end + end - -- username has been already sent - while not sent_password do - local line = conn:get_line() - if not line then - -- remote host disconnected - return connection_error("Password prompt not reached") - end + -- username has been already sent + while not sent_password do + local line = conn:get_line() + if not line then + -- remote host disconnected + return connection_error("Password prompt not reached") + end - if is_login_success(line) then - -- successful login without a password - conn:close() - return login_no_password() + if is_login_success(line) then + -- successful login without a password + conn:close() + return login_no_password() - elseif is_password_prompt(line) then - -- being prompted for a password - conn:discard_line() - print_debug(detail_debug, "Sending password" .. loc) - if not conn:send_line(password) then - return connection_error(conn.error) - end - sent_password = true + elseif is_password_prompt(line) then + -- being prompted for a password + conn:discard_line() + print_debug(detail_debug, "Sending password" .. loc) + if not conn:send_line(password) then + return connection_error(conn.error) + end + sent_password = true - elseif is_login_failure(line) then - -- failed login without a password; explicitly told so - conn:discard_line() - return username_error() + elseif is_login_failure(line) then + -- failed login without a password; explicitly told so + conn:discard_line() + return username_error() - elseif is_username_prompt(line) then - -- failed login without a password; prompted again for a username - return username_error() + elseif is_username_prompt(line) then + -- failed login without a password; prompted again for a username + return username_error() - else - -- ignore; insignificant response line - conn:discard_line() - end + else + -- ignore; insignificant response line + conn:discard_line() + end - end + end - -- password has been already sent - while true do - local line = conn:get_line() - if not line then - -- remote host disconnected - return connection_error("Login not completed") - end + -- password has been already sent + while true do + local line = conn:get_line() + if not line then + -- remote host disconnected + return connection_error("Login not completed") + end - if is_login_success(line) then - -- successful login - conn:close() - return login_success() + if is_login_success(line) then + -- successful login + conn:close() + return login_success() - elseif is_login_failure(line) then - -- failed login; explicitly told so - conn:discard_line() - return login_error() + elseif is_login_failure(line) then + -- failed login; explicitly told so + conn:discard_line() + return login_error() - elseif is_password_prompt(line) or is_username_prompt(line) then - -- failed login; prompted again for credentials - return login_error() + elseif is_password_prompt(line) or is_username_prompt(line) then + -- failed login; prompted again for credentials + return login_error() - else - -- ignore; insignificant response line - conn:discard_line() - end + else + -- ignore; insignificant response line + conn:discard_line() + end - end + end - -- unreachable code - assert(false, "Reached unreachable code") + -- unreachable code + assert(false, "Reached unreachable code") end action = function (host, port) - local ts, tserror = stdnse.parse_timespec(arg_timeout) - if not ts then - return stdnse.format_output(false, "Invalid timeout value: " .. tserror) - end - telnet_timeout = 1000 * ts - telnet_autosize = arg_autosize:lower() == "true" + local ts, tserror = stdnse.parse_timespec(arg_timeout) + if not ts then + return stdnse.format_output(false, "Invalid timeout value: " .. tserror) + end + telnet_timeout = 1000 * ts + telnet_autosize = arg_autosize:lower() == "true" - local target = Target.new(host, port) - if not target then - return stdnse.format_output(false, "Unable to connect to the target") - end + local target = Target.new(host, port) + if not target then + return stdnse.format_output(false, "Unable to connect to the target") + end - local engine = brute.Engine:new(Driver, host, port, target) - engine.options.script_name = SCRIPT_NAME - target.passonly = engine.options.passonly - local _, result = engine:start() - return result + local engine = brute.Engine:new(Driver, host, port, target) + engine.options.script_name = SCRIPT_NAME + target.passonly = engine.options.passonly + local _, result = engine:start() + return result end