ASP.NET托管Active Directory RSAT powershell脚本 - DC挂断

时间:2013-02-18 22:36:16

标签: c# asp.net powershell active-directory impersonation

我正在开发一个ASP.NET(C#)应用程序,该应用程序基本上是用于运行常规管理任务的Powershell脚本的网关。其中一些脚本使用ActiveDirectory RSAT模块,我发现其中一些cmdlet在通过网关调用时无法正常运行,并且跟踪似乎暗示与域控制器的连接成功,但随后被关闭由DC。

以下代码是一个ASP.NET Web表单,其中包含一个用于指定用户名的文本输入。基本上,它执行以下操作:

  • 假设Web用户的身份(已确认由powershell继承)
  • 在该运行空间中创建powershell运行空间和管道
  • 调用Get-ADUser cmdlet并将用户名作为Identity参数传递
  • 通过将用户名称读取到表单上的输出元素来确认成功。

    protected void LookupButton_Click( object sender, EventArgs e ) {
      WindowsImpersonationContext impersonationContext = ((WindowsIdentity)User.Identity).Impersonate();
      Runspace runspace;
      Pipeline pipe;
      try {
        runspace = new_runspace();
        runspace.Open();
        pipe = runspace.CreatePipeline();
        Command cmd = new Command("Get-ADUser");
        cmd.Parameters.Add(new CommandParameter("Identity", text_username.Text));
        pipe.Commands.Add(cmd);
    
        PSObject ps_out = pipe.Invoke().First();
        output.Text = ps_out.Properties["Name"].Value.ToString();
      }
      catch( Exception ex ) {
        error.Text = ex.ToString();
      }
      finally {
        impersonationContext.Undo();
      }
    }
    
    private Runspace new_runspace( ) {
      InitialSessionState init_state = InitialSessionState.CreateDefault();
      init_state.ThreadOptions = PSThreadOptions.UseCurrentThread;
      init_state.ImportPSModule(new[] { "ActiveDirectory" });
      return RunspaceFactory.CreateRunspace(init_state);
    }
    

有趣的部分是catch块(强调我的)中暴露的错误消息中的特定措辞:

  

System.Management.Automation.CmdletInvocationException:无法执行   联系服务器。这可能是因为该服务器不存在,它   目前已关闭,或者它没有Active Directory Web   服务正在运行--->   Microsoft.ActiveDirectory.Management.ADServerDownException:无法执行   联系服务器。这可能是因为该服务器不存在,它   目前已关闭,或者它没有Active Directory Web   服务正在运行---> System.ServiceModel.CommunicationException:The   套接字连接中止。这可能是由错误引起的   处理您的消息或超过接收超时   远程主机或底层网络资源问题。本地套接字   超时为'00:01:59.6870000'。 ---> System.IO.IOException:读取   操作失败,请参阅内部异常。 --->   System.ServiceModel.CommunicationException:套接字连接是   中止。这可能是由处理您的消息或错误导致的   接收远程主机或基础主机超时超时   网络资源问题。本地套接字超时为'00:01:59.6870000'。   ---> System.Net.Sockets.SocketException:远程主机强行关闭现有连接

上层异常表明存在超时,但较低的异常没有指示超时(并且从命令返回只需要几秒钟)。在无法访问服务器的情况下,最低级别的异常消息说明了这一点,但是这个特定的措辞让我觉得在这里有一些身份验证(或其他安全)问题。

更新2013年2月19日: 使用描述here的模拟方法时,脚本按预期运行。这让我觉得问题可能是Windows身份验证提供的WindowsIdentity对象可能不适用于基本上对AD进行RPC调用的脚本。不幸的是,我不想放弃使用Windows身份验证,因为我必须在我的应用程序代码中处理用户的密码(这不是我想要的责任)。

我无法找到任何关于Windows auth正在做什么的文档,或者使用它会导致什么样的模仿。是否可以在使用Windows身份验证时执行此操作,或者我将不得不要求用户向我提供密码?

2 个答案:

答案 0 :(得分:4)

原因

此问题的根本原因是,在IIS中使用Windows身份验证时,安全令牌仅对向Web服务器计算机验证Web客户端计算机有效。相同的令牌对于将Web服务器计算机验证到任何其他计算机无效,这就是我的应用程序尝试执行的操作:

  1. 客户端获取安全令牌并将其发送到Web服务器。
  2. IIS要求DC验证令牌并验证令牌。此时,Web客户端已通过Web服务器验证。
  3. IIS根据应用程序的授权规则检查经过身份验证的身份。
  4. Web应用程序使用IIS收到的令牌模拟身份,然后运行一个脚本,然后继承相同的安全令牌。
  5. 该脚本尝试使用相同的令牌对远程RPC服务进行身份验证。
  6. 域控制器将身份验证尝试识别为重放攻击(该令牌用于其他服务)并关闭连接。
  7. 将这称为Kerberos的“副作用”并不完全正确,但事后我并不明白,尽管事后看来这很明显。我希望有人可以从这些信息中受益。

    解决方案

    解决方案是让应用程序生成自己的安全令牌,然后通过对LogonUser()进行API调用,将其用作Web用户身份验证其他计算机上的服务。您的应用程序代码需要访问用户的清除密码,这可以通过在IIS中仅启用HTTP基本身份验证来实现。 Web服务器仍将强制执行相同的身份验证和授权规则,但用户名和密码都将可用于您的应用程序代码。请注意,这些凭据会以明文的形式传输到网络服务器,因此在生产中使用此凭据之前,您需要使用SSL。

    我根据描述here的过程创建了一个小助手类,以促进此过程。这是一个简短的演示:

    登录助手:

    public class IdentityHelper {
      [DllImport("advapi32.dll")]
      private static extern int LogonUserA( String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken );
    
      [DllImport("advapi32.dll",
        CharSet = CharSet.Auto,
        SetLastError = true)]
      private static extern int DuplicateToken( IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken );
    
      [DllImport("kernel32.dll",
        CharSet = CharSet.Auto)]
      private static extern bool CloseHandle( IntPtr handle );
    
      public const int LOGON32_LOGON_INTERACTIVE = 2;
      public const int LOGON32_PROVIDER_DEFAULT = 0;
      public const int IMPERSONATION_LEVEL_IMPERSONATE = 2;
    
      public static WindowsIdentity Logon( string username, string password, string domain = "" ) {
        IntPtr token = IntPtr.Zero;
        WindowsIdentity wid = null;
    
        if( domain == "" ) {
          split_username(username, ref username, ref domain);
        }
    
        if( LogonUserA(username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0 ) {
          wid = WIDFromToken(token);
        }
        if( token != IntPtr.Zero ) CloseHandle(token);
        return wid;
      }
    
      public static WindowsIdentity WIDFromToken( IntPtr src ) {
        WindowsIdentity wid = null;
        IntPtr token = IntPtr.Zero;
        if( DuplicateToken(src, IMPERSONATION_LEVEL_IMPERSONATE, ref token) != 0 ) {
          wid = new WindowsIdentity(token);
        }
        if( token != IntPtr.Zero ) CloseHandle(token);
        return wid;
      }
    
      private static void split_username( string username, ref string username_out, ref string domain_out ) {
        string[] composite_username = username.Split(new char[] { '\\' });
        if( composite_username.Length == 2 ) {
          domain_out = composite_username[0];
          username_out = composite_username[1];
        }
      }
    }
    

    Powershell助手类:

    public class PSHelper {
      public static Runspace new_runspace() {
        InitialSessionState init_state = InitialSessionState.CreateDefault();
        init_state.ThreadOptions = PSThreadOptions.UseCurrentThread;
        init_state.ImportPSModule(new[] { "ActiveDirectory" });
        return RunspaceFactory.CreateRunspace(init_state);
      }
    }
    

    ASP.NET表单处理程序:

    protected void LookupButton_Click( object sender, EventArgs e ) {
      string outp = "";
      WindowsIdentity wid = IdentityHelper.Logon(Request["AUTH_USER"], Request["AUTH_PASSWORD"]);
      using( wid.Impersonate() ) {
        Runspace runspace;
        Pipeline pipe;
        runspace = PSHelper.new_runspace();
        runspace.Open();
        pipe = runspace.CreatePipeline();
        Command cmd = new Command("Get-ADUser");
        cmd.Parameters.Add(new CommandParameter("Identity", text_username.Text));
        pipe.Commands.Add(cmd);
        outp = pipe.Invoke().First().Properties["Name"].Value.ToString();
      }
      output.Text = outp;
    }
    

答案 1 :(得分:0)

在导入ActiveDirectory模块和尝试执行命令之间添加延迟是否有帮助?