ldapsdk icon indicating copy to clipboard operation
ldapsdk copied to clipboard

Hashed user passwords not handled correctly during bind request

Open omallo opened this issue 8 years ago • 6 comments

I have just gone through this Spring tutorial which uses an embedded UnboundId LDAP server.

While playing around with the sample code, I noticed that doing a simple bind request on the LDAP server fails if the user password is stored as a hash on the server (binding works fine if the password is stored as plaintext).

While looking at the UnboundId sources, I found the following code in the InMemoryRequestHandler class which does the authentication for a simple bind request:

if (userEntry.hasAttributeValue("userPassword", bindPassword.getValue(),
    OctetStringMatchingRule.getInstance()))
{
   ...
}

From what I see, hashed user passwords are not handled correctly by the above code since the password passed in for authentication will always be a plaintext password which is compared directly with the hashed password stored on the server.

omallo avatar Jun 26 '17 22:06 omallo

See also this StackOverflow question from another person having the same problem.

omallo avatar Jun 26 '17 22:06 omallo

Hashing (or encoding in general) isn’t a standard LDAP feature. Although most directory servers implement support for it, not all of it do it in the same way or support the same encoding mechanisms.

To take the example included in the Stack Overflow post, it looks like “{SHA}9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684” uses an unsalted SHA-1 digest with a hexadecimal encoding of the resulting digest bytes. This specific encoding might work in whatever LDAP server is using that encoding, but it won’t work in the UnboundID Directory Server, OpenDS, OpenDJ, or DSEE because they all use base64 rather than hexadecimal encoding, and therefore they would all generate “{SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=” as their representation of the password “pass” encoded with an unsalted SHA-1 digest. If you use a salt, then things can get even more complicated: whether it should be fixed or random, how long it should be, whether it should be appended or prepended to the clear-text password, if/how the salt should be recorded in the encoded password, etc. There are also other considerations for different encoding schemes that relate to their various configuration options (for example, number of digest rounds).

There could also be a variety of other differences in encoding mechanisms, including options around the use of a salt (how long the salt is, whether it should be appended or prepended to the clear-text password, whether there is a fixed salt or if it varies per password, how the per-password salt is stored, etc.), whether the server applies multiple digest rounds,

It’s generally a very bad idea for clients to have any knowledge of password encoding. They should always provide passwords to the server in the clear, and let the server handle it in whatever way it deems appropriate. Having clients deal with encoded passwords is a very bad idea, for several reasons:

  • If clients encode passwords before storing them in the server, then the server can’t perform any validation for those passwords. This could allow clients to provide passwords that violate the intended security policy (e.g., passwords that are too weak, that have already been used by that user, etc.).

  • If clients encode passwords before storing them in the server, then they might also be tempted to retrieve the encoded password from the server and perform their own client-side validation in an attempt to authenticate users rather than performing a bind operation. This could allow clients to incorrectly authenticate users that the server thinks should not be permitted to bind (e.g., because the account has been administratively disabled, the account has been locked because of too many failed authentication attempts, the password has expired, etc.).

  • If some clients expect to deal directly with encoded passwords, then they can run into problems if there is a need to change the encoding scheme (for example, if you want to update to something stronger because SHA-1 is known to have significant weaknesses and is no longer recommended for use). In this case, you’d have to make sure that all clients get updated before you can start using the new scheme.

If all of your clients do send passwords in the clear, then the fact that the in-memory directory server just stores them without any encoding shouldn’t be an issue for those clients. However, it may still be an issue if you want to import LDIF data that includes encoded passwords. But because those encoded passwords could come from a variety of servers that use a variety of encodings, this presents a challenge.

About the only possible approach that we could take to address this would be to update the in-memory directory server to provide pluggable support for encoding passwords. The default would be

About the only possible approach that we could take would be to update the in-memory directory server to provide pluggable support for encoding passwords. The default implementation would probably continue to store passwords in the clear, but we could perhaps include one or more alternate implementations that handle other common schemes.

dirmgr avatar Jun 27 '17 16:06 dirmgr

@dirmgr thanks a lot for your rapid and also very elaborated and excellent feedback!

Just some thoughts from my side:

  • Indeed, there seems to be no real standard for the storage of hashed passwords in an LDAP server. The only thing I could find is this informational IETF document which limits itself to describing what is commonly implemented in some of the major LDAP servers. This document describes to some extend many of the things you mention above (how to encode digests, how to store salts along with passwords, ...) but since it's not a standard document, this is only of limited use as a base for an implementation which should guarantee at least some degree of interoperability among different LDAP servers.
  • I fully agree that clients should never deal with hashed passwords directly. Instead, when doing e.g. a bind request, they should always authenticate with their plaintext password (binding with a hashed password kind of defeats the whole purpose of hashing). This is also what e.g. Open LDAP does where you always bind with your plaintext password even if the password is hashed on the server.
  • The points you mention regarding the possibility of doing some kind of client side authentication based on hashed passwords or storing hashed passwords directly without the server knowing what the actual password is, are very interesting and I agree that the fact of having hashed passwords opens up this kind of possibilities even though such things should clearly be avoided.
  • I agree that having some kind of pluggable hashing support would be the only way to go. For the actual implementation, it would be good to follow the above IETF document as well as considering existing implementations from other LDAP servers. On the other hand, I have to say that I do not quite like implementing something like that just based on some informational document and some existing implementations. It would actually be interesting to know how often hashed passwords are actually used in production systems (unfortunately, I have no experience in that regard).

omallo avatar Jun 27 '17 21:06 omallo

For what it’s worth, RFC 3112 (LDAP Authentication Password Schema) does describe a syntax for storing encoded passwords with configuration information (like a salt). However, I don’t think that it’s widely used, the only standard schemes that it defines are MD5 and SHA1, and they are only used with a salt.

I’m in the process of creating a password encoder API that can be used with the in-memory directory server, and it will hopefully be able to address your needs.

dirmgr avatar Jun 27 '17 21:06 dirmgr

I have just committed a change (dd49c6761f36c1c2672bffd20570665f25948b32) to the LDAP SDK that provides basic support for encoded passwords to the in-memory directory server. The commit message provides some detail about the change, but if you want to use the hex-encoded, unsalted SHA-1 encoding mechanism described in the Stack Overflow post, you would configure it as follows:

final InMemoryDirectoryServerConfig config =
     new InMemoryDirectoryServerConfig("dc=testing,dc=com");
config.setPasswordEncoders(
     new UnsaltedMessageDigestInMemoryPasswordEncoder("{SHA}",
          HexPasswordEncoderOutputFormatter.getLowercaseInstance(),
          MessageDigest.getInstance("SHA-1")));

This will ensure that any new clear-text passwords provided to the server (in an LDIF import, or in an LDAP add request, modify request, or password modify extended request) will be encoded using that SHA-1 mechanism, and that a clear-text password included in a bind request will be properly matched against the encoded representation.

dirmgr avatar Jul 10 '17 16:07 dirmgr

@dirmgr , thanks a lot for the change and for the information of how to use it!

Unfortunately (so to speak ;-) ), I am currently on a longer vacation until the end of August and I will only be able to give it a try by then. I will provide you some feedback then.

omallo avatar Jul 21 '17 20:07 omallo