diff --git a/CHANGELOG b/CHANGELOG index 4e41ab119..b23809f8d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added omp2-brute and omp2-enum-targets, which respectively get + authentication credentials and then a list of scanning targets from + the OpenVAS Management Protocol. [Henri Doreau] + o [NSE] Added backorifice-info from Gorjan Petrovski, which retrieves lots of system information from a BackOrifice server. diff --git a/nselib/omp2.lua b/nselib/omp2.lua new file mode 100644 index 000000000..cb6151c2d --- /dev/null +++ b/nselib/omp2.lua @@ -0,0 +1,179 @@ +--- +-- This library was written to ease interaction with OpenVAS Manager servers +-- using OMP (OpenVAS Management Protocol) version 2. +-- +-- A very small subset of the protocol is implemented. +-- * Connection/authentication +-- * Targets enumeration +-- +-- The library can also store accounts in the registry to share them between +-- scripts. +-- +-- The complete protocol documentation is available on the official OpenVAS +-- website: http://www.openvas.org/omp-2-0.html +-- +-- Sample use: +-- +-- local session = omp2.Session:new() +-- local status, err = session:connect(host, port) +-- local status, err = session:authenticate(username, password) +-- ... +-- session:close() +-- +-- +-- @author Henri Doreau +-- @copyright Same as Nmap -- See http://nmap.org/book/man-legal.html +-- +-- @args omp2.username The username to use for authentication. +-- @args omp2.password The password to use for authentication. +-- + +module(... or "omp2", package.seeall) + +require("stdnse") +require("nmap") + +local HAVE_SSL = false + +if pcall(require,'openssl') then + HAVE_SSL = true +end + +--- A Session class holds connection and interaction with the server +Session = { + + --- Creates a new session object + new = function(self, o) + + o = o or {} + setmetatable(o, self) + self.__index = self + + o.username = nmap.registry.args["omp2.username"] + o.password = nmap.registry.args["omp2.password"] + o.socket = nmap.new_socket() + + return o + end, + + --- Establishes the (SSL) connection to the remote server + connect = function(self, host, port) + if not HAVE_SSL then + return false, "The OMP2 module requires OpenSSL support" + end + + return self.socket:connect(host, port, "ssl") + end, + + --- Closes connection + close = function(self) + return self.socket:close() + end, + + --- Attempts to authenticate on the current connection + authenticate = function(self, username, password) + local status, err, xmldata + + -- TODO escape credentials + status, err = self.socket:send("" + .. "" .. username .. "" + .. "" .. password .. "" + .. "") + + if not status then + stdnse.print_debug("ERROR: %s", err) + return false, err + end + + status, xmldata = self.socket:receive() + if not status then + stdnse.print_debug("ERROR: %s", xmldata) + return false, xmldata + end + + return xmldata:match('status="200"') + end, + + --- Lists targets defined on the remote server + ls_targets = function(self) + local status, err, xmldata + local res, target_names, target_hosts = {}, {}, {} + + status, err = self.socket:send("") + + if not status then + stdnse.print_debug("ERROR: %s", err) + return false, err + end + + status, xmldata = self.socket:receive() + if not status then + stdnse.print_debug("ERROR: %s", xmldata) + return false, xmldata + end + + -- As NSE has no XML parser yet, we use regexp to extract the data from the + -- XML output. Targets are defined as a name and the corresponding host(s). + -- Thus we gather both and return an associative array, using names as keys + -- and hosts as values. + + local i = 0 + for name in xmldata:gmatch("(.-)") do + -- XXX this is hackish: skip the second and third "" tags, as they + -- describe other components than the targets. + -- see: http://www.openvas.org/omp-2-0.html#command_get_targets + if i % 3 == 0 then + table.insert(target_names, name) + end + i = i + 1 + end + + for hosts in xmldata:gmatch("(.-)") do + table.insert(target_hosts, hosts) + end + + for i, _ in ipairs(target_names) do + res[target_names[i]] = target_hosts[i] + end + + return res + end, +} + +--- Registers OMP2 credentials for a given host +function add_account(host, username, password) + if not nmap.registry[host.ip] then + nmap.registry[host.ip] = {} + end + + if not nmap.registry[host.ip]["omp2accounts"] then + nmap.registry[host.ip]["omp2accounts"] = {} + end + + table.insert(nmap.registry[host.ip]["omp2accounts"], {["username"] = username, ["password"] = password}) +end + +--- Retrieves the list of accounts for a given host +function get_accounts(host) + local accounts = {} + local username, password + + username = nmap.registry.args["omp2.username"] + password = nmap.registry.args["omp2.password"] + + if username and password then + table.insert(accounts, {["username"] = username, ["password"] = password}) + end + + if nmap.registry[host.ip] and nmap.registry[host.ip]["omp2accounts"] then + for _, account in pairs(nmap.registry[host.ip]["omp2accounts"]) do + table.insert(accounts, account) + end + end + + if #accounts > 0 then + return accounts + end + return nil +end + diff --git a/scripts/omp2-brute.nse b/scripts/omp2-brute.nse new file mode 100644 index 000000000..72c445bd6 --- /dev/null +++ b/scripts/omp2-brute.nse @@ -0,0 +1,83 @@ +description = [[ +Performs brute force password auditing against the OpenVAS manager using OMPv2. +]] + +--- +-- @usage +-- nmap -p 9390 --script omp2-brute +-- +-- @output +-- PORT STATE SERVICE REASON +-- 9390/tcp open openvas syn-ack +-- | svn-brute: +-- | Accounts +-- |_ admin:secret => Login correct +-- + +author = "Henri Doreau" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"auth", "intrusive"} + +require("omp2") +require("nmap") +require("brute") +require("shortport") + + +portrule = shortport.port_or_service(9390, "openvas") + + +Driver = { + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.session = omp2.Session:new() + return o + end, + + --- Connects to the OpenVAS Manager + -- + -- @return status boolean for connection success/failure + -- @return err string describing the error on failure + connect = function(self) + return self.session:connect(self.host, self.port) + end, + + --- Closes connection + -- + -- @return status boolean for closing success/failure + disconnect = function(self) + return self.session:close() + end, + + --- Attempts to login the the OpenVAS Manager using a given username/password + -- couple. Store the credentials in the registry on success. + -- + -- @param username string containing the login username + -- @param password string containing the login password + -- @return status boolean for login success/failure + -- @return err string describing the error on failure + login = function(self, username, password) + if self.session:authenticate(username, password) then + -- store the account for possible future use + omp2.add_account(self.host, username, password) + return true, brute.Account:new(username, password, "OPEN") + else + return false, brute.Error:new("login failed") + end + end, + + --- Deprecated + check = function(self) + return true + end, +} + +action = function(host, port) + local status, result = brute.Engine:new(Driver, host, port):start() + return result +end + diff --git a/scripts/omp2-enum-targets.nse b/scripts/omp2-enum-targets.nse new file mode 100644 index 000000000..fd0ff4774 --- /dev/null +++ b/scripts/omp2-enum-targets.nse @@ -0,0 +1,125 @@ +description = [[ +Attempts to get the list of targets from an OpenVAS Manager server. + +The script authenticates on the manager using provided or previously cracked +credentials and gets the list of defined targets for each account. + +These targets will be added to the scanning queue in case +newtargets global variable is set. +]] + +--- +-- @usage +-- nmap -p 9390 --script omp2-brute,omp2-enum-targets +-- +-- @usage +-- nmap -p 9390 --script omp2-enum-targets --script-args omp2.username=admin,omp2.password=secret +-- +-- @output +-- PORT STATE SERVICE +-- 9390/tcp open openvas +-- | omp2-enum-targets: +-- | +-- | Targets for account admin: +-- | TARGET HOSTS +-- | Sales network 192.168.20.0/24 +-- | Production network 192.168.30.0/24 +-- |_ Firewall 192.168.1.254 +-- + + +author = "Henri Doreau" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} +dependencies = {"omp2-brute"} + + +require("tab") +require("omp2") +require("target") +require("stdnse") +require("shortport") + + +portrule = shortport.port_or_service(9390, "openvas") + + +--- Return the list of targets defined for a given user +-- +-- @param host the target host table +-- @param port the targetted OMP port +-- @param username the username to use to login +-- @param password the password to use to login +-- @return the list of targets for this user or nil +local function account_enum_targets(host, port, username, password) + local targets + local session = omp2.Session:new() + + local status, err = session:connect(host, port) + + if not status then + stdnse.print_debug("%s: connection failure (%s)", SCRIPT_NAME, err) + return nil + end + + if session:authenticate(username, password) then + targets = session:ls_targets() + else + stdnse.print_debug("%s: authentication failure (%s:%s)", SCRIPT_NAME, username, password) + end + + session:close() + + return targets +end + +--- Generate the output string representing the list of discovered targets +-- +-- @param targets the list of targets as a name->hosts mapping +-- @return the array as a formatted string +local function report(targets) + local outtab = tab.new() + + tab.add(outtab, 1, "TARGET") + tab.add(outtab, 2, "HOSTS") + tab.nextrow(outtab) + + for name, hosts in pairs(targets) do + tab.addrow(outtab, name, hosts) + end + + return tab.dump(outtab) +end + +action = function(host, port) + local results = {} + local credentials = omp2.get_accounts(host) + + if not credentials then + -- unable to authenticate on the server + return "No valid account available!" + end + + for _, account in pairs(credentials) do + + local username, password = account.username, account.password + + local targets = account_enum_targets(host, port, username, password) + + if targets ~= nil then + table.insert(results, "\nTargets for account " .. username .. ":") + table.insert(results, report(targets)) + else + table.insert(results, "\nNo targets found for account " .. username) + end + + if target.ALLOW_NEW_TARGETS and targets ~= nil then + stdnse.print_debug("%s: adding new targets %s", SCRIPT_NAME, stdnse.strjoin(", ", targets)) + target.add(unpack(targets)) + end + + end + + return stdnse.format_output(true, results) +end + diff --git a/scripts/script.db b/scripts/script.db index 6deaae446..970221995 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -119,6 +119,8 @@ Entry { filename = "nping-brute.nse", categories = { "auth", "intrusive", } } Entry { filename = "nrpe-enum.nse", categories = { "discovery", "intrusive", } } Entry { filename = "ntp-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "ntp-monlist.nse", categories = { "discovery", "intrusive", } } +Entry { filename = "omp2-brute.nse", categories = { "auth", "intrusive", } } +Entry { filename = "omp2-enum-targets.nse", categories = { "discovery", "safe", } } Entry { filename = "oracle-brute.nse", categories = { "auth", "intrusive", } } Entry { filename = "oracle-enum-users.nse", categories = { "auth", "intrusive", } } Entry { filename = "oracle-sid-brute.nse", categories = { "auth", "intrusive", } }