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

时间:2015-11-04 15:20:11

标签: c# security ntlm sspi iwa

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

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

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

3.Client发送授权标头并包含以下数据:

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响应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ã©....   

现在,如果我将应用程序作为服务运行,我将获得几乎相同的响应,但是在步骤#6中AcceptSecurityContext将失败并返回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 个答案:

答案 0 :(得分:2)

已经有一段时间但我认为在应用程序池的高级设置中加载用户配置文件设置为 false 时,我发现了类似的问题。该设置是IIS 7.0的新增功能。文档确实说 false 值对应于Windows Server 2003的行为,但我记得没有加载配置文件会以某种方式干扰SSPI子系统。而且你是对的,那里有很少的错误报告,我不得不跳过一些箍来挑逗它的答案。

enter image description here

<强>更新

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

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

最终更新

好的,我能够重现这个问题,我实际上能够让它发挥作用。由于首次调用Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger)返回AcceptSecurityContext,您的SEC_I_CONTINUE_NEEDED方法至少会被调用两次。每次Authenticate通过调用AcquireCredentialsHandle函数获取新的凭据句柄。这在我作为LocalSystem运行的控制台和内部服务中起作用,但如果服务在域帐户下运行则不行,就像你说的那样。

所以,我从AcquireCredentialsHandle拨出了Authenticate电话,这样我就可以获得一次,然后重复用于后续来电。这为我修好了服务。

在相关说明中,您应该使用FreeCredentialsHandle调用释放凭据句柄,否则您可能会在lsass.exe中出现内存泄漏,这需要您重新启动服务器。请参阅AcquireCredentialsHandle

的MSDN说明中的备注部分