何となく Blog by Jitta
Microsoft .NET 考

目次

Blog 利用状況
  • 投稿数 - 761
  • 記事 - 18
  • コメント - 35955
  • トラックバック - 222
ニュース
  • IE7以前では、表示がおかしい。div の解釈に問題があるようだ。
    IE8の場合は、「互換」表示を OFF にしてください。
  • 検索エンジンで来られた方へ:
    お望みの情報は見つかりましたか? よろしければ、コメント欄にどのような情報を探していたのか、ご記入ください。
It's ME!
  • はなおか じった
  • 世界遺産の近くに住んでます。
  • Microsoft MVP for Visual Developer ASP/ASP.NET 10, 2004 - 9, 2011
広告

記事カテゴリ

書庫

日記カテゴリ

ギャラリ

その他

わんくま同盟

同郷

 

背景

設定ファイルでユーザの偽装を行うと、アプリケーション全体が、偽装したユーザで動作します。もし、偽装したユーザが大きな特権を持っていると、OS コマンド インジェクション、バッファ オーバーフローなどの攻撃を受けた場合に、OS 全体に被害を広げることになります。特に ASP.NET など、不特定多数の人が使うことが前提ならば、最悪の場合、システムがクラッシュする危険があります。

Vista で導入された UAC の原則に倣い、最初から特権ユーザとして動作するのではなく、必要な部分で局所的に、短期間のみ、偽装するようにします。

前提条件

  • .NET Framework 1.x(2.0 は未検証)

  • あらかじめ、本当に偽装が必要か、よく確認する

コード

注:VB.NET のコードを、エディタ上で C# に書き換えています。もしかすると、コンパイルエラーが出るかもしれません。


using System;
using System.ComponentModel;
using System.Security;
using System.Security.Principal;
using System.Runtime.InteropServices;
public class Impersonate : IDisposable {
    // これは、enum で定義するべき。。。
    private static int LOGON32_LOGON_INTERACTIVE = 2;
    private static int LOGON32_PROVIDER_DEFAULT = 0;
    private WindowsImpersonationContext impersonationContext;
    [DllImport("advapi32.dll", SetLastError=true)]
    private static extern bool LogonUser(
        string lpszUsername,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        out IntPtr phToken
        );
    [DllImport("advapi32.dll", SetLastError=true)]
    private extern static bool DuplicateToken(
        IntPtr ExistingTokenHandle,
        int SECURITY_IMPERSONATION_LEVEL,
        out IntPtr DuplicateTokenHandle
        );
    [DllImport("advapi32.dll", SetLastError=true)]
    private static extern bool RevertToSelf();
    [DllImport("kernel32.dll", SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);
    private Impersonate(WindowsIdentity tempWindowsIdentity) {
        impersonationContext = tempWindowsIdentity.Impersonate();
    }
    // 本体
    public static Impersonate ImpersonateValidUser(
        string userName,
        string domain,
        string password) {
        WindowsIdentity tempWindowsIdentity;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;
        Impersonate retValue = null;
        try {
            if (RevertToSelf() == true) {
                if (LogonUser(userName, domain, password,
                    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out token) == true) {
                    if (DuplicateToken(token, 2, out tokenDuplicate) == true) {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        retValue = new Impersonate(tempWindowsIdentity);
                        if (retValue.impersonationContext == null) {
                            retValue = null;
                        }
                    }
                }
            }
            return retValue;
        } finally {
            // try - finally は2重にするべき。。。
            if (!tokenDuplicate.Equals(IntPtr.Zero)) {
                CloseHandle(tokenDuplicate);
            }
            if (!token.Equals(IntPtr.Zero)) {
                CloseHandle(token);
            }
        }
    }
    public virtual void Dispose() {
        if (impersonationContext != null) {
            impersonationContext.Undo();
            impersonationContext = null;
        }
    }
    ~Impersonate() {
        this.Dispose();
    }
}

使う方


// using が使えるかな?
Impersonate impersonate = null;
try {
    impersonate = Impersonate.ImpersonateValidUser("user", "domain", "password");
    // 何らかの処理
} finally {
    if (impersonate != null) impersonate.Dispose();
}

説明

Windows ユーザは、.NET Framework では WindowsIdentity クラスで表します。このクラスのメンバに、他のユーザに偽装する Impersonate メソッドがあります。

しかし、ログインするという行為は、.NET Frameowork の外にある概念です。したがって、.NET Framwework では、ユーザ名とパスワードを確認するような仕組みは提供されていません。なので、Platform Invoke(プラットフォーム呼び出し P/Invoke)を行います。

今回使用するのは、LogonUser 関数です。第2引数で指定したドメインに属する、第1引数で指定したユーザに、第3引数で指定したパスワードを使ってログインを試みます。ローカル ユーザに偽装する場合、第2引数にはマシン名を入力します。

偽装を解除するには、偽装操作を行う前の Windows ユーザーを表す、WindowsImpersonationContext クラスのメンバ メソッドである、Undo メソッドを使います。

なお、第4引数で指定した、LOGON32_LOGON_INTERACTIVE は、デスクトップとの対話処理をするために、偽装するユーザのプロファイルを読み込みます。これにより、レジストリの HKCU ハイブを操作したり、画面のあるプロセスを起動することが出来るようになります。これはまた、セキュリティ上のリスクともなり得ますので、第4引数の指定はよく注意しておこなってください。

一連の流れを説明します。

  1. 現在偽装していないことを確認する(RevertToSelf)

  2. 偽装するユーザの、ユーザ名とパスワードを確認する(LogonUser)

  3. 現在ログイン中のユーザのコンテキストを複製する(DuplicateToken)

  4. 偽装したユーザで処理を行う

  5. 偽装を解除する(Undo)

このうち、「偽装したユーザで処理を行う」以外は、定型処理になります。今回紹介する Impersonate クラスは、定型処理を1つのクラスとしてまとめました(Facade パターン?)。

Impersonate クラスは IDisposable インターフェイスを実装します。ここで Dispose メソッドは、「偽装を解除する」処理を行います。

懸案事項

偽装する方法は提示しましたが、安全に偽装するユーザのアカウント情報を入力する方法は提示していません。Win32 API を使うか、暗号化されたファイルを使って、アカウントとパスワードを管理します。

.NET Framework 2.0 では、SecureString クラスが導入されています。これを使用することで、パスワードが、使用期間を超えてメモリ上に残ることを回避できます。String クラスでは、使用しているメモリが回収されても、同じメモリ領域が再利用されるまで、メモリの内容は保持されています。

参考

修正履歴

ビルドが通るところまで確認。

2006/11/10

タイトル: ACCESSファイルにアクセス不可(VSUG 質問スレッド)での SRさんのご指摘により、VB → C# 変換時のミスを修正

  • 変数宣言で、As が残っていた

  • If が、何ヶ所か大文字だった

  • デストラクタの宣言を間違っていた

  • Strict On にしていなかったらしいorz

  • コンパイルしていないから、out パラメータがいるというのが・・・

  • nanka,wakarankedo,eraikanchigaiwohitotsu...

投稿日時 : 2006年6月6日 22:24
コメント
タイトル
名前
Url
コメント