none
System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials accepts old password

    질문

  • Hi, I'm trying to implement a custom Membership provider for an app using S.DS.AM and I'm facing a problem where if a user changes it's password (via the UserPrincipal.ChangePassword or any other mean like AD mmc plugin), the old password of the user is still being accepted by the ValidateCredentials method of the PrincipalContext class.

    I've done some packet capture (with WireShark) to see the Kerberos packets on the wire and what I see when ValidateCredentials is called with the previous password is that there is a error_code: KRB5KDC_ERR_PREAUTH_FAILED (24).

    It seems like the validation should fail, yet the ValidateCredentials method returns true. Anybody got that problem ?

    The thing gets worse if I try to use the PrincipalContext in a singleton pattern instead of using a new one for each validation : 
    if a user logs in with it's previous password (which will work), all other request to ValidateCredentian will then fail with a LdapException : "Ldap server unavailable"

    Any suggestions ?

    Thanks !

    Vincent
    2009년 4월 6일 월요일 오후 2:55

답변

  • OK, I just dug into the ValidateCredentials code using Reflector to see what it was attempting to execute. It's a giant catch-all for all sorts of different scenarios, like validating against local principals which hit the SAM database instead of AD, and also account for different network LDAP options and OS versions, etc. In short, any app's scenario probably won't hit more than 25% of the code in there.

    However, what counts is that ContextOption does *not* ensure the use of Kerberos only. It turns out that under certain situations (like if you are specifying AD rather than local, and you have a sufficiently up to date server), the code chooses to do Negotiate no matter what. In that sense, specifying Sealing probably means that it will use Kerberos, but not necessarily exclusively. The flag that really matters is burried several layers under that. Under the covers, this method ends up establishing an LdapConnection, setting the network Credentials for the connection, setting that AuthType (the actual flag that matters!), and finally calling the Bind() method. The LdapConnection.Bind() method establishes an authenticated connection to one of the AD servers using the specified credentials. The problem is that when PrincipalContext.ValidateCredentials sets up this call (in your scenario), it *always* sets the AuthType = Negotiate. In this case, Kerberos does in fact get used, and ends up failing, but the system falls back to NTLM.

    One way of solving the issue is to forget about PrincipalContext.ValidateCredentials() and do the validation yourself manually. It's actually not that hard and only includes two lines of code. First, create a new instance of LdapConnection and use the constructor that allows you to specify the LDAP ID, the NetworkCredentials (an object that holds the user name and password), and the AuthType (which you can then specifically set to Kerberos only). The second line of code simply calls the Bind() method. If Bind() fails, it will throw an exception that can include some "login failed" exception. Also, don't forget that LdapConnection is a Disposable class and must be properly Disposed after use.
    -Rob Teixeira
    • 답변으로 표시됨 Demvin 2009년 4월 8일 수요일 오후 1:21
    2009년 4월 7일 화요일 오후 6:28

모든 응답

  • OK, there are a few things in play here. You can use LDAP to query the directory, but for authentication, it can use either NTLM or Kerberos, or (by defualt) Negotiate (which tries both in case one fails or isn't present - not all windows machines are capable of using Kerberos depending on the OS version, for example).

    One of the "features" in authentication is that when the password is administratively changed, the old password is still available for some specified amount of time. This is because you can have cached credentials on shares, services, other machines, etc. that would all break immediately if the old credentials just stopped working. Eventually, the old password does phase out though. The amount of time is configurable, from what I remember, though I can't remember if it's a local registry setting or a network admin policy.

    Also, if I remember correctly, you don't get this behavior if you use strictly Kerberos only, and not Negotiate. I seem to remember reading that the issue, i mean feature, of the old password persisting for some time is not implemented in pure Kerberos authentication.
    -Rob Teixeira
    2009년 4월 6일 월요일 오후 9:05
  • Hello Rob,

    Thank you for your input.

    I also thought this could be a NTLM issue (since Server 2003 SP1, old password are still valid for 60 minutes by default), but since this does not apply for Kerberos and I am quite positive from packet inspection that Kerberos is being used here and not NTLM, I'm still puzzled.

    My next step will be to disable Negotiate and force kerberos.

    Thanks again.  Any other input would be apreciated.
    2009년 4월 7일 화요일 오전 2:18
  • So I have replaced ContextOption.Negotiate for ContextOption.Sealing just to be sure Kerberos is being used, and I can see the same kind of Kerberos packets on the wire.  One again the Kerberos authentication seems to fail, yet the ValidateCredentials method returns true.

    I'd really like to hear from MS about this problem, as far as I am concerned it could be a security issue since this behavior is only documented for NTLM auth.

    Thanks !

    2009년 4월 7일 화요일 오후 1:47
  • OK, I just dug into the ValidateCredentials code using Reflector to see what it was attempting to execute. It's a giant catch-all for all sorts of different scenarios, like validating against local principals which hit the SAM database instead of AD, and also account for different network LDAP options and OS versions, etc. In short, any app's scenario probably won't hit more than 25% of the code in there.

    However, what counts is that ContextOption does *not* ensure the use of Kerberos only. It turns out that under certain situations (like if you are specifying AD rather than local, and you have a sufficiently up to date server), the code chooses to do Negotiate no matter what. In that sense, specifying Sealing probably means that it will use Kerberos, but not necessarily exclusively. The flag that really matters is burried several layers under that. Under the covers, this method ends up establishing an LdapConnection, setting the network Credentials for the connection, setting that AuthType (the actual flag that matters!), and finally calling the Bind() method. The LdapConnection.Bind() method establishes an authenticated connection to one of the AD servers using the specified credentials. The problem is that when PrincipalContext.ValidateCredentials sets up this call (in your scenario), it *always* sets the AuthType = Negotiate. In this case, Kerberos does in fact get used, and ends up failing, but the system falls back to NTLM.

    One way of solving the issue is to forget about PrincipalContext.ValidateCredentials() and do the validation yourself manually. It's actually not that hard and only includes two lines of code. First, create a new instance of LdapConnection and use the constructor that allows you to specify the LDAP ID, the NetworkCredentials (an object that holds the user name and password), and the AuthType (which you can then specifically set to Kerberos only). The second line of code simply calls the Bind() method. If Bind() fails, it will throw an exception that can include some "login failed" exception. Also, don't forget that LdapConnection is a Disposable class and must be properly Disposed after use.
    -Rob Teixeira
    • 답변으로 표시됨 Demvin 2009년 4월 8일 수요일 오후 1:21
    2009년 4월 7일 화요일 오후 6:28
  • Rob, you were absolutely right.

    I implemented the credential verification with a LdapConnection, forcing Kerberos and everything is fine.

    Also from what I could observe, if you really enter a bad password, ValidateCredential consumes 2 bad password attempt : one for Kerberos and one with NTLM, thus reaching the bad password attempt limit twice as fast...

    Hope this gets fixed, because I can see the AccountManagement being quite useful in the future.

    Thanks a lot !

    Vincent Demers
    2009년 4월 8일 수요일 오후 1:21
  • Another way to trick that (in my case, we don't have certificates to apply Kerberos) is changing the password two times in a row - like there was a initial password being set before the actual new password - so that the old one is completely eliminated. Not sure if this is a good practice but it works. This is a old post but I thought I would share my testing with this work-around... Hope it is useful for someone.
    2011년 4월 13일 수요일 오전 1:37