Hi all,

We detected a problem with the function is_ldapv3 in ldap.inc leading to a 
false positive in plugin 10723 (ldap_null_bind.nasl) when testing an active 
directory (all windows server version).

The is_ldapv3 function is to basic and check directly a hardcoded offset for 
the error code. This works fine for (at least) some openldap version, but it 
fails when querying an active directory server, whereas the answer is 
perfectly valid.

This is due to the fact that active directories encode the BER/ASN.1 fields in 
a different (but legal) way than openldap.
- OpenLDAP encodes sizes on 1 byte since answers are < 128bytes.
- Active Directory behaves differently and encodes size using the special 
value 0x80, than means that size is encoded in the following bytes (see 
http://en.wikipedia.org/wiki/Basic_Encoding_Rules#Length for details). It 
seems to always store size as a 4bytes integer even if the size is < 128bytes

As a consequence, to determine the offset of the error code in the response 
data, the LDAPMessage has to be carefully parsed, decoding all the BER/ASN.1 
tokens to correctly shift the offset !


So find attached to this mail :
- the modified ldap.inc tested against openldap and windows AD;
- the (same) modifications as a patch against current ldap.inc.
With this changes, the V3 bind is correctly checked against either an openldap 
server or an active directory, and the false positive in ldap_null_bind.nasl 
vanishes.

If you want to play with wireshark, find enclosed 2 pcap files of the V3 bind 
on an openldap server and on a win 2000 active directory. This illustrates the 
different ways of encoding the sizes, leading to the differents offsets to 
probe for the bind error code.





One more thought about LDAPv2 and NULL bind. LDAPv2 is deprecated since some 
years because of design flaws and security issues (see 
http://tools.ietf.org/html/rfc3494). LDAPv3 is widely used since many years 
too.
So IMHO, ldap_null_bind.nasl should be removed as it does not sound relevant 
anymore and replaced by a plugin testing if V3 bind is allowed or not. If V3 
bind is not allowed, this is a security hole (too old and insecure ldap 
server), whereas if it's allowed, there is no problem since null bind (on 
rootdse) is mandatory in V3.


Thanks for reading !

-- 
Guillaume Castagnino
    [email protected] / [email protected]
###############################################################################
# Functions for LDAP
#
# Authors:
# Michael Meyer
#
# Copyright:
# Copyright (c) 2009 Greenbone Networks GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2
# (or any later version), as published by the Free Software
# Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
# USA.
###############################################################################

function ldap_alive(port)
{

   local_var  req, buf, response;

   req = raw_string(0x30,0x84,0x00,0x00,0x00,0x59,0x02,0x01,0x05,0x63,0x84,0x00,
                    0x00,0x00,0x50,0x04,0x13,0x64,0x63,0x3d,0x6f,0x70,0x65,0x6e,
                    0x76,0x61,0x73,0x64,0x63,0x2c,0x64,0x63,0x3d,0x6e,0x65,0x74,
                    0x0a,0x01,0x02,0x0a,0x01,0x00,0x02,0x01,0x00,0x02,0x01,0x00,
                    0x01,0x01,0x00,0xa3,0x84,0x00,0x00,0x00,0x13,0x04,0x0b,0x6f,
                    0x62,0x6a,0x65,0x63,0x74,0x43,0x6c,0x61,0x73,0x73,0x04,0x04,
                    0x75,0x73,0x65,0x72,0x30,0x84,0x00,0x00,0x00,0x0d,0x04,0x0b,
                    0x64,0x69,0x73,0x70,0x6c,0x61,0x79,0x4e,0x61,0x6d,0x65);  

   soc = open_sock_tcp(port);
   if(!soc)return NULL;

   send(socket:soc, data:req);
   buf = recv(socket:soc, length:1);
   if( buf == NULL )return NULL;
   close(soc);

   if(strlen(buf) == 1) {
     response = hexstr(buf);
     if(response =~ "^30$" )return TRUE;
   }
 return NULL;
}

# extract the message length
# WARNING offset is updated by the function call (reference) !
function get_ber_size(buf, offset) {
    local_var lm_length, length_length, i;
    lm_length = ord(buf[offset]);
    offset++;
    if(lm_length > 128) {
        # undetermined length message
        length_length = lm_length - 128;
        lm_length = 0;
        for(i=0; i<length_length; i++) {
            lm_length = (lm_length << 8) | ord(buf[offset++]);
        }
    }
    return lm_length;
}

function is_ldapv3(port) {
    local_var offset, lm_length, messageId_length, bindResponse_length, resultCode_length, resultCode, i, soc, buf;

    soc = open_sock_tcp(port);
    if(!soc) return FALSE;

    req =  raw_string(0x30,0x0c,0x02,0x01,0x01,0x60,0x07,0x02,0x01,0x03,0x04,0x00,0x80,0x00); # v3 bind
    send(socket:soc, data:req);

    buf = recv(socket:soc, length:128);
    close(soc);

    if(!buf) return FALSE;

    # decode ldapMessage length (encoded as BER)
    offset = 0;
    if(ord(buf[offset++]) != 48) return FALSE; # (0x30)
    lm_length = get_ber_size(buf, offset);
    if (strlen(buf) < lm_length + offset) return FALSE; # whoops, we have not enough data (should never happen since bindResponse is a short message)

    # we are not at offset = message id, we skip it
    if (ord(buf[offset++]) != 2) return FALSE; # messageId is an INT
    messageId_length = get_ber_size(buf, offset);
    offset += messageId_length;

    # now enter the bindResponse
    if (ord(buf[offset++]) != 97) return FALSE; # (0x61)
    bindResponse_length = get_ber_size(buf, offset);

    # now dig into response code
    if (ord(buf[offset++]) != 10) return FALSE; # (0x0A)
    resultCode_length = get_ber_size(buf, offset);
    resultCode = 0;
    for (i=0; i<resultCode_length; i++) {
        resultCode = (resultCode << 8) | ord(buf[offset++]);
    }
    if (resultCode == 0) return TRUE; # server has accepted the v3 bind

    return FALSE;
}  
--- ldap.inc.old	2012-03-28 11:53:59.511051142 +0200
+++ ldap.inc	2012-03-28 13:59:40.056263026 +0200
@@ -52,23 +52,60 @@
  return NULL;
 }
 
