当应用程序作为服务运行时,AcceptSecurityContext失败

人气:759 发布:2022-10-16 标签: security c# windows-authentication ntlm sspi

问题描述

我有一个简单的HTTP服务器,它使用协商协议对客户端进行身份验证。它使用SSPI调用来获取服务器凭据并建立安全上下文。服务器在域中,并代表域用户运行。一切正常,如果我在控制台模式下启动服务器,我会得到HTTP200响应。但是,当我将其作为服务运行时,我收到了SEC_E_INVALID_HANDLE错误。以下是我在控制台模式下启动它时发生的情况:

1.客户端发送HTTP GET请求http://localhost:8082

2.服务器使用WWW-AUTHENTICATE:Neighate标头进行响应。

3.客户端发送授权标头,包含以下数据:

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.服务器响应HTTP 401错误并协商标头提示继续:

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.客户端发送授权标头:

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.服务器使用HTTP 200和协商标头进行响应:

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ã©....   
现在,如果我将应用程序作为服务运行,我将得到几乎相同的响应,但AcceptSecurityContext将在第6步失败,并返回SEC_E_INVALID_HANDLE错误。我想知道,如果我运行相同的应用程序并将相同的用户指定为服务登录标识,它为什么会失败?这可能与会话0隔离有关吗?此外,有没有更好的解决方法,我在事件查看器中看不到任何错误消息,无效句柄错误也没有太多说明丢失的内容。

以下是要进行身份验证的服务器代码:

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);
    }
}
在这两种情况下,我都试图使用相同的域用户从运行服务器的同一台计算机上访问网页。此外,我使用相同的域用户来运行控制台应用程序和Windows服务。该问题在Windows Server 2003上不可重现,这使我认为它与新的安全功能有关。

推荐答案

已经有一段时间了,但我认为当加载用户配置文件在应用程序池的高级设置中设置为False时,我看到了类似的问题。该设置是IIS 7.0中的新功能。文档确实说FALSE值对应于Windows Server2003行为,但我记得不加载配置文件会以某种方式干扰SSPI子系统。你是对的,从那里报告的错误很少,我不得不跳过一些圈子才能从中梳理出答案。

更新

这些函数最终依赖于Kerberos客户端实现,其中很大一部分驻留在lsass.exe进程中。这里有一个很好的关于整个子系统故障排除的链接:http://blogs.msdn.com/b/canberrapfe/archive/2012/01/02/kerberos-troubleshooting.aspx

此外,我还记得,一旦客户端出现身份验证问题,我们最终会将其追溯到运行在Server 2008上的客户端(或类似的情况,重要的是版本高于2003)与运行在Server 2003上的辅助域控制器之间的某些协议不匹配。没有进一步跟踪,客户端仅升级了DC。

最终更新

好的,我能够重现这个问题,而且我实际上能够使它工作。由于第一次调用AcceptSecurityContext返回SEC_I_CONTINUE_NEEDED,您的Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger)方法至少被调用两次。每次Authenticate通过调用AcquireCredentialsHandle函数获取新的凭据句柄。这对我在控制台和以LocalSystem身份运行的内部服务有效,但如果服务是在域帐户下运行的,就像你说的那样。

因此,我将AcquireCredentialsHandle调用从Authenticate中提取出来,以便我可以获取它一次,然后在后续传入调用中重复使用。这为我修复了服务。

请注意,您应该使用FreeCredentialsHandle调用释放凭据句柄,否则您可能会在lsass.exe中获得内存泄漏,这将需要您重新启动服务器。请参阅AcquireCredentialsHandle的MSDN说明中的备注部分

969