Hi *, I recently got the message below which seems to point to a security flaw in relation to LDAP/AD authentications. It's a long mail and I'm not 100% sure I understood it correctly. But it seems there's a way to a) zero out a password, switching the login to an unauthenticated one b) zero out user and password, switching the login to an anonymous one I think the correct way to fix this is removing zero bytes from the affected strings. I could implement that in various places and I wonder what would be the best: 1) in the LDAP and AD auth plugins 2) in the auth handling (thus applying to all auth plugins) 3) in $INPUT filtering all GET and POST vars always I am leaning towards 3) but there might be a reasonable case where you might want to post a zero byte? I am also not sure about the severity of the bug and would like to get your input on that. Andi ---------- Forwarded message ---------- From: Matthew Daley <mattd@xxxxxxxxxxx> Date: Tue, Sep 9, 2014 at 11:13 AM Subject: Dokuwiki (maybe) security issue: Null byte poisoning in LDAP authentication To: andi@xxxxxxxxxxxxxx Hi, I've been doing some research on a security issue that has already been discovered and elaborated on, but where little progress has been undertaken on resolving the underlying causes until recently. Please excuse the length and form-letter style of this message; it is being sent to a number of affected projects' security contacts. Nevertheless, I have tested your application (Dokuwiki; details below) and have found that it is susceptible to the issue, hence this message. The issue is in how some software performs user authentication using LDAP servers in certain configurations. It affects a variety of applications, platforms and programming languages that make use of LDAP; PHP was one of them (up until recently). Since your application uses PHP and has the ability to authenticate users using PHP's LDAP extension, your users may in turn be affected. My personal opinion is that PHP itself is the component at fault, and not your application. However, MITRE (who assign CVEs, etc.) disagree and suggest that individual applications that make use of PHP's LDAP extension and that are vulnerable to the issue are at fault. Hence, I am writing this message to your project for two reasons: 1) You may indeed wish to treat the issue as a security vulnerability in your own application and hence handle it via your normal processes. 2) I intend to publically disclose all of my findings in the near future (see below); your application will be listed as one of many I tested that were found vulnerable to the issue. Hence, this message also acts as a heads-up on this future disclosure. Alternatively, your project might agree with my interpretation of where the issue's fault lies instead of MITRE's, in which case presumably no action would be taken on your part. A description of the issue itself follows. If you are familiar with LDAP and how the various kinds of binds work, please excuse the verbosity! --- Users can authenticate to an LDAP directory server by performing a "bind" operation with it. This allows clients to access restricted information in the directory, perform updates and so on. Other applications can use this bind operation as a way of allowing users to authenticate to the application, using the LDAP server as a centralized source of account information. In this case they usually do not go on and use the bound connection to perform privileged LDAP operations, but instead simply unbind or close it after noting the bind operation's success. There are two main methods of bind; simple and SASL. Within the simple method, there are three submethods: anonymous, authenticated, and unauthenticated. See RFC 4513, section 5 (<http://tools.ietf.org/html/rfc4513#section-5>). * An anonymous bind is performed by providing an empty name and an empty password. Most applications already ensure that non-empty usernames are given when handling their untrusted login input, and so this bind method does not relate to the issue at hand. * An authenticated bind is performed by providing a non-empty name and a non-empty password. This is the usual form of binding which applications (intend) to make. * However, an unauthenticated bind is performed by providing a non-empty name and an *empty* password. An unauthenticated bind results in a success result being returned from the bind operation, just like if either of the other forms of bind were successfully performed. Applications that are using LDAP for user authentication must ensure that only authenticated simple binds are attempted. If not, they may incorrectly assume that the success of a bind operation they initiated was due to a successful authenticated bind being performed even though an unauthenticated or anonymous bind may have actually been performed instead. This is done by ensuring that the username and password used in the bind operation are non-empty. Most applications correctly check for non-empty passwords as part of handling LDAP-based authentication. Although only anonymous binds are explicitly called out in the PHP documentation for the function that performs the binds in question (ldap_bind, <http://www.php.net/manual/en/function.ldap-bind.php>), many commenters have noted this possible security flaw and have warned others about the need to check for non-empty passwords (i.e. <http://www.php.net/manual/en/function.ldap-bind.php#46660>). However, there is another related, subtle issue: PHP's LDAP extension provides bindings to the C-based OpenLDAP library, including the ldap_bind function, to perform LDAP binds. The PHP-side function takes in a LDAP connection object and username (DN) and password strings as arguments, with its semantics being the same as the OpenLDAP ldap_bind function called with LDAP_AUTH_SIMPLE method argument. PHP passes the PHP string arguments to the OpenLDAP C function - which expects C-style null-terminated strings - by passing a pointer to the PHP string's value data in memory. String values in PHP can contain arbitrary byte values, including the null character (byte value 0x00). If an argument to PHP's ldap_bind contains such a null byte, no special action is taken, so from the OpenLDAP C ldap_bind function's point of view, such strings are truncated at the first null byte. Hence, an attacker can pass a string starting with a null byte as a password when authenticating to an application that uses PHP's ldap_bind. This will, in many cases, bypass the application's own check for a non-empty password (since the string is non-empty from PHP's perspective), but still appear to be empty to the OpenLDAP ldap_bind function, leading to an unauthenticated bind being performed against the application's intent. This allows authentication bypass, as the attacker can login as any given user without needing to know their real LDAP password. --- I have been testing the susceptibility of many applications, both PHP- and other-language-based, to this issue. First, one needs an LDAP server that allows unauthenticated binds to be performed. The OpenLDAP server explicitly denies such binds by default as a conscious security choice; see section 14.3.1 of the OpenLDAP server administrators' guide, <http://www.openldap.org/doc/admin24/security.html#Authentication Methods>: "An unauthenticated bind also results in an anonymous authorization association. Unauthenticated bind mechanism is disabled by default, but can be enabled by specifying "allow bind_anon_cred" in slapd.conf(5). As a number of LDAP applications mistakenly generate unauthenticated bind request when authenticated access was intended (that is, they do not ensure a password was provided), this mechanism should generally remain disabled." However, there is at least one other popular LDAP server that does *not* deny unauthenticated binds: Microsoft's Active Directory. I have been testing with Windows Server 2008 R2 Standard w/ SP1 and Windows Server 2012 R2 Standard, and have verified that unauthenticated binds are fully supported and allowed. Others have verified the same on Microsoft Windows 2003 and Novell NetWare eDirectory Server 8.8. --- As mentioned earlier, this has already been discovered and elaborated on earlier, see <https://net.educause.edu/ir/library/pdf/csd4875.pdf> and <http://www.php.net/manual/en/function.ldap-bind.php#73718>, both by Alex Everett, said previous discoverer. Even the aforementioned RFC 4513 calls it out in section 6.3.1 (<http://tools.ietf.org/html/rfc4513#section-6.3.1>). However, it appears little has changed since then, until now. I recently submitted a patch to PHP that causes ldap_bind to raise an error if null bytes are present in either the username or the password string. Instances of your application that are running on a version of PHP that contains this patch are not vulnerable to the issue, but otherwise are. The details of this patch are as follows: Affected PHP versions: <= 5.5.11, <= 5.4.27 Fixed PHP versions: 5.5.12, 5.4.28 Fix: <http://git.php.net/?p=php-src.git;a=blobdiff;f=ext/ldap/ldap.c;h=9fe48a03aa04ed57ef752b0aad632b912205545f;hp=9d3a710b6044a60680c60f4480b83d12b9bf1cc2;hb=ad1b9eef98df53adefa0c79c02e5dc1f2b928b8c;hpb=40a931 6dff6e043b534844b2ab167318250be277> Changelog: <http://git.php.net/?p=php-src.git;a=blobdiff;f=NEWS;h=2d98de7fa0ca4847e04559777da1b97edc5a4625;hp=66f0d05645a7ceefc93840c36a8789d928ce434b;hb=ad1b9eef98df53adefa0c79c02e5dc1f2b928b8c;hpb=40a9316dff6 e043b534844b2ab167318250be277> --- As mentioned earlier, I have tested your application and have found that it is vulnerable to this issue. The version(s) of your application I tested, including any relevant plugin or extension information, is as follows: Dokuwiki 2014-05-05a, with either the authldap plugin (2013-04-19) or authad plugin (2014-02-14) In order to help demonstrate this issue to your project, I have set up a publically-accessible Active Directory server (Windows Server 2012 R2 Standard). You can configure an instance of your application to authenticate using this AD server in order to replicate the issue. The details of this server are as follows: IP: 54.68.122.145 Port: TCP 389 (default; no encryption) Active Directory domain name: AD Account suffix: @ad.ldap.demo Subtree containing user objects: CN=Users,DC=ad,DC=ldap,DC=demo Bind user DN (to bind with before performing a user search): CN=bindaccount.d733,CN=Users,DC=ad,DC=ldap,DC=demo Bind user password: passw0rd.a2fe Test user DN (to use the null byte bypass to gain access to): CN=testuser.2d09,CN=Users,DC=ad,DC=ldap,DC=demo Test user password: passw0rd.f028 (All the usual Active Directory LDAP settings apply, such as the user login name being given by the "sAMAccountName" attribute, using LDAP version 3, and so on.) In order to login with null bytes present in user passwords, I suggest the use of a simple HTTP proxy such as proxpy (<https://code.google.com/p/proxpy/>). Here is a plugin for proxpy that changes all instances of the string "NULLBYTE" into URL-encoded representations of null bytes: -- 8< -- def proxy_mangle_request(req): v = req.getHeader('Content-Type') if v and 'application/x-www-form-urlencoded' in v[0]: req.body = req.body.replace('NULLBYTE', '%00') return req -- 8< -- However, for easy testing, it may just be easier to hack your application's login handling code so as to manually inject a null byte at the start of a user's password. Observe that you are able to login to your application as the "testuser" account with any password, so long as it begins with a null byte. --- As mentioned at the start of this message, I intend to post a wrap-up of my findings to various security mailing lists in the near future. I have set a date for this disclosure of 4 weeks from now: Tuesday the 7th of October. Thank you for reading through this huge message! (Also, I hate to say it, but just in case: I am happy to be credited on any announcement; just use my name as-is. Cheers!) - Matthew Daley In addition, I have just found that Dokuwiki is also vulnerable to null-byte-forced anonymous binds (as opposed to just unauthenticated binds): With a Dokuwiki instance using Active Directory for LDAP authentication (as described earlier), one can log in using a username that has a null byte as the first character and with a password that similarly has a null byte as the first character. This will result in an anonymous bind being performed by Dokuwiki, and hence the log in will always succeed, regardless of the whether the username exists or not in the LDAP directory. -- splitbrain.org -- DokuWiki mailing list - more info at http://www.dokuwiki.org/mailinglist