DLL プリロード攻撃を防止するためのライブラリの安全な読み込み


概要


完全修飾パスを指定することなくアプリケーションが動的にダイナミック リンク ライブラリ (DLL) を読み込むと、Windows では、一連の明確に定義されたディレクトリを検索することにより、DLL の特定が試行されます。攻撃者がいずれかのディレクトリを制御した場合、攻撃者は、アプリケーションで想定していた DLL の代わりに悪意のあるコピーの DLL をアプリケーションに強制的に読み込ませることができます。このような攻撃は "DLL のプリロード攻撃 (DLL Preloading Attack)" と呼ばれ、共有 DLL ライブラリの動的な読み込みをサポートするすべてのオペレーティング システムに共通する問題です。このような攻撃の効果としては、アプリケーションを実行しているユーザーのコンテキストで攻撃者がコードを実行できることが考えられます。アプリケーションが管理者として実行されている場合、これは特権のローカルな昇格に至る可能性があります。マイクロソフトは、このような攻撃への新たな関心を把握しています。相互関係にあるマイクロソフトのカスタマーに対するこの問題の影響を限定的にするため、カスタマーがこの問題を認識するようにし、アプリケーションでこの問題に対処するための必要なツールを入手できるように、このドキュメントを開発者コミュニティに公開しています。 

詳細


DLL のプリロード攻撃について

LoadLibrary ベースの攻撃

完全修飾パスを指定することなくアプリケーションが動的に DLL を読み込むと、Windows では、DLL 検索順と呼ばれる一連の明確に定義されたディレクトリを線形的に検索することにより、この DLL の特定が試行されます。Windows で DLL 検索順の内部において DLL が特定されると、Windows でその DLL が読み込まれます。ただし、Windows により DLL 検索順のディレクトリで DLL が検出されないと、Windows により DLL 読み込み操作に対してエラーが返されます。 DLL を動的に読み込むために使用される LoadLibrary 関数および LoadLibraryEx 関数の DLL 検索順は以下のとおりです。
  1. アプリケーションが読み込まれたディレクトリ
  2. システム ディレクトリ
  3. 16 ビット システム ディレクトリ
  4. Windows ディレクトリ
  5. 現在の作業ディレクトリ (CWD)
  6. PATH 環境変数に設定されているディレクトリ


次のような状況を考えます。


  • アプリケーションが、アプリケーションの CWD で検出されると想定される完全修飾パスを指定することなく DLL を読み込みます。
  • アプリケーションは、DLL が検出されなかったケースを処理する完全な準備ができています。
  • 攻撃者は、アプリケーションに関するこの情報を知っていて、CWD を制御します。
  • 攻撃者は、特殊な細工のある独自バージョンの DLL を CWD にコピーします。ここでは、攻撃者がこの操作を実行するアクセス許可を持っていることが前提になっています。
  • Windows では、DLL 検索順のディレクトリを検索し、アプリケーションの CWD でその DLL を検出します。
この場合、特殊な細工のある DLL がアプリケーションの内部で実行され、現在のユーザーの特権を取得します。

推奨事項
この攻撃を防止するために、アプリケーションは空の文字列 ("") を使用して SetDllDirectory API を呼び出すことにより、DLL 検索パスから現在の作業ディレクトリ (CWD) を削除することができます。アプリケーションが現在のディレクトリからの DLL の読み込みに依存している場合は、現在の作業ディレクトリを取得し、それを使用して LoadLibrary の完全修飾パスで渡してください。



また、マイクロソフトは、ユーザーがどのバージョンの Windows を実行しているかを判断するために、一部の開発者が LoadLibrary を使用して特定の DLL が存在するかどうかを検証していることも認識しています。開発者は、このようにするとアプリケーションが脆弱になる可能性があることを認識する必要があります。アプリケーションが実行されている Windows リリースに、影響を受けるライブラリが実際には存在しない場合、攻撃者はその同じ名前を持つライブラリを CWD に導入することができます。マイクロソフトは、このテクニックを使用しないことを強く推奨します。代わりに、MSDN の資料「システム バージョンの取得」で説明されている推奨テクニックを使用してください。

