Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AcceptSecurityContext fails when application is running as a service

I have a simple HTTP server that authenticates clients with Negotiate protocol. It uses SSPI calls to acquire server credentials and establish security context. The server is in domain and is running on behalf of the domain user. Everything works fine and I'm getting HTTP 200 response if I start the server in console mode. However when I'm running it as a service I'm getting SEC_E_INVALID_HANDLE error. Here is what happens when I start it in the console mode:

1.Client sends HTTP Get request http://localhost:8082

2.Server responds with WWW-Authenticate: Negotiate header.

3.Client sends Authorization header and includes the following data:

60 73 06 06 2B 06 01 05 05 02 A0 69 30 67 A0 30  `s..+..... i0g 0
30 2E 06 0A 2B 06 01 04 01 82 37 02 02 0A 06 09  0...+....7.....
2A 86 48 82 F7 12 01 02 02 06 09 2A 86 48 86 F7  *H÷......*H÷
12 01 02 02 06 0A 2B 06 01 04 01 82 37 02 02 1E  ......+....7...
A2 33 04 31 4E 54 4C 4D 53 53 50 00 01 00 00 00  ¢3.1NTLMSSP.....
97 B2 08 E2 04 00 04 00 2D 00 00 00 05 00 05 00  ².â....-.......
28 00 00 00 06 01 B1 1D 00 00 00 0F 50 41 43 45  (.....±.....PACE
4D 42 4C 41 48                                   MBLAH             

4.Server responds with HTTP 401 error and negotiate header prompting to continue:

A1 81 CE 30 81 CB A0 03 0A 01 01 A1 0C 06 0A 2B  ¡Î0Ë ....¡...+
06 01 04 01 82 37 02 02 0A A2 81 B5 04 81 B2 4E  ....7...¢µ.²N
54 4C 4D 53 53 50 00 02 00 00 00 08 00 08 00 38  TLMSSP.........8
00 00 00 15 C2 89 E2 B0 3B BE 20 45 33 FD 92 80  ....Ââ°;¾ E3ý
04 E7 01 00 00 00 00 72 00 72 00 40 00 00 00 06  .ç.....r.r.@....
01 B1 1D 00 00 00 0F 42 00 4C 00 41 00 48 00 02  .±.....B.L.A.H..
00 08 00 42 00 4C 00 41 00 48 00 01 00 0A 00 50  ...B.L.A.H.....P
00 41 00 43 00 45 00 4D 00 04 00 10 00 62 00 6C  .A.C.E.M.....b.l
00 61 00 68 00 2E 00 63 00 6F 00 6D 00 03 00 1C  .a.h...c.o.m....
00 50 00 61 00 63 00 65 00 6D 00 2E 00 62 00 6C  .P.a.c.e.m...b.l
00 61 00 68 00 2E 00 63 00 6F 00 6D 00 05 00 10  .a.h...c.o.m....
00 62 00 6C 00 61 00 68 00 2E 00 63 00 6F 00 6D  .b.l.a.h...c.o.m
00 07 00 08 00 5D B3 C5 9A 0F 17 D1 01 00 00 00  .....]³Å..Ñ....
00                                               .             

5.Client sends Authorization header:

A1 77 30 75 A0 03 0A 01 01 A2 5A 04 58 4E 54 4C  ¡w0u ....¢Z.XNTL
4D 53 53 50 00 03 00 00 00 00 00 00 00 58 00 00  MSSP.........X..
00 00 00 00 00 58 00 00 00 00 00 00 00 58 00 00  .....X.......X..
00 00 00 00 00 58 00 00 00 00 00 00 00 58 00 00  .....X.......X..
00 00 00 00 00 58 00 00 00 15 C2 88 E2 06 01 B1  .....X....Ââ..±
1D 00 00 00 0F C0 BD 0C 5B F5 F9 35 FE 78 6D 08  .....À½.[õù5þxm.
BF 7B D9 CC E3 A3 12 04 10 01 00 00 00 F5 17 A7  ¿{ÙÌã£.......õ.§
50 2D 22 9A 84 00 00 00 00                       P-"....     

6.Server responds with HTTP 200 and negotiate header:

A1 1B 30 19 A0 03 0A 01 00 A3 12 04 10 01 00 00  ¡.0. ....£......
00 43 87 E0 88 C1 36 E3 A9 00 00 00 00           .CàÁ6ã©....   

Now if I run the application as a service I will get almost identical responses, but AcceptSecurityContext will fail on step #6 and return SEC_E_INVALID_HANDLE error. I wonder why would it fail if I run the same application and specify the same user as a service logon identity? Could it be somehow related to session 0 isolation? Also is there a way to troubleshoot it better, I don't see any error messages in the event viewer and the invalid handle error doesn't say much about what is missing.

Here is the server code to authenticate:

public static WinAuthResult Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger)
{
    if (clientTokenBytes == null || clientTokenBytes.Length == 0)
    {
        ClearContext(clientId);
        throw new Win32Exception(Secur32.SEC_E_INVALID_TOKEN);
    }

    var serverCredExpiry = new Secur32.SECURITY_INTEGER();
    var serverCredHandle = new Secur32.SecHandle();
    var acquireResult = Secur32.AcquireCredentialsHandle(null, securityPackage, Secur32.SECPKG_CRED_INBOUND, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, out serverCredHandle, out serverCredExpiry);
    if (acquireResult != Secur32.SEC_E_OK)
        throw new Win32Exception(acquireResult);

    var oldContextExists = contexts.ContainsKey(clientId);
    var oldContextHandle = GetContextHandle(clientId);
    var newContextHandle = new Secur32.SecHandle();
    var clientToken = new Secur32.SecBufferDesc(clientTokenBytes);
    var outputToken = new Secur32.SecBufferDesc(61440);
    var contextAttributes = (uint)0;
    var outputCresExpiry = new Secur32.SECURITY_INTEGER();

    int acceptResult;
    if (!oldContextExists)
    {
        acceptResult = Secur32.AcceptSecurityContext(
            ref serverCredHandle,
            IntPtr.Zero,
            ref clientToken,
            0,
            Secur32.SECURITY_NATIVE_DREP,
            ref newContextHandle,
            ref outputToken,
            out contextAttributes,
            out outputCresExpiry);
    }
    else
    {
        acceptResult = Secur32.AcceptSecurityContext(
            ref serverCredHandle,
            ref oldContextHandle,
            ref clientToken,
            0,
            Secur32.SECURITY_NATIVE_DREP,
            ref newContextHandle,
            ref outputToken,
            out contextAttributes,
            out outputCresExpiry);
    }

    if (acceptResult == Secur32.SEC_E_OK)
    {
        ClearContext(clientId);
        return new WinAuthResult(false, outputToken.GetSecBufferByteArray());
    }
    else if (acceptResult == Secur32.SEC_I_CONTINUE_NEEDED)
    {
        contexts[clientId] = newContextHandle;
        return new WinAuthResult(true, outputToken.GetSecBufferByteArray());
    }
    else
    {
        ClearContext(clientId);
        throw new Win32Exception(acceptResult);
    }
}

In both cases I'm trying to access the web page from the same machine where the server is running and with the same domain user. Also I'm using the same domain user to run console app and windows service. The issue is not reproducible on Windows Server 2003 which makes me think that it is something related to new security features.

like image 416
username Avatar asked Nov 04 '15 15:11

username


Video Answer


1 Answers

It's been a while but I think I saw similar issues when Load User Profile was set to false in the Advanced Settings of the Application Pool. That setting is new to IIS 7.0. The documentation does say that the false value corresponds to the Windows Server 2003 behavior but I recall that not loading the profile interferes somehow with the SSPI subsystem. And you're right, there is precious little error reporting from there, I had to jump through some hoops to tease the answer out of it.

enter image description here

UPDATE

Those functions ultimately rely on Kerberos client implementation of which a large part resides in the lsass.exe process. Here's a good link regarding troubleshooting the whole subsystem: http://blogs.msdn.com/b/canberrapfe/archive/2012/01/02/kerberos-troubleshooting.aspx

Also, I remember that once a client had issues around authentication that we ultimately traced down to some protocol mismatch between a client running on Server 2008 (or something like that, important fact is the version was higher than 2003) connecting to a secondary domain controller running on Server 2003. Didn't trace it further, the client just upgraded the DC.

FINAL UPDATE

OK, I was able to reproduce the problem and I was actually able to make it work. Your Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger) method gets called at least twice due to first call to AcceptSecurityContext returning SEC_I_CONTINUE_NEEDED. Every time Authenticate gets a new credentials handle by calling AcquireCredentialsHandle function. That worked for me in console and inside service running as LocalSystem, but not if the service was running under domain account, just like you said.

So, I pulled the AcquireCredentialsHandle call out of Authenticate so that I could obtain it once and then reuse for subsequent incoming calls. That fixed the service for me.

On a related note, you should free the credentials handle using FreeCredentialsHandle call, otherwise you could get a memory leak in lsass.exe which would require you to restart the server. See Remarks section in the MSDN description of AcquireCredentialsHandle

like image 63
Michael Domashchenko Avatar answered Oct 23 '22 09:10

Michael Domashchenko