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", } }