-function is_ldapv3(port) {
-
-  soc = open_sock_tcp(port);
-  if(!soc)return FALSE;
-
-  req =  raw_string(0x30,0x0c,0x02,0x01,0x01,0x60,0x07,0x02,0x01,0x03,0x04,0x00,0x80,0x00); # v3 bind
-  send(socket:soc, data:req);
+# extract the message length
+# WARNING offset is updated by the function call (reference) !
+function get_ber_size(buf, offset) {
+    local_var lm_length, length_length, i;
+    lm_length = ord(buf[offset]);
+    offset++;
+    if(lm_length > 128) {
+        # undetermined length message
+        length_length = lm_length - 128;
+        lm_length = 0;
+        for(i=0; i<length_length; i++) {
+            lm_length = (lm_length << 8) | ord(buf[offset++]);
+        }
+    }
+    return lm_length;
+}
 
-  buf = recv(socket:soc, length:128);
-  close(soc);
+function is_ldapv3(port) {
+    local_var offset, lm_length, messageId_length, bindResponse_length, resultCode_length, resultCode, i, soc, buf;
 
-  if(!buf)return FALSE;
+    soc = open_sock_tcp(port);
+    if(!soc) return FALSE;
 
-  if(ord(buf[9]) == 0 && ord(buf[10]) == 4 && ord(buf[11] == 0)) { # ord(buf[9] == bindResponse -> resultCode. 0 == success, >0 == error
-    return TRUE;
-  }  
+    req =  raw_string(0x30,0x0c,0x02,0x01,0x01,0x60,0x07,0x02,0x01,0x03,0x04,0x00,0x80,0x00); # v3 bind
+    send(socket:soc, data:req);
 
-  return FALSE;
+    buf = recv(socket:soc, length:128);
+    close(soc);
+
+    if(!buf) return FALSE;
+
+    # decode ldapMessage length (encoded as BER)
+    offset = 0;
+    if(ord(buf[offset++]) != 48) return FALSE; # (0x30)
+    lm_length = get_ber_size(buf, offset);
+    if (strlen(buf) < lm_length + offset) return FALSE; # whoops, we have not enough data (should never happen since bindResponse is a short message)
+
+    # we are not at offset = message id, we skip it
+    if (ord(buf[offset++]) != 2) return FALSE; # messageId is an INT
+    messageId_length = get_ber_size(buf, offset);
+    offset += messageId_length;
+
+    # now enter the bindResponse
+    if (ord(buf[offset++]) != 97) return FALSE; # (0x61)
+    bindResponse_length = get_ber_size(buf, offset);
+
+    # now dig into response code
+    if (ord(buf[offset++]) != 10) return FALSE; # (0x0A)
+    resultCode_length = get_ber_size(buf, offset);
+    resultCode = 0;
+    for (i=0; i<resultCode_length; i++) {
+        resultCode = (resultCode << 8) | ord(buf[offset++]);
+    }
+    if (resultCode == 0) return TRUE; # server has accepted the v3 bind
 
+    return FALSE;
 }  

Attachment: ldap-bindv3-openldap.pcap
Description: Binary data

Attachment: ldap-bindv3-win2k.pcap
Description: Binary data

_______________________________________________
Openvas-plugins mailing list
[email protected]
http://lists.wald.intevation.org/cgi-bin/mailman/listinfo/openvas-plugins

Reply via email to