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

Posted

技术标签:

【中文标题】当应用程序作为服务运行时,AcceptSecurityContext 失败【英文标题】:AcceptSecurityContext fails when application is running as a service 【发布时间】:2015-11-04 15:20:11 【问题描述】:

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

1.Client发送HTTP Get请求http://localhost:8082

2.Server 响应 WWW-Authenticate: Negotiate header。

3.Client发送Authorization header,包含如下数据:

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.Client发送授权头:

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 响应 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 上无法重现,这让我认为它与新的安全功能有关。

【问题讨论】:

【参考方案1】:

已经有一段时间了,但我想我在应用程序池的高级设置中将 Load User Profile 设置为 false 时看到了类似的问题。该设置是 IIS 7.0 的新设置。文档确实说 false 值对应于 Windows Server 2003 行为,但我记得不加载配置文件会以某种方式干扰 SSPI 子系统。你是对的,从那里报告了宝贵的小错误,我不得不跳过一些圈子来找出答案。

更新

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

另外,我记得一旦客户端在身份验证方面遇到问题,我们最终会追溯到服务器 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 描述中的 Remarks 部分

【讨论】:

感谢您的回复。最初我用 Tomcat + Waffle 过滤器重现了这个问题,后来决定在 C# 上创建一个小例子,使用自写的 HTTP 服务器,它只提取客户端标头并调用 SSPI。在这两种情况下,我都得到了相同的行为——当应用程序作为服务运行时,AcceptSecurityContext 失败。不幸的是,我不能使用 IIS 高级设置,因为最终我需要让它适用于 Tomcat。 关于更新。协商协议不一定使用 Kerberos,我认为在我的情况下,当我从同一服务器登录时使用 NTLM(当我测试其他工具时,它们给出了类似的结果并报告 NTLM)。如果我远程登录或尝试使用 IP 地址或将其作为控制台应用程序运行,我不会遇到任何问题。唯一不起作用的情况是将此应用程序作为服务运行。看起来我需要更改服务的某些限制或特权。 您的最后一次更新解决了我的问题。非常感谢!

以上是关于当应用程序作为服务运行时,AcceptSecurityContext 失败的主要内容,如果未能解决你的问题,请参考以下文章

当 XAMPP 作为服务运行时,页面无法连接 ODBC 服务器(在 Windows Server 2008 上)

无法访问显示组件 - 从 Windows 服务调用

作为服务运行时未找到 ODBC Lib

作为 Windows 服务托管时,远程 WMI 不起作用

作为启动 RDP 程序运行时如何停止初始形式最大化?

ffmpeg -hls_time选项在作为服务运行时无法正常工作