locked
AcceptSecurityContext (NTLM) returns SEC_E_LOGON_DENIED RRS feed

  • Question

  • I'm working on a system that provides a consistent set of security identities for users across our platforms, including systems that have no concept of a Windows domain. As such, we have a main service that lists identities, and then links to those identities based on other systems. I'm trying to add a system that will allow Windows domain accounts to be linked into this system. The concept I've come up with is to use the account SID as a key to link to the main identity records.

    I've been able to produce an explicit logon-based system. Credentials are passed into LogonUserW with LOGON32_LOGON_NETWORK, and I am able to obtain a TOKEN_USER structure from the resulting token that provides a SID for mapping. I pass in a test domain account and it works perfectly.

    My next goal is to produce a system that allows for applications to implicitly pass on the identity of the currently logged-in user, and to that end I've been trying to set up an SSPI negotiation using the NTLM package. My objective is to get a context on the server side whose associated token can provide a SID for use in the mapping, as with the credentials-based approach.

    I've created an application to act as both the client and the server to prove the concept, but when it comes to the final AcceptSecurityContext call, the returned status code is SEC_E_LOGON_DENIED.

    Here is the proof-of-concept code:

    #include <Windows.h>
    
    #define SECURITY_WIN32
    
    #include <security.h>
    #include <schannel.h>
    
    int main()
    {
    	SecPkgInfoW *pkgInfo;
    
    	CredHandle clientCredHandle;
    	TimeStamp clientCredHandleExpiry;
    	CtxtHandle clientContext;
    	ULONG clientContextAttributes;
    	TimeStamp clientContextExpiry;
    
    	SecBufferDesc clientToServerBuffer;
    
    	CredHandle serverCredHandle;
    	TimeStamp serverCredHandleExpiry;
    	CtxtHandle serverContext;
    	ULONG serverContextAttributes;
    	TimeStamp serverContextExpiry;
    
    	SecBufferDesc serverToClientBuffer;
    
    	SECURITY_STATUS result;
    
    	result = QuerySecurityPackageInfoW(L"NTLM", &pkgInfo);
    	
    	result = AcquireCredentialsHandleW(NULL, L"NTLM", SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, &clientCredHandle, &clientCredHandleExpiry);
    
    	memset(&clientToServerBuffer, 0, sizeof(clientToServerBuffer));
    
    	SecBuffer clientTokenBuffer;
    
    	clientTokenBuffer.BufferType = SECBUFFER_TOKEN;
    	clientTokenBuffer.cbBuffer = pkgInfo->cbMaxToken;
    	clientTokenBuffer.pvBuffer = malloc(clientTokenBuffer.cbBuffer);
    
    	clientToServerBuffer.ulVersion = SECBUFFER_VERSION;
    	clientToServerBuffer.cBuffers = 1;
    	clientToServerBuffer.pBuffers = &clientTokenBuffer;
    
    	result = InitializeSecurityContextW(&clientCredHandle, NULL, L"test/testInstance/testService", ISC_REQ_CONNECTION | ISC_REQ_ALLOCATE_MEMORY, 0, SECURITY_NETWORK_DREP, NULL, 0, &clientContext, &clientToServerBuffer, &clientContextAttributes, &clientContextExpiry);
    
    	result = AcquireCredentialsHandleW(NULL, L"NTLM", SECPKG_CRED_INBOUND, NULL, NULL, NULL, NULL, &serverCredHandle, &serverCredHandleExpiry);
    
    	memset(&serverToClientBuffer, 0, sizeof(serverToClientBuffer));
    
    	SecBuffer serverTokenBuffer;
    
    	serverTokenBuffer.BufferType = SECBUFFER_TOKEN;
    	serverTokenBuffer.cbBuffer = pkgInfo->cbMaxToken;
    	serverTokenBuffer.pvBuffer = malloc(clientTokenBuffer.cbBuffer);
    
    	serverToClientBuffer.ulVersion = SECBUFFER_VERSION;
    	serverToClientBuffer.cBuffers = 1;
    	serverToClientBuffer.pBuffers = &serverTokenBuffer;
    
    	result = AcceptSecurityContext(&serverCredHandle, NULL, &clientToServerBuffer, ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_CONNECTION, SECURITY_NETWORK_DREP, &serverContext, &serverToClientBuffer, &serverContextAttributes, &serverContextExpiry);
    
    	clientTokenBuffer.cbBuffer = pkgInfo->cbMaxToken;
    
    	result = InitializeSecurityContextW(&clientCredHandle, &clientContext, L"test/testInstance/testService", ISC_REQ_CONNECTION | ISC_REQ_ALLOCATE_MEMORY, 0, SECURITY_NETWORK_DREP, &serverToClientBuffer, 0, &clientContext, &clientToServerBuffer, &clientContextAttributes, &clientContextExpiry);
    
    	serverTokenBuffer.cbBuffer = pkgInfo->cbMaxToken;
    
    	result = AcceptSecurityContext(&serverCredHandle, &serverContext, &clientToServerBuffer, ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_CONNECTION, SECURITY_NETWORK_DREP, &serverContext, &serverToClientBuffer, &serverContextAttributes, &serverContextExpiry);
    }
    

    My user account is a regular domain account that has been added to my development machine's local Administrators group.

    I am at a loss to understand why this authentication is failing. As far as I can see, the actual SSPI back-and-forth is working perfectly, and the final InitializeSecurityContext call on the client side returns SEC_E_OK, but provides a buffer for me to pass back to the server, and when the "server" side processes that, two lines down, I get the aforementioned error.

    What am I doing wrong here? Is there some sort of intent I need to declare somewhere, to tell the system *why* I am doing the authentication? I imagine that this type of negotiation is exactly what occurs when I browse to an intranet web server configured to use NTLM pass-through authentication, but my account has no difficulty doing that. Is there a privilege that server code needs to hold in order to do this type of authentication?

    Many thanks,

    Jonathan Gilbert

    Friday, August 2, 2013 5:14 PM