サードパーティのプラグインを読み込むが、LoadLibrary 呼び出しで修飾パスを使用することをプラグインに強制できないアプリケーションは、SetDllDirectory("") を呼び出して CWD を削除してから、SetDllDirectory("プラグインのインストール場所") を呼び出して DLL 検索パスにプラグインのインストール ディレクトリを追加する必要があります。

SearchPath ベースの攻撃

アプリケーションが SearchPath API を使用して DLL を特定し、SearchPath により返されるパスを動的に読み込む場合は、同じような攻撃が存在します。SearchPath API の既定の検索順は以下のとおりです。
  • アプリケーションが読み込まれたディレクトリ
  • 現在の作業ディレクトリ (CWD)
  • システム ディレクトリ
  • 16 ビット システム ディレクトリ
  • Windows ディレクトリ
  • PATH 環境変数に設定されているディレクトリ
このパターンは安全でないため、このパターンはお勧めしません。出力の使用目的が LoadLibrary 関数への呼び出しの内部に存在する場合、.dll ファイルを特定する方法としては SearchPath 関数はお勧めしません。SearchPath 関数の検索順は LoadLibrary 関数により使用される検索順とは異なるため、これは正しくない .dll ファイルを特定する可能性があります。.dll ファイルを特定して読み込む必要がある場合は、LoadLibrary 関数を使用します。

ShellExecute と CreateProcess

このような問題の変種は、開発者が ShellExecuteCreateProcess などの同じような関数を呼び出して外部実行可能ファイルを読み込む場合にも存在する可能性があります。開発者がバイナリを読み込み、完全修飾パスを指定する場合には注意することをお勧めします。ライブラリではなくバイナリを読み込む場合には、これにより複雑さが軽減されます。

ソフトウェア開発者向けの推奨手順

開発者は以下のようにすることをお勧めします。

  • 安全ではないライブラリ読み込み (個別の例はこの資料の後で説明します) のインスタンスに対してアプリケーションを検証します。以下の操作が該当します。
    • SearchPath を使用してライブラリまたはコンポーネントの場所を識別する。
    • LoadLibrary を使用してオペレーティング システムのバージョンを識別する。
  • 可能である場合、LoadLibrary、CreateProcess、および ShellExecute への呼び出しに完全修飾パスを使用する。
  • 空の文字列 ("") を使用して SetDllDirectory への呼び出しを実装し、必要である場合に既定の DLL 検索順から現在の作業ディレクトリを削除する。SetDllDirectory はプロセス全体に影響することに注意します。そのため、LoadLibrary への呼び出しの前後ではなく、プロセス初期化の早い段階で一度この操作を行う必要があります。SetDllDirectory はプロセス全体に影響するため、異なる値を使用して SetDllDirectory を呼び出す複数のスレッドは、未定義の動作の原因になる可能性があります。また、プロセスがサードパーティの DLL を読み込むように設計されている場合、プロセス全体を設定すると非互換性の原因になるかどうかを確認するためにテストが必要です。既知の問題としては、アプリケーションが Visual Basic for Applications に依存する場合、プロセス全体の設定は非互換性の原因になることがあります。
  • SetSearchPathMode 関数を使用して、プロセス用の安全なプロセス検索モードを有効にします。これにより、現在の作業ディレクトリが、プロセスの有効期限の SearchPath 検索一覧で最後の場所に移動します。
  • 安全な検索モードが有効である場合であっても、完全修飾パスを指定することなく DLL の存在を調べるために SearchPath を使用することは避けます。これは、依然として DLL のプリロード攻撃に至る可能性があるためです。

安全ではないライブラリ読み込みの識別の手引き

ソース コードでは、安全ではないライブラリ読み込みの例は以下のとおりです。
  • 次のコード例では、アプリケーションは最も安全度の低い検索パスを使用して "schannel.dll" を検索します。攻撃者が CWD に schannel.dll を配置できる場合、アプリケーションが適切なライブラリを探すために Windows ディレクトリを検索する前であっても、schannel.dll が読み込まれます。
    DWORD retval = SearchPath(NULL, "schannel", ".dll", err, result, NULL); 
    HMODULE handle = LoadLibrary(result);
  • 次のコード例では、LoadLibrary() 呼び出しに関してこのドキュメントの最初で説明されているさまざまなアプリケーションとオペレーティング システムの場所から、アプリケーションはライブラリの読み込みを試行します。ファイルが存在しないリスクがある場合、アプリケーションは現在の作業ディレクトリからファイルの読み込みを試行することがあります。この場合は、前の例よりもやや危険度が低くなります。ただし、環境が完全には予測可能でない場合、アプリケーション ユーザーは依然としてリスクにさらされます。
    HMODULE handle = LoadLibrary("schannel.dll");



