从 IIS 应用程序打开命名管道通道时 WCF 访问被拒绝

WCF Access is Denied when opening named pipe channel from IIS application

我们有一些遗留的 Web 应用程序代码,我们正在更新这些代码并将其移植到 .NET 4.0 运行时中。

代码在 class 库中,并使用 WCF 连接到命名管道端点。

当我从控制台应用程序启动连接时,一切正常。

当我从 Web 应用程序启动连接时,收到异常:

Access is denied
Server stack trace:
  at System.ServiceModel.Channels.AppContainerInfo.GetCurrentProcessToken()
  at System.ServiceModel.Channels.AppContainerInfo.RunningInAppContainer()
  at System.ServiceModel.Channels.AppContainerInfo.get_IsRunningInAppContainer()
  at System.ServiceModel.Channels.PipeSharedMemory.BuildPipeName(String pipeGuid)
  at System.ServiceModel.Channels.PipeSharedMemory.get_PipeName()
  at System.ServiceModel.Channels.PipeConnectionInitiator.GetPipeName(Uri uri, IPipeTransportFact… Object[] , Object[] )
  at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
  at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
  at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
  at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
  at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

错误起源于托管代码和非托管代码之间的边界,其中调用了 advapi32.dll:

[SecurityCritical]
private static SafeCloseHandle GetCurrentProcessToken()
{
  SafeCloseHandle TokenHandle = (SafeCloseHandle) null;
  if (!UnsafeNativeMethods.OpenProcessToken(UnsafeNativeMethods.GetCurrentProcess(), TokenAccessLevels.Query, out TokenHandle))
    throw System.ServiceModel.FxTrace.Exception.AsError((Exception) new Win32Exception(Marshal.GetLastWin32Error()));
  return TokenHandle;
}

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr ProcessHandle, TokenAccessLevels DesiredAccess, out SafeCloseHandle TokenHandle);

网络上的各种主题建议删除元素或设置 impersonate="false"

<system.web>
  <identity impersonate="true"/>
</system.web>

确实,这可以解决我的问题。但是,我不确定这可能会对应用程序 (SharePoint 2016) 产生什么副作用,因此我不愿意简单地删除此属性。

SecurityCritical 属性给了我一些提示,这可能与.NET 2.0 和.NET 4.0 之间CAS 模型的变化有关。代码已安装到 GAC 中,因此它应该 运行 已经完全信任,但我还是试了一下。

我也试过在方法中添加 [SecuritySafeCritical] 和调用 IChannel.Open() 的 class 无济于事。

我还尝试在程序集中添加 [assembly: SecurityRules(SecurityRuleSet.Level1)],因为这应该锁定在 .NET Framework 2.0 安全规则中。

我正在寻找任何其他见解和其他方法来尝试解决此问题。

与其他 Stack post: 有一些相似之处,只是没有发生显式模拟,所以我不确定修复是否适用。

另外请注意,当我尝试调用 System.Diagnostics.Process.GetCurrentProcess() 时,会抛出相同的错误。尝试获取当前执行进程的句柄时也会出现该错误。

我得出的结论是,此问题与 .NET 4.0 中 System.ServiceModel 的内部结构有关。

最初,我认为这可能与 Server 2016 UAC 或 .NET 4.0/IIS 10 Web 应用程序运行时设置有关(例如 .NET 2.0 与 .NET 4.0 CAS 模型)。我在 .NET 3.5 中创建了一个简单的 Web 应用程序并尝试调用 Process.GetCurrentProcess().Handle。我 运行 在新服务器中使用它,但它因同样的 "Access is denied" 错误而失败。

我将其放入旧服务器(Windows Server 2008 R2、.NET 3.5)并 运行 它在那里期望它能正常工作并且瞧瞧,它也失败了。所以我浏览了 3.5 中 System.ServiceModel 的源代码,发现没有 AppContainerInfo,因此很可能 3.5 代码根本没有进行相同的 Win32 API 级别调用。

我的结论是我们之前没有遇到这个错误,因为旧的 3.0 库不需要从 advapi32.dll 调用 APIs 或者有一些其他机制来创建管道名称。

确实,这是 3.0 中 PipeConnectionInitiator.GetPipeName 的前几行实现":

internal static string GetPipeName(Uri uri)
{
  string[] strArray = new string[3]
  {
    "+",
    uri.Host,
    "*"
  };
  bool[] flagArray = new bool[2]{ true, false };
  for (int index1 = 0; index1 < strArray.Length; ++index1)
  {
    for (int index2 = 0; index2 < flagArray.Length; ++index2)
    {

这是 4.0 中的前几行:

internal static string GetPipeName(Uri uri, IPipeTransportFactorySettings transportFactorySettings)
{
  AppContainerInfo appContainerInfo = PipeConnectionInitiator.GetAppContainerInfo(transportFactorySettings);
  string[] strArray = new string[3]
  {
    "+",
    uri.Host,
    "*"
  };
  bool[] flagArray = new bool[2]{ true, false };
  string str1 = string.Empty;
  string str2 = (string) null;
  for (int index1 = 0; index1 < strArray.Length; ++index1)
  {
    for (int index2 = 0; index2 < flagArray.Length; ++index2)
    {
      if (appContainerInfo == null || !flagArray[index2])

因此 4.0 实现需要访问权限才能执行 OpenProcessToken

如果代码充分隔离,一个选项是使用程序集绑定重定向:

<runtime>
  <assemblyBinding>
    <dependentAssembly>
      <assemblyIdentity name="System.ServiceProcess" publicKeyToken="b77a5c561934e089" culture="neutral" />
      <bindingRedirect oldVersion="4.0.0.0" newVersion="3.0.0.0" />
    </dependentAssembly>            
  </assemblyBinding>
</runtime>

并简单地强制运行时绑定到旧版本。

不幸的是,该应用程序对 System.ServiceModel 4.0 有一些依赖性,因此切换起来并不那么简单。