Answers

  • I have found an answer! While trying to find examples of existing client/server implementations, I came across the Apache module mod_auth_sspi, which provides both client and server implementations of SSPI. One by one, I compared by parameters with those it passed in, and the one that made a difference was the pszTargetName. This is described in the documentation as a Service Principal Name, so I had looked that up and found this:

    http://technet.microsoft.com/en-us/library/cc961723.aspx

    According to that document, the basic structure of an SPN is "serviceType/instanceName/serviceName". Apache's mod_auth_sspi, however, simply passes in the name of the SSPI package, which in this case would be the string "NTLM". Now, I'm pretty sure this is actually not correct, as it is supposed to identify the service it's connecting to, but it works, and I also tried a DNS host name and that worked too. I haven't tested this, but I believe the general rule is that pszTargetName must look like a DNS host name and must not contain '/' characters. I'm sure there's more to it, but that gets it working for me and gets me moving forward again.

    Hopefully that helps someone else out there!

    Many thanks :-)

    Friday, August 2, 2013 6:25 PM

All replies

  • I have found an answer! While trying to find examples of existing client/server implementations, I came across the Apache module mod_auth_sspi, which provides both client and server implementations of SSPI. One by one, I compared by parameters with those it passed in, and the one that made a difference was the pszTargetName. This is described in the documentation as a Service Principal Name, so I had looked that up and found this:

    http://technet.microsoft.com/en-us/library/cc961723.aspx

    According to that document, the basic structure of an SPN is "serviceType/instanceName/serviceName". Apache's mod_auth_sspi, however, simply passes in the name of the SSPI package, which in this case would be the string "NTLM". Now, I'm pretty sure this is actually not correct, as it is supposed to identify the service it's connecting to, but it works, and I also tried a DNS host name and that worked too. I haven't tested this, but I believe the general rule is that pszTargetName must look like a DNS host name and must not contain '/' characters. I'm sure there's more to it, but that gets it working for me and gets me moving forward again.

    Hopefully that helps someone else out there!

    Many thanks :-)

    Friday, August 2, 2013 6:25 PM
  • 1. Firstly I am having the same issue. And I don't want an answer "don't use NTLM", I want an answer as to why this happens.

    2. your answer makes no real sense to me.

    3. This looks like a bug in the system. A logon may fail but then it should fail both ends, not just one.

    4. The logon succeeds with the same client and server code on my side when logged in from localhost and I think from the network too. So I think it is failing when it is meant to be failing.

    The issue for me is not the server getting the invalid logon response but the client getting the SEC_E_OK when the login has failed.

    Tuesday, July 18, 2017 4:15 PM
  • We decided that the server is correct, it has accepted logons where we expected it to, and rejected them when we expected that too.

    The client side is incorrect.

    Our workaround, as development time costs money, is to simply have the server inform the client that logon failed and then close the connection.

    I guess this means that our server may well also send the client a message that the logon was successful, as it cannot rely on the SEC_E_OK result.

    Tuesday, July 18, 2017 4:36 PM