1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00

o [NSE] Added credential storage library (creds.lua) and modified the brute

library and scripts to make use of it. [Patrik]
This commit is contained in:
patrik
2011-06-19 17:18:29 +00:00
parent 5561f89642
commit f4bf440b14
16 changed files with 298 additions and 102 deletions

View File

@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
o [NSE] Added credential storage library (creds.lua) and modified the brute
library and scripts to make use of it. [Patrik]
o [NSE] Added a MySQL audit script and a rulebase that supports auditing a
subset of the MySQL CIS 1.0.2 Benchmark. [Patrik]

View File

@@ -164,7 +164,7 @@
-- by '/'
--
-- Version 0.6
-- Version 0.7
-- Created 06/12/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 07/13/2010 - v0.2 - added connect, disconnect methods to Driver
-- <patrik@cqure.net>
@@ -176,10 +176,12 @@
-- David's request.
-- Revised 08/30/2010 - v0.6 - added support for custom iterators and did some
-- needed cleanup.
-- Revised 06/19/2010 - v0.7 - added support for creds library
module(... or "brute", package.seeall)
require 'unpwdb'
require 'datafiles'
require 'creds'
-- Options that can be set through --script-args
Options = {
@@ -244,12 +246,9 @@ Account =
-- following <code>OPEN</code>, <code>LOCKED</code>,
-- <code>DISABLED</code>.
new = function(self, username, password, state)
local o = {}
local o = { username = username, password = password, state = state }
setmetatable(o, self)
self.__index = self
o.username = username
o.password = password
o.state = state
return o
end,
@@ -258,27 +257,12 @@ Account =
-- @return string representation of object
toString = function( self )
local creds
if ( #self.username > 0 ) then
creds = ("%s:%s"):format( self.username, #self.password > 0 and self.password or "<empty>" )
else
creds = ("%s"):format( ( self.password and #self.password > 0 ) and self.password or "<empty>" )
end
-- An account have the following states
--
-- OPEN - Login was successful
-- LOCKED - The account was locked
-- DISABLED - The account was disabled
if ( self.state == "OPEN" ) then
return ("%s => Login correct"):format( creds )
elseif ( self.state == "LOCKED" ) then
return ("%s => Account locked"):format( creds )
elseif ( self.state == "DISABLED" ) then
return ("%s => Account disabled"):format( creds )
else
return ("%s => Account has unknown state (%s)"):format( creds, self.state )
end
return ( "%s => %s"):format(creds, self.state.msg )
end,
}
@@ -290,11 +274,9 @@ Error =
retry = false,
new = function(self, msg)
local o = {}
local o = { msg = msg, done = false }
setmetatable(o, self)
self.__index = self
o.msg = msg
o.done = false
return o
end,
@@ -340,7 +322,6 @@ Error =
Engine =
{
STAT_INTERVAL = 20,
terminate_all = false,
--- Creates a new Engine instance
--
@@ -350,21 +331,23 @@ Engine =
-- @param options table containing any script specific options
-- @return o new Engine instance
new = function(self, driver, host, port, options)
local o = {}
local o = {
driver = driver,
host = host,
port = port,
driver_options = options,
terminate_all = false,
error = nil,
counter = 0,
threads = {},
tps = {},
iterators = {},
found_accounts = {},
options = Options:new(),
}
setmetatable(o, self)
self.__index = self
o.driver = driver
o.driver_options = options
o.host = host
o.port = port
o.options = Options:new()
o.found_accounts = {}
o.threads = {}
o.counter = 0
o.max_threads = tonumber(nmap.registry.args["brute.threads"]) or 10
o.iterators = {}
o.error = nil
o.tps = {}
o.max_threads = stdnse.get_script_args("brute.threads") or 10
return o
end,
@@ -509,31 +492,31 @@ Engine =
return status, response
end,
login = function(self, valid_accounts )
local condvar = nmap.condvar( valid_accounts )
login = function(self, cvar )
local condvar = nmap.condvar( cvar )
local thread_data = self.threads[coroutine.running()]
local interval_start = os.time()
while( true ) do
-- Should we terminate all threads?
if ( Engine.terminate_all or thread_data.terminate ) then break end
if ( self.terminate_all or thread_data.terminate ) then break end
local status, response = self:doAuthenticate()
if ( status ) then
-- Prevent locked accounts from appearing several times
if ( not(self.found_accounts) or self.found_accounts[response.username] == nil ) then
creds.Credentials:new( self.options.script_name, self.host, self.port ):add(response.username, response.password, response.state )
stdnse.print_debug("Discovered account: %s", response:toString())
table.insert( valid_accounts, response:toString() )
self.found_accounts[response.username] = true
-- Check if firstonly option was set, if so abort all threads
if ( self.options.firstonly ) then Engine.terminate_all = true end
if ( self.options.firstonly ) then self.terminate_all = true end
end
else
if ( response and response:isAbort() ) then
Engine.terminate_all = true
self.terminate_all = true
self.error = response:getMessage()
break
elseif( response and response:isDone() ) then
@@ -559,7 +542,7 @@ Engine =
-- if delay was speciefied, do sleep
if ( self.options.delay > 0 ) then stdnse.sleep( self.options.delay ) end
end
condvar("broadcast")
condvar "broadcast"
end,
--- Starts the brute-force
@@ -568,8 +551,11 @@ Engine =
-- @return err string containing error message on failure
start = function(self)
local result, valid_accounts, stats = {}, {}, {}
local condvar = nmap.condvar( valid_accounts )
local result, cvar, stats = {}, {}, {}
local condvar = nmap.condvar( cvar )
assert(self.options.script_name, "SCRIPT_NAME was not set in options.script_name")
assert(self.port.number and self.port.protocol and self.port.service, "Invalid port table detected")
-- Only run the check method if it exist. We should phase this out
-- in favor of a check in the action function of the script
@@ -629,16 +615,18 @@ Engine =
-- Startup all worker threads
for i=1, self.max_threads do
local co = stdnse.new_thread( self.login, self, valid_accounts )
local co = stdnse.new_thread( self.login, self, cvar )
self.threads[co] = {}
self.threads[co].running = true
end
-- wait for all threads to finnish running
while self:threadCount()>0 do condvar("wait") end
while self:threadCount()>0 do condvar "wait" end
local valid_accounts = creds.Credentials:new(self.options.script_name, self.host, self.port):getTable()
-- Did we find any accounts, if so, do formatting
if ( #valid_accounts > 0 ) then
if ( valid_accounts and #valid_accounts > 0 ) then
valid_accounts.name = "Accounts"
table.insert( result, valid_accounts )
else
@@ -661,7 +649,7 @@ Engine =
-- Did any error occure? If so add this to the result.
if ( self.error ) then
result = result .. (" \n\n ERROR: %s"):format( self.error )
result = result .. (" \n ERROR: %s"):format( self.error )
return false, result
end
return true, result

210
nselib/creds.lua Normal file
View File

@@ -0,0 +1,210 @@
--- The credential class stores found credentials in the Nmap registry
--
--
-- @author "Patrik Karlsson <patrik@cqure.net>"
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
-- Version 0.1
-- Created 2011/02/06 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
module(... or "creds", package.seeall)
require('ipOps')
-- Table containing the different account states
State = {
LOCKED = { msg = 'Account is locked' },
VALID = { msg = 'Account is valid' },
DISABLED = { msg = 'Account is disabled' },
CHANGEPW = { msg = 'Password needs to be changed at next logon' },
}
ALL_DATA = "all_script_data"
-- The RegStorage class
RegStorage = {
--- Creates a new RegStorage instance
--
-- @return a new instance
new = function(self)
local o = {}
setmetatable(o, self)
self.__index = self
o.filter = {}
return o
end,
--- Add credentials to storage
--
-- @param scriptname the name of the script adding the credentials
-- @param host host table, name or ip
-- @param port number containing the port of the service
-- @param service the name of the service
-- @param user the name of the user
-- @param pass the password of the user
-- @param state of the account
add = function( self, scriptname, host, port, service, user, pass, state )
local cred = {
scriptname = scriptname,
host = host,
port = port,
service = service,
user = user,
pass = pass,
state = state
}
nmap.registry.creds = nmap.registry.creds or {}
table.insert( nmap.registry.creds, cred )
end,
--- Sets the storage filter
--
-- @param host table containing the host
-- @param port table containign the port
setFilter = function( self, host, port )
self.filter.host = host
self.filter.port = port
end,
--- Retrieves the table containing all credential records
--
-- @return table containing all credential records
getAll = function( self )
local tbl = nmap.registry.creds
local new_tbl = {}
local host, port = self.filter.host, self.filter.port
if ( not(tbl) ) then return end
for _, v in pairs(tbl) do
local h = ( v.host.ip or v.host )
if ( not(host) and not(port) ) then
table.insert(new_tbl, v)
elseif ( not(host) and ( port == v.port ) ) then
table.insert(new_tbl, v)
elseif ( ( host and ( h == host or h == host.ip ) ) and not(port) ) then
table.insert(new_tbl, v)
elseif ( ( host and ( h == host or h == host.ip ) ) and port.number == v.port ) then
table.insert(new_tbl, v)
end
end
return new_tbl
end,
}
-- The credentials class
Credentials = {
--- Creates a new instance of the Credentials class
-- @param scriptname string containing the name of the script
-- @param host table as received by the scripts action method
-- @param port table as received by the scripts action method
new = function(self, scriptname, host, port)
local o = {}
setmetatable(o, self)
self.__index = self
o.storage = RegStorage:new()
o.storage:setFilter(host, port)
o.host = host
o.port = ( port and port.number ) and port.number
o.service = ( port and port.service ) and port.service
o.scriptname = scriptname
return o
end,
--- Add a discovered credential
--
-- @param host host table, name or ip
-- @param port number containing the port of the service
-- @param service the name of the service
-- @param user the name of the user
-- @param pass the password of the user
-- @param state of the account
add = function( self, user, pass, state )
local pass = ( pass and #pass > 0 ) and pass or "<empty>"
assert( self.host, "No host supplied" )
assert( self.port, "No port supplied" )
assert( state, "No state supplied")
assert( self.scriptname, "No scriptname supplied")
-- there are cases where we will only get a user or password
-- so as long we have one of them, we're good
if ( user or pass ) then
self.storage:add( self.scriptname, self.host, self.port, self.service, user, pass, state )
end
end,
--- Returns a table of credentials
--
-- @return tbl table containing the discovered credentials
getTable = function(self)
local result = {}
local all = self.storage:getAll()
if ( not(all) ) then return end
for _, v in pairs(self.storage:getAll()) do
local h = ( v.host.ip or v.host )
local svc = ("%s/%s"):format(v.port,v.service)
local c
if ( v.user and #v.user > 0 ) then
c = ("%s:%s - %s"):format(v.user, v.pass, v.state.msg)
else
c = ("%s - %s"):format(v.pass, v.state.msg)
end
local script = v.scriptname
assert(type(h)=="string", "Could not determine a valid host")
if ( script == self.scriptname or self.scriptname == ALL_DATA ) then
result[h] = result[h] or {}
result[h][svc] = result[h][svc] or {}
table.insert( result[h][svc], c )
end
end
local output = {}
for hostname, host in pairs(result) do
local host_tbl = { name = hostname }
for svcname, service in pairs(host) do
local svc_tbl = { name = svcname }
for _, account in ipairs(service) do
table.insert(svc_tbl, account)
end
-- sort the accounts
table.sort( svc_tbl, function(a,b) return a<b end)
table.insert( host_tbl, svc_tbl )
end
-- sort the services
table.sort( host_tbl,
function(a,b)
return tonumber(a.name:match("^(%d+)")) < tonumber(b.name:match("^(%d+)"))
end
)
table.insert( output, host_tbl )
end
-- sort the IP addresses
table.sort( output, function(a, b) return ipOps.compare_ip(a.name, "le", b.name) end )
if ( self.host and self.port and #output > 0 ) then
output = output[1][1]
output.name = nil
elseif ( self.host and #output > 0 ) then
output = output[1]
output.name = nil
end
return output
end,
--- Get credentials with optional host and port filter
-- If no filters are supplied all records are returned
--
-- @param host table or string containing the host to filter
-- @param port number containing the port to filter
-- @return table suitable from <code>stdnse.format_output</code>
__tostring = function(self)
local all = self:getTable()
if ( all ) then return stdnse.format_output(true, all) end
end,
}

View File

@@ -262,7 +262,7 @@ local Driver =
nmap.registry.credentials['backorifice'] = {}
end
table.insert( nmap.registry.credentials.backorifice, { password = password } )
return true, brute.Account:new("", password, "OPEN")
return true, brute.Account:new("", password, creds.State.VALID)
else
-- The only indication that the password is incorrect is a timeout
local err = brute.Error:new( "Incorrect password" )
@@ -271,9 +271,6 @@ local Driver =
end
end,
check = function( self )
return true
end
}
action = function( host, port )
@@ -283,6 +280,7 @@ action = function( host, port )
engine.options.firstonly = true
engine.options.passonly = true
engine.options.script_name = SCRIPT_NAME
status, result = engine:start()

View File

@@ -136,7 +136,7 @@ Driver =
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")
return true, brute.Account:new( username, password, creds.State.VALID)
end
return false, brute.Error:new( "Incorrect password" )
@@ -147,10 +147,6 @@ Driver =
self.sockpool:releaseSocket( self.socket )
end,
check = function( self )
return true
end,
}
@@ -159,6 +155,7 @@ action = function(host, port)
local pool = SocketPool:new(10)
local engine = brute.Engine:new(Driver, host, port, pool )
engine.options.script_name = SCRIPT_NAME
status, result = engine:start()
pool:shutdown()

View File

@@ -55,8 +55,8 @@ Driver = {
"Client-DPAP-Version: 1.1\r\n" ..
"\r\n\r\n"
local creds = base64.enc("nmap:" .. password)
data = data:format( self.host.ip, self.port.number, self.host.ip, creds )
local c = base64.enc("nmap:" .. password)
data = data:format( self.host.ip, self.port.number, self.host.ip, c )
local status = self.socket:send( data )
if ( not(status) ) then
@@ -73,7 +73,7 @@ Driver = {
end
if ( data:match("^HTTP/1.1 200 OK") ) then
return true, brute.Account:new(username, password, "OPEN")
return true, brute.Account:new(username, password, creds.State.VALID)
end
return false, brute.Error:new( "Incorrect password" )
@@ -111,6 +111,7 @@ action = function(host, port)
engine.options.firstonly = true
engine.options:setOption( "passonly", true )
engine.options.script_name = SCRIPT_NAME
status, result = engine:start()

View File

@@ -79,7 +79,7 @@ Driver = {
nmap.registry.credentials['http'] = {}
end
table.insert( nmap.registry.credentials.http, { username = username, password = password } )
return true, brute.Account:new( username, password, "OPEN")
return true, brute.Account:new( username, password, creds.State.VALID)
end
return false, brute.Error:new( "Incorrect password" )
end,
@@ -105,6 +105,7 @@ action = function( host, port )
local path = nmap.registry.args['http-brute.path']
local method = string.upper(nmap.registry.args['http-brute.method'] or "GET")
local engine = brute.Engine:new(Driver, host, port, method )
engine.options.script_name = SCRIPT_NAME
if ( not(path) ) then
return " \n ERROR: No path was specified (see http-brute.path)"

View File

@@ -135,7 +135,7 @@ Driver = {
nmap.registry['credentials'] = nmap.registry['credentials'] or {}
nmap.registry.credentials['http'] = nmap.registry.credentials['http'] or {}
table.insert( nmap.registry.credentials.http, { username = username, password = password } )
return true, brute.Account:new( username, password, "OPEN")
return true, brute.Account:new( username, password, creds.State.VALID)
end
return false, brute.Error:new( "Incorrect password" )
@@ -235,6 +235,8 @@ action = function( host, port )
-- there's a bug in http.lua that does not allow it to be called by
-- multiple threads
engine:setMaxThreads(1)
engine.options.script_name = SCRIPT_NAME
if ( not(uservar) ) then
engine.options:setOption( "passonly", true )
end

View File

@@ -78,10 +78,10 @@ Driver =
nmap.registry['informix-brute'] = {}
end
table.insert( nmap.registry['informix-brute'], { ["username"] = username, ["password"] = password } )
return true, brute.Account:new(username, password, "OPEN")
return true, brute.Account:new(username, password, creds.State.VALID)
-- Check for account locked message
elseif ( data:match("INFORMIXSERVER does not match either DBSERVERNAME or DBSERVERALIASES") ) then
return true, brute.Account:new(username, password, "OPEN")
return true, brute.Account:new(username, password, creds.State.VALID)
end
return false, brute.Error:new( data )
@@ -93,21 +93,13 @@ Driver =
self.helper:Close()
end,
--- Perform a connection with the helper, this makes sure that the Informix
-- instance is correct.
--
-- @return status true on success false on failure
-- @return err containing the error message on failure
check = function( self )
return true
end,
}
action = function(host, port)
local status, result
local engine = brute.Engine:new(Driver, host, port )
engine.options.script_name = SCRIPT_NAME
status, result = engine:start()

View File

@@ -47,7 +47,7 @@ Driver = {
local status = self.helper:login( self.target, username, password, "CHAP")
if ( status ) then
return true, brute.Account:new(username, password, "OPEN")
return true, brute.Account:new(username, password, creds.State.VALID)
end
return false, brute.Error:new( "Incorrect password" )
@@ -78,7 +78,10 @@ action = function( host, port )
if ( status ) then return "No authentication required" end
local accounts
status, accounts = brute.Engine:new(Driver, host, port):start()
local engine = brute.Engine:new(Driver, host, port)
engine.options.script_name = SCRIPT_NAME
status, accounts = engine:start()
if ( status ) then return accounts end
end

View File

@@ -165,7 +165,7 @@ Driver =
login = function(self, _, password)
if self:testpass(password) then
return true, brute.Account:new("", password, "OPEN")
return true, brute.Account:new("", password, creds.State.VALID)
end
return false, brute.Error:new("Incorrect password")
end,
@@ -173,16 +173,13 @@ Driver =
disconnect = function(self)
return self.socket:close()
end,
check = function(self) --deprecated
return true
end,
}
action = function(host, port)
local engine = brute.Engine:new(Driver, host, port)
engine.options.firstonly = true
engine.options:setOption("passonly", true)
engine.options.script_name = SCRIPT_NAME
local status, result = engine:start()
return result
end

View File

@@ -64,20 +64,18 @@ Driver = {
if self.session:authenticate(username, password) then
-- store the account for possible future use
omp2.add_account(self.host, username, password)
return true, brute.Account:new(username, password, "OPEN")
return true, brute.Account:new(username, password, creds.State.VALID)
else
return false, brute.Error:new("login failed")
end
end,
--- Deprecated
check = function(self)
return true
end,
}
action = function(host, port)
local status, result = brute.Engine:new(Driver, host, port):start()
local engine = brute.Engine:new(Driver, host, port)
engine.options.script_name = SCRIPT_NAME
local status, result = engine:start()
return result
end

View File

@@ -100,10 +100,10 @@ Driver =
local status, data = self.helper:Login( username, password )
if ( status ) then
return true, brute.Account:new(username, password, "OPEN")
return true, brute.Account:new(username, password, creds.State.VALID)
-- Check for account locked message
elseif ( data:match("ORA[-]28000") ) then
return true, brute.Account:new(username, password, "LOCKED")
return true, brute.Account:new(username, password, creds.State.LOCKED)
-- check for any other message
elseif ( data:match("ORA[-]%d+")) then
stdnse.print_debug(3, "username: %s, password: %s, error: %s", username, password, data )
@@ -147,6 +147,7 @@ Driver =
action = function(host, port)
local status, result
local engine = brute.Engine:new(Driver, host, port )
engine.options.script_name = SCRIPT_NAME
if ( not( nmap.registry.args['oracle-brute.sid'] ) and not( nmap.registry.args['tns.sid'] ) ) then
return "ERROR: Oracle instance not set (see oracle-brute.sid or tns.sid)"

View File

@@ -61,7 +61,7 @@ Driver = {
end
return false, brute.Error:new( "Incorrect password" )
end
return true, brute.Account:new(username, password, "OPEN")
return true, brute.Account:new(username, password, creds.State.VALID)
end,
disconnect = function(self) return self.helper:close() end,
@@ -100,6 +100,7 @@ action = function(host, port)
end
end
local engine = brute.Engine:new(Driver, host, port)
engine.options.script_name = SCRIPT_NAME
local status, result = engine:start()
return result
end

View File

@@ -207,7 +207,7 @@ Driver =
self.invalid_users[username] = true
return false, brute.Error:new("Username not found")
elseif ( status and msg:match("success") ) then
return true, brute.Account:new(username, password, "OPEN")
return true, brute.Account:new(username, password, creds.State.VALID)
else
return false, brute.Error:new( "Incorrect password" )
end
@@ -250,12 +250,14 @@ action = function(host, port)
return " \n Anonymous SVN detected, no authentication needed"
end
if ( not( svn.auth_mech["CRAM-MD5"] ) ) then
if ( not(svn.auth_mech) or not( svn.auth_mech["CRAM-MD5"] ) ) then
return " \n No supported authentication mechanisms detected"
end
local invalid_users = {}
status, accounts = brute.Engine:new(Driver, host, port, invalid_users):start()
local engine = brute.Engine:new(Driver, host, port, invalid_users)
engine.options.script_name = SCRIPT_NAME
status, accounts = engine:start()
if( not(status) ) then
return accounts
end

View File

@@ -69,7 +69,8 @@ Driver =
login = function( self, username, password )
local status, data = self.vnc:handshake()
if ( not(status) and data:match("Too many authentication failures") ) then
if ( not(status) and ( data:match("Too many authentication failures") or
data:match("Your connection has been rejected.") ) ) then
local err = brute.Error:new( data )
err:setAbort( true )
return false, err
@@ -83,7 +84,7 @@ Driver =
status, data = self.vnc:login( nil, password )
if ( status ) then
return true, brute.Account:new("", password, "OPEN")
return true, brute.Account:new("", password, creds.State.VALID)
elseif ( not( data:match("Authentication failed") ) ) then
local err = brute.Error:new( data )
-- This might be temporary, set the retry flag
@@ -132,6 +133,7 @@ action = function(host, port)
local status, result
local engine = brute.Engine:new(Driver, host, port )
engine.options.script_name = SCRIPT_NAME
engine.options.firstonly = true
engine.options:setOption( "passonly", true )