以下は、より適切な安全度の高いライブラリ読み込みの例です。

  • 次のコード例では、ライブラリは完全修飾パスを使用して直接読み込まれます。攻撃者がアプリケーションの対象ディレクトリへの書き込みアクセス許可を既に持っている場合を除き、攻撃者が悪意のあるコードを導入するリスクはありません。

    HMODULE handle = LoadLibrary("c:\\windows\\system32\\schannel.dll");


    : システム ディレクトリを確認する方法の詳細については、次の資料を参照してください。

    GetSystemDirectorySHGetKnownFolderPath
  • 次のコード例では、LoadLibrary を呼び出す前に検索パスから現在の作業ディレクトリが削除されます。これによりリスクが大幅に軽減されます。理由としては、攻撃者が DLL のプリロード攻撃を使用するには、アプリケーション ディレクトリ、Windows ディレクトリ、またはユーザーのパスで指定されているディレクトリのいずれかを制御する必要があるためです。
    SetDllDirectory ("");
    HMODULE handle = LoadLibrary("schannel.dll");
  • (MS09-014 に記載されている) セキュリティ更新プログラム 963027 がインストールされているすべてのシステムでは、次のコードは CWD を検索順の最後の場所に永続的に移動させます。そのプロセスの内部から検索モードを変更しようとする SetSearchPathMode 関数への以降の呼び出しはすべて失敗します。
    SetDllDirectory ("");
    HMODULE handle = LoadLibrary("schannel.dll");
  • 次のコード例では、LoadLibrary を呼び出す前に検索パスから現在の作業ディレクトリが削除されます。これによりリスクが大幅に軽減されます。理由としては、攻撃者が DLL のプリロード攻撃を使用するには、アプリケーション ディレクトリ、Windows ディレクトリ、またはユーザーのパスで指定されているディレクトリのいずれかを制御する必要があるためです。
    SetSearchPathMode (BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE | BASE_SEARCH_PATH_PERMANENT );
    HMODULE handle = LoadLibrary("schannel.dll");

安全ではない読み込みを動的に検出するための Process Monitor の使用

マイクロソフトは、Process Monitor という名前のツールを公開しています。このツールを使用すると、開発者と管理者は、実行中のプロセスの動作を厳密に追跡することができます。Process Monitor を使用すると、この種の問題に対してアプリケーションのいずれかが脆弱である可能性があるかどうかを動的に検出できます。
  • Process Monitor をダウンロードするには、以下のマイクロソフト Web ページにアクセスしてください。
  • 特定のディレクトリに設定された CWD を使用することで、アプリケーションを起動してみます。たとえば、ファイル ハンドラーがアプリケーションに割り当てられた拡張子を持つファイルをダブルクリックします。
  • 以下のフィルターを使用して Process Monitor を設定します。



    Process Monitor のフィルター画像
  • 脆弱性が存在するパスにヒットした場合、次のような画面が表示されます。Process Monitor のフィルター画像 2

    DLL を読み込むためのリモート ファイル共有への呼び出しは、これが脆弱性が存在するプログラムであることを示しています。

補足資料


詳細については、次のマイクロソフト Web ページを参照してください。

ダイナミック リンク ライブラリの検索順SearchPath 関数に関する MSDN のドキュメントLoadLibrary 関数に関する MSDN のドキュメントSetDllDirectory 関数に関する MSDN のドキュメントSetSearchPathMode 関数に関する MSDN のドキュメントMicrosoft Office の Principal Security Engineer である David Leblanc により投稿されたブログDLL のプリロード攻撃の MSRC Engineering チームの Andrew Roths により投稿されたブログ