[dokuwiki] Fwd: Dokuwiki (maybe) security issue: Null byte poisoning in LDAP authentication

  • From: Andreas Gohr <andi@xxxxxxxxxxxxxx>
  • To: DokuWiki Mailinglist <dokuwiki@xxxxxxxxxxxxx>
  • Date: Thu, 18 Sep 2014 20:10:16 +0200

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

Other related posts: