1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-16 12:49:02 +00:00

Tweaked ldap-brute.nse to work correctly when the target AD implementation is 2008 R2 and perhaps other sources.

Added detection of accounts where the credentials are correct, but the account is expired, not allowed to log on at the time of the scan or has been limited to logging in from particular hosts.

Notes on these changes were sent to the mailing list.
This commit is contained in:
tomsellers
2011-08-08 00:26:02 +00:00
parent 05bcbe71d9
commit e7b2ffe7c8
2 changed files with 31 additions and 8 deletions

View File

@@ -93,6 +93,10 @@ o [NSE] Added ftp-vuln-cve2010-4221 script, which checks if the ProFTPD
o [NSE] Added ftp-vsftpd-backdoor, which detects a backdoor that was introduced o [NSE] Added ftp-vsftpd-backdoor, which detects a backdoor that was introduced
into vsftpd-2.3.4 source code distributions. [Daniel Miller] into vsftpd-2.3.4 source code distributions. [Daniel Miller]
o [NSE] Added support for 2008 R2 functional level Active Directory instances
to ldap-brute. Also added detection for valid credentials where the target
account was expired or limited by time or login host constraints. [Tom Sellers]
Nmap 5.59BETA1 [2011-06-30] Nmap 5.59BETA1 [2011-06-30]
o [NSE] Added 40 scripts, bringing the total to 217! You can learn o [NSE] Added 40 scripts, bringing the total to 217! You can learn

View File

@@ -16,8 +16,8 @@ fully distinguished name. E.g., "Patrik Karlsson" vs.
This type of authentication is not supported on e.g. OpenLDAP. This type of authentication is not supported on e.g. OpenLDAP.
This script uses some AD-specific support and optimizations: This script uses some AD-specific support and optimizations:
* LDAP on Windows 2003 reports different error messages depending on whether an account exists or not. If the script recieves an error indicating that the username does not exist it simply stops guessing passwords for this account and moves on to the next. * LDAP on Windows 2003 reports different error messages depending on whether an account exists or not. If the script receives an error indicating that the username does not exist it simply stops guessing passwords for this account and moves on to the next.
* The script attempts to authenticate with the username only if no LDAP base is specified. The benefit of authenticating this way is that the LDAP path of each account does not need to be known in advance as it's looked up by the server. * The script attempts to authenticate with the username only if no LDAP base is specified. The benefit of authenticating this way is that the LDAP path of each account does not need to be known in advance as it's looked up by the server. This technique will only find a match if the account Display Name matches the username being attempted.
]] ]]
--- ---
@@ -45,10 +45,11 @@ require 'ldap'
require 'unpwdb' require 'unpwdb'
require 'comm' require 'comm'
-- Version 0.3 -- Version 0.4
-- Created 01/20/2010 - v0.1 - created by Patrik Karlsson -- Created 01/20/2010 - v0.1 - created by Patrik Karlsson
-- Revised 01/26/2010 - v0.2 - cleaned up unpwdb related code, fixed ssl stuff -- Revised 01/26/2010 - v0.2 - cleaned up unpwdb related code, fixed ssl stuff
-- Revised 02/17/2010 - v0.3 - added AD specific checks and fixed bugs related to LDAP base -- Revised 02/17/2010 - v0.3 - added AD specific checks and fixed bugs related to LDAP base
-- Revised 08/07/2011 - v0.4 - adjusted AD match strings to be level independent, added additional account condition checks
portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"}) portrule = shortport.port_or_service({389,636}, {"ldap","ldapssl"})
@@ -163,29 +164,47 @@ action = function( host, port )
end end
-- Is AD telling us the account does not exist? -- Is AD telling us the account does not exist?
if not status and response:match("AcceptSecurityContext error, data 525, vece") then if not status and response:match("AcceptSecurityContext error, data 525,") then
invalid_account_cnt = invalid_account_cnt + 1 invalid_account_cnt = invalid_account_cnt + 1
break break
end end
-- Account Locked Out -- Account Locked Out
if not status and response:match("AcceptSecurityContext error, data 775, vece") then if not status and response:match("AcceptSecurityContext error, data 775,") then
table.insert( valid_accounts, string.format("%s => Account locked out", fq_username ) ) table.insert( valid_accounts, string.format("%s => Account locked out", fq_username ) )
break break
end end
-- Login correct, account disabled -- Login correct, account disabled
if not status and response:match("AcceptSecurityContext error, data 533, vece") then if not status and response:match("AcceptSecurityContext error, data 533,") then
table.insert( valid_accounts, string.format("%s:%s => Login correct, account disabled", fq_username, password:len()>0 and password or "<empty>" ) ) table.insert( valid_accounts, string.format("%s:%s => Login correct, account disabled", fq_username, password:len()>0 and password or "<empty>" ) )
break break
end end
-- Login correct, user must change password -- Login correct, user must change password
if not status and response:match("AcceptSecurityContext error, data 773, vece") then if not status and response:match("AcceptSecurityContext error, data 773,") then
table.insert( valid_accounts, string.format("%s:%s => Login correct, user must change password", fq_username, password:len()>0 and password or "<empty>" ) ) table.insert( valid_accounts, string.format("%s:%s => Login correct, user must change password", fq_username, password:len()>0 and password or "<empty>" ) )
break break
end end
-- Login correct, user account expired
if not status and response:match("AcceptSecurityContext error, data 701,") then
table.insert( valid_accounts, string.format("%s:%s => Login correct, user account expired", fq_username, password:len()>0 and password or "<empty>" ) )
break
end
-- Login correct, user account logon time restricted
if not status and response:match("AcceptSecurityContext error, data 530,") then
table.insert( valid_accounts, string.format("%s:%s => Login correct, user account logon time restricted", fq_username, password:len()>0 and password or "<empty>" ) )
break
end
-- Login correct, user account can only log in from certain workstations
if not status and response:match("AcceptSecurityContext error, data 531,") then
table.insert( valid_accounts, string.format("%s:%s => Login correct, user account cannot login from current host", fq_username, password:len()>0 and password or "<empty>" ) )
break
end
--Login, correct --Login, correct
if status then if status then
status = is_valid_credential( socket, context ) status = is_valid_credential( socket, context )
@@ -205,7 +224,7 @@ action = function( host, port )
passwords("reset") passwords("reset")
end end
stdnse.print_debug( "Finnished brute against LDAP, total tries: %d, tps: %d", tot_tries, ( tot_tries / ( ( nmap.clock_ms() - clock_start ) / 1000 ) ) ) stdnse.print_debug( "Finished brute against LDAP, total tries: %d, tps: %d", tot_tries, ( tot_tries / ( ( nmap.clock_ms() - clock_start ) / 1000 ) ) )
if ( invalid_account_cnt == user_cnt and base_dn ~= nil ) then if ( invalid_account_cnt == user_cnt and base_dn ~= nil ) then
return "WARNING: All usernames were invalid. Invalid LDAP base?" return "WARNING: All usernames were invalid. Invalid LDAP base?"