3

基本的に、日常的な管理タスク用のPowershellスクリプトを実行するためのゲートウェイであるASP.NET(C#)アプリの作成に取り組んでいます。これらのスクリプトの一部はActiveDirectoryRSATモジュールを使用しており、ゲートウェイを介して呼び出されたときにこれらのコマンドレットの一部が正しく実行されないことがわかりました。トレースは、ドメインコントローラーへの接続が成功したことを示しているようですが、シャットダウンされています。 DCによって。

次のコードは、ユーザー名を指定するための1つのテキスト入力を持つASP.NETWebフォームです。基本的に、それは以下を行います:

  • WebユーザーのIDを想定します(PowerShellによって継承されることが確認されています)
  • PowerShellRunspaceとそのRunSpace内にパイプラインを作成します
  • Get-ADUserコマンドレットを呼び出し、ユーザー名を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:サーバーに接続できません。これは、このサーバーが存在しないか、現在ダウンしているか、ActiveDirectoryWebサービスが実行されていないことが原因である可能性があります。---> Microsoft.ActiveDirectory.Management.ADServerDownException:サーバーに接続できません。これは、このサーバーが存在しないか、現在ダウンしているか、ActiveDirectoryWebサービスが実行されていないことが原因である可能性があります。---> System.ServiceModel.CommunicationException:ソケット接続が中止されました。これは、メッセージの処理エラー、リモートホストによる受信タイムアウトの超過、または根本的なネットワークリソースの問題が原因である可能性があります。ローカルソケットのタイムアウトは「00:01:59.6870000」でした。---> System.IO.IOException:読み取り操作が失敗しました。内部例外を参照してください。---> System.ServiceModel.CommunicationException:ソケット接続が中止されました。これは、メッセージの処理エラー、リモートホストによる受信タイムアウトの超過、または根本的なネットワークリソースの問題が原因である可能性があります。ローカルソケットのタイムアウトは「00:01:59.6870000」でした。--->System.Net.Sockets.SocketException:既存の接続がリモートホストによって強制的に閉じられました

上位レベルの例外はタイムアウトがあったことを示していますが、下位例外によって示されるタイムアウトはありませんでした(コマンドから戻るのに数秒しかかかりませんでした)。サーバーに到達できない場合は、最下位レベルの例外メッセージで同じように表示されますが、この特定の表現により、ここで何らかの認証(またはその他のセキュリティ)の問題が発生していると思われます。

2013年2月19日更新:ここで説明する偽装方法を使用すると、スクリプトは期待どおりに実行されます。これにより、問題は、Windows認証によって提供されるWindowsIdentityオブジェクトが、ADに対して本質的にRPC呼び出しを行うスクリプトにはおそらく不適切である可能性があると思われます。残念ながら、アプリケーションコードでユーザーのパスワードを処理する必要があるため、Windows認証を放棄することはあまり望ましくありません(これは私が望む責任ではありません)。

Windows authが正確に何を行っているのか、またはその使用によってどのような種類のなりすましが許可されているのかについてのドキュメントを見つけることができませんでした。Windows認証を使用しているときにこれを行うことは可能ですか、それともユーザーにパスワードの入力を要求する必要がありますか?

4

2 に答える 2

4

原因

この問題の根本的な原因は、IISでWindows認証を使用する場合、セキュリティトークンはWebクライアントマシンをWebサーバーマシンに対して認証する場合にのみ有効であるということです。同じトークンは、Webサーバーマシンを他のマシンに対して認証するためには無効であり、それが私のアプリケーションが行おうとしていたことです。

  1. クライアントはセキュリティトークンを取得し、それをWebサーバーに送信します。
  2. IISはDCにトークンの検証を要求し、トークンが検証されます。この時点で、WebクライアントはWebサーバーに対して認証されます。
  3. IISは、認証されたIDをアプリケーションの承認ルールと照合します。
  4. Webアプリケーションは、IISが受信したトークンを使用してIDを偽装し、同じセキュリティトークンを継承するスクリプトを実行します。
  5. スクリプトは、同じトークンを使用してリモートRPCサービスへの認証を試みます。
  6. ドメインコントローラーは、認証の試行をリプレイ攻撃(そのトークンは別のサービスに対するもの)として認識し、接続をシャットダウンします。

これをKerberosの「副作用」と呼ぶのは正確には正しくありませんが、後から考えると明らかなように見えますが、最初は明白ではありませんでした。誰かがこの情報から利益を得ることができることを願っています。

解決

これに対する解決策は、アプリケーションが独自のセキュリティトークンを生成し、 LogonUser()へのAPI呼び出しを行うことで、他のマシン上のサービスに対するWebユーザーとして認証するために使用できるようにすることです。アプリケーションコードは、ユーザーのクリアパスワードにアクセスする必要があります。これは、IISでHTTP基本認証のみを有効にすることで利用可能になります。Webサーバーは引き続き同じ認証と承認のルールを適用しますが、ユーザー名とパスワードの両方がアプリケーションコードで使用できるようになります。これらのクレデンシャルは平文でWebサーバーに送信されるため、本番環境でこれを使用する前にSSLが必要になることに注意してください。

ここで説明する手順に基づいて、このプロセスを容易にする小さなヘルパークラスを作成しました。これが短いデモンストレーションです:

ログオンヘルパー:

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;
}
于 2013-02-20T22:06:44.407 に答える
0

ActiveDirectoryモジュールをインポートしてからコマンドを実行しようとするまでの間に遅延を追加するのに役立ちますか?

于 2013-02-19T02:42:29.947 に答える