View Issue Details

IDProjectCategoryView StatusLast Update
0017640mantisbtsecuritypublic2014-12-21 13:31
Reportermattd Assigned Todregad  
PrioritynormalSeveritymajorReproducibilityalways
Status closedResolutionfixed 
Product Version1.2.17 
Target Version1.2.18Fixed in Version1.2.18 
Summary0017640: CVE-2014-6387: Null byte poisoning in LDAP authentication
Description

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 (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:

1.2.17

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.0eef,CN=Users,DC=ad,DC=ldap,DC=demo
Bind user password: passw0rd.9e27
Test user DN (to use the null byte bypass to gain access to):
CN=testuser.bcbd,CN=Users,DC=ad,DC=ldap,DC=demo
Test user password: passw0rd.8f94

(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

EDIT(dregad): removed <> around links and added missing information as per 0017640:0041193

Steps To Reproduce

With an instance of Mantis set up to use an Active Directory server for LDAP authentication, login as any valid user with a null byte as the password. The login attempt will succeed, even though the user's valid password was not given.

TagsNo tags attached.

Relationships

related to 0017967 closeddregad Reporting an issue gives: 'Invalid argument supplied for foreach()' in '/opt/mantisbt-1.2.18/core/gpc_api.php' line 259 
related to 0017977 closeddregad Fix handling of due dates 

Activities

mattd

mattd

2014-09-09 05:07

reporter   ~0041193

Apologies, I forgot to fill in some information in the description! Embarassing...

The version of Mantis I tested was 1.2.17, and the missing test LDAP server information is as follows:

Bind user DN (to bind with before performing a user search):
CN=bindaccount.0eef,CN=Users,DC=ad,DC=ldap,DC=demo
Bind user password: passw0rd.9e27
Test user DN (to use the null byte bypass to gain access to):
CN=testuser.bcbd,CN=Users,DC=ad,DC=ldap,DC=demo
Test user password: passw0rd.8f94

dregad

dregad

2014-09-09 07:56

developer   ~0041194

Hi Matt, thanks for the report. I (we) will look at it as time allows.

Has a CVE been assigned for this yet ? I would guess not since you said MITRE stated it was individual applications that needed to be fixed, but anyway if there is please let us know the number if (or when) you have it. If not, a reference to any public discussion on this topic (e.g. oss-security mailing list) would be nice to have.

grangeway

grangeway

2014-09-09 19:20

reporter   ~0041197

... and here lies one of the reasons for https://github.com/mantisbt/mantisbt/commit/fc02c46eea9d9e7cc472a7fc1801ea65d467db76 which was originally coded before we decided on our next branch debate in October 2011.

Pretty sure once you've added those commits to 1.2.17 this issue goes.

It's long been an issue in php (and other languages/frameworks) that you can use null bytes to inject in some cases. Our approach for this as everything goes through gpc_string/gpc_int etc was to strip out null bytes in gpc_string with str_replace( "\0","",$t_result );

Matt: if you get a few moments (as you've obviously got a readily available framework setup), can you see if this old patch fixes the vulnerability area you are planning on reporting on?

Pretty sure it does as IBM's appscan I believe would try adding null bytes to parameters back in 2011 - however nice approach taking it a step further with the authentication bypass from the anonymous binds.

dregad

dregad

2014-09-10 20:54

developer   ~0041201

I can confirm the ability to login as the test LDAP user with an arbitrary password by prefixing it with \0 (I used Firefox 'Tamper Data' plugin).

As suggested by @grangeway, backporting fc02c46e fixes the issue.

mattd

mattd

2014-09-12 07:02

reporter   ~0041210

dregad: No, there is no CVE assigned for the issue when it comes to PHP. As I understand it, MITRE considers that applications should keep in mind null byte poisoning attacks when performing LDAP binds, even though it's the PHP -> C boundary which is causing the issue in the first place.

There's been no recent public discussion about the issue that I'm aware of.

FWIW, I've tested the backported patch (215968fa) and found it fixes the issue. Thank you both for your prompt response!

dregad

dregad

2014-09-12 10:05

developer   ~0041211

CVE request: http://thread.gmane.org/gmane.comp.security.oss.general/13792

grangeway

grangeway

2014-09-13 09:12

reporter   ~0041214

Hi Damien,

Could you explain why:

a) It wasn't mentioned that I'd already identified there was issues with null bytes in mantis, back in 2011 ( https://github.com/mantisbt/mantisbt/commit/f3c032f8e46abce74dd3ebf0a50e0cbbd677653b ), and done a 'catch all' fix

b) why you didn't cover the fact the original fix was to cover issues with null byte injection across the code base i.e. the CVE should be for avoiding null byte injection and not just LDAP.

Back when it was coded, it stopped possible issues in the LDAP, DB layer and file system. In the case of the file system issues, I believe these are no longer an issue without the patch as we've added additional validation around include/require calls.

Whilst as I say, Mattd's taken the root cause a step further by identifying a specific flaw, the fact we've already done work to avoid this and other vulnerabilities as a catch-all within the code, it would be nice to receive some acknowledgement for that...

Equally, why haven't we followed our normal policy on this (with regards to CVE's)?

Related Changesets

MantisBT: master fc02c46e

2013-10-12 13:58

Paul Richards


Details Diff
Strip null bytes out of GPC input strings Affected Issues
0017640, 0017977
mod - core/gpc_api.php Diff File

MantisBT: master-1.2.x 215968fa

2013-10-12 13:58

Paul Richards

Committer: dregad


Details Diff
Strip null bytes out of GPC input strings

Backporting commit fc02c46eea9d9e7cc472a7fc1801ea65d467db76 from master
branch to fix issue 0017640

Signed-off-by: Damien Regad <dregad@mantisbt.org>
Affected Issues
0017640, 0017967, 0017977
mod - core/gpc_api.php Diff File

MantisBT: master f725b469

2014-05-31 04:40

Paul Richards


Details Diff
Fix handling of due dates

This was broken due to commit fc02c46eea9d9e7cc472a7fc1801ea65d467db76.

This commit added stripping of null bytes, but did not correctly handle null values
Affected Issues
0017640, 0017977
mod - core/gpc_api.php Diff File

MantisBT: master-1.2.x 580d45e9

2014-05-31 04:40

Paul Richards

Committer: dregad


Details Diff
Fix 0017977: handling of due dates

Commit 215968fa8ff33e327f0600765a5caa24de392cbc (backported from master
fc02c46eea9d9e7cc472a7fc1801ea65d467db76 to fix issue 0017640) added
stripping of null bytes in GPC API, but did not correctly handle null
values.

This is a backport of commit f725b46954a514880792dd4be8228287756fac3d
from master branch, to address this issue.

Signed-off-by: Damien Regad <dregad@mantisbt.org>
Affected Issues
0017640, 0017977
mod - core/gpc_api.php Diff File

MantisBT: master-1.2.x 99ada4de

2014-12-21 06:46

dregad


Details Diff
Fix system warning in gpc_get_string_array()

The fix for issue 0017640 did not consider that the value returned by
gpc_get() is not necessarily an array - it can be the default value
(e.g. null) causing PHP to throw an 'Invalid argument supplied for
foreach()' warning.

Fixes 0017967, regression from 215968fa8ff33e327f0600765a5caa24de392cbc
Affected Issues
0017640, 0017967
mod - core/gpc_api.php Diff File

MantisBT: master 61c8548c

2014-12-21 06:46

dregad


Details Diff
Fix system warning in gpc_get_string_array()

The fix for issue 0017640 did not consider that the value returned by
gpc_get() is not necessarily an array - it can be the default value
(e.g. null) causing PHP to throw an 'Invalid argument supplied for
foreach()' warning.

Fixes 0017967 (ported from 1.2.x)
Affected Issues
0017640, 0017967
mod - core/gpc_api.php Diff File