From 480e5ac605c8d2fc369b4cd9e730dc08f303d930 Mon Sep 17 00:00:00 2001 From: patrik Date: Fri, 2 Mar 2012 12:39:18 +0000 Subject: [PATCH] o [NSE] Added the library rpcap and the scripts rpcap-brute and rpcap-info which perform brute force password guessing and extract information from the WinPcap Remote Packet Capture daemon. [Patrik] --- CHANGELOG | 4 + nselib/rpcap.lua | 437 ++++++++++++++++++++++++++++++++++++++++ scripts/rpcap-brute.nse | 92 +++++++++ scripts/rpcap-info.nse | 90 +++++++++ scripts/script.db | 2 + 5 files changed, 625 insertions(+) create mode 100644 nselib/rpcap.lua create mode 100644 scripts/rpcap-brute.nse create mode 100644 scripts/rpcap-info.nse diff --git a/CHANGELOG b/CHANGELOG index 09bcca07a..a4af9bc03 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added the library rpcap and the scripts rpcap-brute and rpcap-info + which perform brute force password guessing and extract information from the + WinPcap Remote Packet Capture daemon. [Patrik] + o [NSE] Added authentication support to MongoDB library and modified existing scripts to support it. Added the script mongodb-brute to perform password brute force guessing. [Patrik] diff --git a/nselib/rpcap.lua b/nselib/rpcap.lua new file mode 100644 index 000000000..2b967ede5 --- /dev/null +++ b/nselib/rpcap.lua @@ -0,0 +1,437 @@ +--- +-- This library implements the fundamentals needed to communicate with the +-- WinPcap Remote Capture Deamon. It currently supports authenticating to +-- the service using either NULL-, or Password-based authentication. +-- In addition it has the capabilities to list the interfaces that may be +-- used for sniffing. +-- +-- The library consist of classes handling Request and classes +-- handling Response. The communication with the service is +-- handled by the Comm class, and the main interface for script +-- writers is kept under the Helper class. +-- +-- The following code snipplet illustrates how to connect to the service and +-- extract information about network interfaces: +-- +-- local helper = rpcap.Helper:new(host, port) +-- helper:connect() +-- helper:login() +-- helper:findAllInterfaces() +-- helper:close() +-- +-- +-- For a more complete example, consult the rpcap-info.nse script. +-- +-- @author "Patrik Karlsson " + + +module(... or "rpcap", package.seeall) + +require 'ipops' +require 'match' + + +RPCAP = { + + MessageType = { + ERROR = 1, + FIND_ALL_INTERFACES = 2, + AUTH_REQUEST = 8, + }, + + -- Holds the two supported authentication mechanisms PWD and NULL + Authentication = { + + PWD = { + + new = function(self, username, password) + local o = { + type = 1, + username = username, + password = password, + } + setmetatable(o, self) + self.__index = self + return o + end, + + __tostring = function(self) + local DUMMY = 0 + return bin.pack(">SSSSAA", self.type, DUMMY, #self.username, #self.password, self.username, self.password) + end, + + }, + + NULL = { + + new = function(self) + local o = { + type = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, + + __tostring = function(self) + local DUMMY = 0 + return bin.pack(">SSSS", self.type, DUMMY, 0, 0) + end, + + } + + }, + + -- The common request and response header + Header = { + size = 8, + new = function(self, type, value, length) + local o = { + version = 0, + type = type, + value= value or 0, + length = length or 0 + } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local header = RPCAP.Header:new() + local pos + pos, header.version, header.type, header.value, header.length = bin.unpack(">CCSI", data) + return header + end, + + __tostring = function(self) + return bin.pack(">CCSI", self.version, self.type, self.value, self.length) + end, + + }, + + -- The implemented request types are kept here + Request = { + + Authentication = { + + new = function(self, data) + local o = { + header = RPCAP.Header:new(RPCAP.MessageType.AUTH_REQUEST, nil, #data), + data = data, + } + setmetatable(o, self) + self.__index = self + return o + end, + + __tostring = function(self) + return tostring(self.header) .. tostring(self.data) + end, + + }, + + FindAllInterfaces = { + + new = function(self) + local o = { + header = RPCAP.Header:new(RPCAP.MessageType.FIND_ALL_INTERFACES) + } + setmetatable(o, self) + self.__index = self + return o + end, + + __tostring = function(self) + return tostring(self.header) + end, + + + } + + }, + + -- Parsers for responses are kept here + Response = { + + Authentication = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local resp = RPCAP.Response.Authentication:new() + local pos = RPCAP.Header.size + 1 + resp.header = RPCAP.Header.parse(data) + return resp + end + }, + + Error = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local err = RPCAP.Response.Error:new() + local pos = RPCAP.Header.size + 1 + err.header = RPCAP.Header.parse(data) + pos, err.error = bin.unpack("A" .. err.header.length, data, pos) + return err + end + + }, + + FindAllInterfaces = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + + -- Each address is made up of 4 128 byte fields, this function + -- parses these fields and return the response, if it + -- understands it. Otherwise it simply increases the pos by the + -- correct offset, to get us to the next field. + local function parseField(data, pos) + local offset = pos + local family, port + pos, family, port = bin.unpack(">SS", data, pos) + + if ( family == 0x0017 ) then + -- not sure why... + pos = pos + 4 + + local ipv6 + pos, ipv6 = bin.unpack("B16", data, pos) + return offset + 128, ipOps.bin_to_ip(ipv6) + elseif ( family == 0x0002 ) then + local ipv4 + pos, ipv4 = bin.unpack("B4", data, pos) + return offset + 128, ipOps.bin_to_ip(ipv4) + end + + return offset + 128, nil + end + + -- Parses one of X addresses returned for an interface + local function parseAddress(data, pos) + local fields = {"ip", "netmask", "bcast", "p2p"} + local addr = {} + + for _, f in ipairs(fields) do + pos, addr[f] = parseField(data, pos) + end + + return pos, addr + end + + local resp = RPCAP.Response.FindAllInterfaces:new() + local pos = RPCAP.Header.size + 1 + resp.header = RPCAP.Header.parse(data) + resp.ifaces = {} + + for i=1, resp.header.value do + local name_len, desc_len, iface_flags, addr_count, dummy + pos, name_len, desc_len, iface_flags, addr_count, dummy = bin.unpack(">SSISS", data, pos) + + local name, desc + pos, name, desc = bin.unpack("A" .. name_len .. "A" .. desc_len, data, pos) + + local addrs = {} + for j=1, addr_count do + local addr + pos, addr = parseAddress(data, pos) + local cidr + if ( addr.netmask ) then + local bits = ipOps.ip_to_bin(addr.netmask) + local ones = bits:match("^(1*)") + cidr = #ones + table.insert(addrs, ("%s/%d"):format(addr.ip,cidr)) + else + table.insert(addrs, addr.ip) + end + end + table.insert(resp.ifaces, { name = name, desc = desc, addrs = addrs }) + end + return resp + end, + } + + + } + +} + +-- Maps packet types to classes +RPCAP.TypeToClass = { + [1] = RPCAP.Response.Error, + [130] = RPCAP.Response.FindAllInterfaces, + [136] = RPCAP.Response.Authentication, +} + + +-- The communication class +Comm = { + + -- Creates a new instance of the Comm class + -- @param host table + -- @param port table + -- @return o instance of Comm + new = function(self, host, port) + local o = { host = host, port = port, socket = nmap.new_socket() } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Connects the socket to the server + connect = function(self) + return self.socket:connect(self.host, self.port) + end, + + -- Sends an instance of the request class to the server + -- @param req class instance + -- @return status true on success, false on failure + -- @return err string containing error message if status is false + send = function(self, req) + return self.socket:send(req) + end, + + -- receives a packet and attempts to parse it if it has a supported parser + -- in RPCAP.TypeToClass + -- @return status true on success, false on failure + -- @return resp instance of a Response class or + -- err string containing the error message + recv = function(self) + local status, hdr_data = self.socket:receive_buf(match.numbytes(RPCAP.Header.size), true) + if ( not(status) ) then + return status, data + end + + local header = RPCAP.Header.parse(hdr_data) + if ( not(header) ) then + return false, "rpcap: Failed to parse header" + end + + local status, data = self.socket:receive_buf(match.numbytes(header.length), true) + if ( not(status) ) then + return false, "rpcap: Failed to read packet data" + end + + if ( RPCAP.TypeToClass[header.type] ) then + local resp = RPCAP.TypeToClass[header.type].parse(hdr_data .. data) + if ( resp ) then + return true, resp + end + end + + return false, "Failed to receive response from server" + end, + + -- Sends and request and receives the response + -- @param req the instance of the Request class to send + -- @return status true on success, false on failure + -- @return resp instance of a Response class or + -- err string containing the error message + exch = function(self, req) + local status, data = self:send(tostring(req)) + if ( not(status) ) then + return status, data + end + return self:recv() + end, + + -- closes the socket + close = function(self) + return self.socket:close() + end, + +} + + +Helper = { + + -- Creates a new instance of the Helper class + -- @param host table + -- @param port table + -- @return o instance of Helper + new = function(self, host, port) + local o = { + host = host, + port = port, + comm = Comm:new(host, port) + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Connects to the server + connect = function(self) + return self.comm:connect(self.host, self.port) + end, + + -- Authenticates to the service, in case no username or password is given + -- NULL authentication is assumed. + -- @param username [optional] + -- @param password [optional] + -- @return status true on success, false on failure + -- @return err string containing error mesage on failure + login = function(self, username, password) + local auth + + if ( username and password ) then + auth = RPCAP.Authentication.PWD:new(username, password) + else + auth = RPCAP.Authentication.NULL:new() + end + + local req = RPCAP.Request.Authentication:new(tostring(auth)) + local status, resp = self.comm:exch(req) + + if ( not(status) ) then + return false, resp + end + + if ( status and resp.error ) then + return false, resp.error + end + return true + end, + + -- Requests a list of all interfaces + -- @return table containing interfaces and addresses + findAllInterfaces = function(self) + local req = RPCAP.Request.FindAllInterfaces:new() + local status, resp = self.comm:exch(req) + + if ( not(status) ) then + return false, resp + end + + local results = {} + for _, iface in ipairs(resp.ifaces) do + local entry = {} + entry.name = iface.name + table.insert(entry, iface.desc) + table.insert(entry, { name = "Addresses", iface.addrs }) + table.insert(results, entry) + end + return true, results + end, + + -- Closes the connection to the server + close = function(self) + return self.comm:close() + end, +} \ No newline at end of file diff --git a/scripts/rpcap-brute.nse b/scripts/rpcap-brute.nse new file mode 100644 index 000000000..73e7a41f1 --- /dev/null +++ b/scripts/rpcap-brute.nse @@ -0,0 +1,92 @@ +description = [[ +Performs brute force password guessing against the WinPcap Remote Capture +Daemon (rpcap). +]] + +--- +-- @usage +-- nmap -p 2002 --script rpcap-brute +-- +-- @output +-- PORT STATE SERVICE REASON +-- 2002/tcp open globe syn-ack +-- | rpcap-brute: +-- | Accounts +-- | monkey:Password1 - Valid credentials +-- | Statistics +-- |_ Performed 3540 guesses in 3 seconds, average tps: 1180 +-- +-- + +require 'brute' +require 'rpcap' +require 'shortport' + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"intrusive", "brute"} + +portrule = shortport.port_or_service(2002, "rpcap", "tcp") + +Driver = { + + new = function(self, host, port) + local o = { helper = rpcap.Helper:new(host, port) } + setmetatable(o, self) + self.__index = self + return o + end, + + connect = function(self) + return self.helper:connect() + end, + + login = function(self, username, password) + local status, resp = self.helper:login(username, password) + if ( status ) then + return true, brute.Account:new(username, password, creds.State.VALID) + end + return false, brute.Error:new( "Incorrect password" ) + end, + + disconnect = function(self) + return self.helper:close() + end, + +} + +local function validateAuth(host, port) + local helper = rpcap.Helper:new(host, port) + local status, result = helper:connect() + if ( not(status) ) then + return false, result + end + status, result = helper:login() + helper:close() + + if ( status ) then + return false, "Authentication not required" + elseif ( not(status) and + "Authentication failed; NULL autentication not permitted." == result ) then + return true + end + return status, result +end + +action = function(host, port) + + local status, result = validateAuth(host, port) + if ( not(status) ) then + return result + end + + local engine = brute.Engine:new(Driver, host, port ) + + engine.options.script_name = SCRIPT_NAME + engine.options.firstonly = true + status, result = engine:start() + + return result +end + + diff --git a/scripts/rpcap-info.nse b/scripts/rpcap-info.nse new file mode 100644 index 000000000..6766f8a77 --- /dev/null +++ b/scripts/rpcap-info.nse @@ -0,0 +1,90 @@ +description = [[ +Connect to the rpcap service, a service providing remote sniffing capabilities +through WinPcap, and retrieves interface information. The service can either be +setup to require authentication or not and also supports IP restrictions. +]] + +--- +-- @usage +-- nmap -p 2002 --script rpcap-info +-- nmap -p 2002 --script rpcap-info --script-args="creds.rpcap='administrator:foobar'" +-- +-- @output +-- PORT STATE SERVICE REASON +-- 2002/tcp open rpcap syn-ack +-- | rpcap-info: +-- | \Device\NPF_{0D5D1364-1F1F-4892-8AC3-B838258F9BB8} +-- | Intel(R) PRO/1000 MT Desktop Adapter +-- | Addresses +-- | fe80:0:0:0:aabb:ccdd:eeff:0011 +-- | 192.168.1.127/24 +-- | \Device\NPF_{D5EAD105-B0BA-4D38-ACB4-6E95512BC228} +-- | Hamachi Virtual Network Interface Driver +-- | Addresses +-- |_ fe80:0:0:0:aabb:ccdd:eeff:0022 +-- +-- @args creds.rpcap username:password to use for authentication +-- + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discover", "safe"} +dependencies = {"rpcap-brute"} + +require 'creds' +require 'rpcap' +require 'shortport' + +portrule = shortport.port_or_service(2002, "rpcap", "tcp") + +local function fail(err) return ("\n ERROR: %s"):format(err or "") end + +local function getInfo(host, port, username, password) + + local helper = rpcap.Helper:new(host, port) + local status, resp = helper:connect() + if ( not(status) ) then + return false, "Failed to connect to server" + end + status, resp = helper:login(username, password) + + if ( not(status) ) then + return false, resp + end + + status, resp = helper:findAllInterfaces() + helper:close() + if ( not(status) ) then + return false, resp + end + + port.version.name = "rpcap" + port.version.product = "WinPcap remote packet capture daemon" + nmap.set_port_version(host, port, "hardmatched") + + return true, resp +end + +action = function(host, port) + + -- patch-up the service name, so creds.rpcap will work, ugly but needed as + -- tcp 2002 is registered to the globe service in nmap-services ... + port.service = "rpcap" + + local c = creds.Credentials:new(creds.ALL_DATA, host, port) + local states = creds.State.VALID + creds.State.PARAM + local status, resp = getInfo(host, port) + + if ( status ) then + return stdnse.format_output(true, resp) + end + + for cred in c:getCredentials(states) do + status, resp = getInfo(host, port, cred.user, cred.pass) + if ( status ) then + return stdnse.format_output(true, resp) + end + end + + return fail(resp) +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index 5ea7abae5..0fb9e8c45 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -250,6 +250,8 @@ Entry { filename = "rexec-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "riak-http-info.nse", categories = { "discovery", "safe", } } Entry { filename = "rlogin-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "rmi-dumpregistry.nse", categories = { "default", "discovery", "safe", } } +Entry { filename = "rpcap-brute.nse", categories = { "brute", "intrusive", } } +Entry { filename = "rpcap-info.nse", categories = { "discover", "safe", } } Entry { filename = "rpcinfo.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "rsync-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "rsync-list-modules.nse", categories = { "discovery", "safe", } }