AD, LDS and LDAP unauthenticated binds: A series of unfortunate security events

Update: December 2018: Microsoft have provided an option to disable unauthenticated binds in Windows Server 2019

A caution to anyone that uses applications that rely on LDAP authentication against Active Directory, or Active Directory Lightweight Directory Services to do so. Both services will appear to accept a blank password for any users when performing a simple bind. While behind the scenes, that's not what is happening, if your application doesn't check for and reject a logon attempt with the blank password itself, it might incorrectly assume a successful authentication against LDAP. This post details how I came to learn about this behaviour, how wide spread the problem is, and what can be done about it.

The discovery

A few weeks ago, I was at my desk, enjoying my lunch, when I received a call from a customer in a panic. He told me that our AD LDS server was allowing people to access his application without typing in a password. I assumed he was talking about anonymous binds (binding with no username and password) and informed him that it's a normal LDAP thing, and it just means his application probably hadn't been coded properly.

But he insisted that was not what was happening. An anonymous bind requires a blank username and password, but he told me he was entering a username but had a blank password. I assumed he was going mad, but decided to fire up ldp.exe to test the scenario, confident that I'd be back to my sandwich and mindless web browsing in no time.

Imagine my surprise when I was greeted with the following response

res = ldap_simple_bind_s(ld, 'uid=ryan,ou=users,dc=lithnet,dc=local, <unavailable>); // v.3

Sorry... what?!

"Ummm..... Let me call you back..."

The investigation

We were transitioning away from an Oracle LDAP server at the time, so I tried to perform the same bind operation there:

res = ldap_simple_bind_s(ld, 'uid=ryan,ou=users,dc=lithnet,dc=local', <unavailable>); // v.3
Error <48>: ldap_simple_bind_s() failed: Inappropriate Authentication
Server error: binds with a dn require a password


Good. That's what I would have expected. What on earth was LDS doing? First I needed to make sure I didn't have an information disclosure issue. There is sensitive information in our directory, and it would be really bad news if this data was disclosed. I tried to perform a search in LDS after 'binding' with no password.

ldap_search_s(ld, "DC=lithnet,DC=local", 2, "uid=ryan", attrList,  0, &msg)
Error: Search: Operations Error. <1>
Server error: 000004DC: LdapErr: DSID-0C090752, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v2580
Error 0x4DC The operation being requested was not performed because the user has not been authenticated.

Phew. Looking back at the logon result, it did indeed show I was authenticated as the built-in account 'NT AUTHORITY\ANONYMOUS LOGON'. This is the same result as performing an anonymous bind. Without granting permissions to this account, no data was going to be inadvertently disclosed. But it still didn't explain why the user could bind without a password. It looks like the user specified is being authenticated successfully without a password, although we are not being authenticating as that user at all.

Now, I want to talk about anonymous binds for a moment. There is some confusion out there in articles and posts I have read about this behaviour, with people saying that this is an anonymous bind and it is normal behaviour. Anonymous binds are indeed normal and required by the LDAP specification. Clients must be able to connect to the RootDSE anonymously, which contains information they need to understand the capabilities, configuration, and authentication types that the directory supports. LDS allows anonymous binds to the RootDSE, as does any RFC-compliant LDAP client. The fact that we were bound as ANONYMOUS LOGON isn't the issue. The fact that we provided a username and no password is an issue. This is NOT an anonymous bind as per the RFC specification.

Moving on, I decided to try to log into an application that I knew used LDS for authentication.

Phew, again. The application is obviously smart enough to detect the blank password and refuse to authenticate. I started wondering if this the responsibility of all applications to implement this check? It does sound like a reasonable safety precaution to implement when coding an application, but I've seen my fair share of code enough to know that not all developers are as prudent as this. I tried to login to the application the customer called me about, and sure enough, it let me into the application. I had varying results from other applications I tried. It really came down to the specific LDAP library and authentication implementation that these clients used. Suffice to say, it was not a one-off problem.

Rolling up the sleeves

It was time to dig into the LDAP RFC to see what is going on here. Section 5.1.2 of RFC 4513 revealed something very interesting that I did not know about LDAP before. It reads;

5.1.2. Unauthenticated Authentication Mechanism of Simple Bind

An LDAP client may use the unauthenticated authentication mechanism of the simple Bind method to establish an anonymous authorization state by sending a Bind request with a name value (a distinguished name in LDAP string form [RFC4514] of non-zero length) and specifying the simple authentication choice containing a password value of zero length

So this was apparently a legitimate thing, and it's called an Unauthenticated bind. Note this is distinct from an "Anonymous bind" where both a blank username and blank password must be provided. Reading further along reveals something even more interesting

The distinguished name value provided by the client is intended to be used for trace (e.g., logging) purposes only. The value is not to be authenticated or otherwise validated (including verification that the DN refers to an existing directory object). The value is not to be used (directly or indirectly) for authorization purposes.
It seems that this 'feature' exists only for logging purposes. According to the RFC, you don't even need a real DN. I tested this out.

res = ldap_simple_bind_s(ld, 'this is not a DN', <unavailable>); // v.3

Well, that worked, as the RFC said it should. At this point I started to get a bit worried. I've spent a long time working with LDAP directories and I didn't know this was a thing. What chance do we have that application developers, who don't spend their lives living and breathing identity and LDAP, are aware of this and implement appropriate mitigations? Reading the next paragraph did give me some hope.

Unauthenticated Bind operations can have significant security issues (see Section 6.3.1). In particular, users intending to perform Name/Password Authentication may inadvertently provide an empty password and thus cause poorly implemented clients to request Unauthenticated access.

Yes! This is exactly what I was concerned about.

Clients SHOULD be implemented to require user selection of the Unauthenticated Authentication Mechanism by means other than user input of an empty password. Clients SHOULD disallow an empty password input to a Name/Password Authentication user interface.

Urgh. Well, yes I agree 100% in principal.  They SHOULD do that. It's basic input validation. But how many of it actually even know about this curiosity though? The next line was the one I wanted to see.

Additionally, Servers SHOULD by default fail Unauthenticated Bind requests with a resultCode of unwillingToPerform.
By default, the server should not accept this! Perhaps we changed something in our build and accidentally enabled this? To rule this out, I built a brand new LDS instance on a fresh Windows Server 2012 R2 server. I fired up LDP.exe, and guess what?

res = ldap_simple_bind_s(ld, 'this is not a DN', <unavailable>); // v.3

Then, I had a thought, accompanied by a feeling of dread. AD LDS uses the same underlying engine as Active Directory. Could the same problem exist there?

res = ldap_simple_bind_s(ld, 'this is not a DN', <unavailable>); // v.3

Yep. :(

Mixed emotions

I've got mixed emotions at this point. I'm happy that we didn't do something wrong in our build, but also, part of me was wishing that we had. Trawling through the AD and LDS documentation made no mention of a setting to be able to turn this off. At this point we had over a hundred applications moving off the old Oracle LDAP server and onto LDS. Some of these were home grown, and could be fixed ourselves, others were written by 3rd parties, and getting code updates would be a lot more complicated.

At this point, I'd given up on lunch.

It is important to note, that this issue poses absolutely no risk to the security of your LDAP system. AD and LDS are secure and there is no risk of information disclosure (unless you deliberately grant rights to NT Authority\Anonymous logon). The security of your applications that unconditionally delegate responsibility for authentication to the LDAP directory however, is another question.

We logged a case with Microsoft, and after much deliberation about the intent of the RFC and whether it was the client or servers responsibility to fix this, we submitted a product change request, which is still under consideration.

The discussion we had centred around the fact that this issue stems from poorly coded applications. It's hard to disagree with this point. Especially since the RFC says that clients should not allow this to happen. But the RFC also says that the servers also should not allow this to happen. When it comes down to it, we have to consider which is easier to do? Find and update every bad application that was ever made, and keep an eye on every application to come in the future? That's going to be about as successful as herding cats.

Or do we prevent the problem at its source? Unauthenticated binds serve no useful purpose that I am aware of. The RFC mentions that it exists for trace purposes, and I doubt that many people even know such a thing exists. There is certainly no product I have ever come across that required unauthenticated binds turned on for it to work. The RFC states that it should be turned off by default, so we can consider that it isn't crucial to LDAP operations.

How wide spread is this problem?

There are only a handful of LDAP server vendors, but untold numbers of LDAP clients. I decided to investigate a few well-known LDAP providers, to determine what their support for disabling unauthenticated binds is.

ProductCan be disabledDisabled by default
Red Hat Directory Server Yes Yes
OpenLDAP Yes Yes
Novell eDirectory Yes No
Oracle/Sun Directory Server Yes Yes
Microsoft AD LDS/ADAM No No
Microsoft Active Directory No No

Red Hat Directory Server, Open LDAP and Oracle/Sun Directory all ship with unauthenticated binds disabled by default. As per the RFC. Novell has it enabled, but provide guidance on turning it off. Microsoft has the only product (in my admittedly limited research) that does not provide this option at all. Given how wide-spread the deployment of Active Directory is, this is really a concern.

It's a common enough problem that there are many publicly listed vulnerabilities in the CVE database. Keep in mind that these are only the ones that were publicly disclosed. There are no doubt many more not listed, or not yet even known. If well-established vendors such as VMWare and Apache can make these mistakes, it goes to show that anyone can. We're talking some serious issues here. Even the SSSD authentication module in Linux was vulnerable until 2010.

We have to ask the question, if unauthenticated binds are the source of so many critical security issues in other products, and they add little to no measurable value, why should that capability be turned on by default in any product? Why should that capability even exist?

So, who should fix it?

Discussion forums and bug reports are full of back-and-forthing between the developers saying that its a misconfiguration on the LDAP server side, and users being stuck with products like AD LDS that do not allow you to configure this, one way or another, begging the developers to fix it on the application side. The RFC compounds this problem by stating that it is both the client and server's responsibility to protect against the problem.

In my view, both parties have a responsibility here.

We can't reasonably expect every application developer to read every page of the LDAP RFCs. This is a counter-intuitive 'feature' hidden away in the depths of a protocol definition. One could argue that adequate testing would identify this, but that's assuming you are testing against a directory that has this setting turned on. You could also argue that applications should be checking their input values, and again you wouldn't be wrong. Ultimately, an application is responsible for its own security.

But server vendors have a responsibility to ship products in a secure configuration that protects the organizations using them. I'm a big believer in the fact that LDAP is NOT an authentication protocol (I've got a blog post coming about this), and yes technically, there are no security flaws in the LDAP server itself. However, we have to accept the reality, rightly or wrongly, that it's a very widely-used de facto authentication protocol. A default setting change here (or at minimum, the option to change the setting), protects the organizations who use these applications from easy to avoid cases of information disclosure, unauthorized access, and a potential raft of other serious security issues.

There is no discernible impact to turning off unauthenticated binds. If we were to see an LDAP v4 in the future, I would hope to see this confusing and unnecessary feature removed. The only better than that, would be if we stop using LDAP as an authentication protocol altogether. While that's a future that, at least for now, only exists in my dreams, Microsoft can and should do something now to provide the ability to turn off unauthenticated binds in AD and LDS, and set it to be off by default in a future version of the product. The world will be a better (safer) place.