久保 正樹(JPCERTコーディネーションセンター) [著]
戸田 洋三(JPCERTコーディネーションセンター) [著]
本連載では、最近話題のDLL hijacking vulnerabilityと呼ばれるプログラムのDLL読み込みに関する脆弱性について、前後編の2回に分けて解説します。後編では、DLL hijackingの脆弱性に対し攻撃を受けないアプリケーションを開発するため、Microsoftが開発者向けに公開している対策方法の一部を紹介します。
はじめに
前編では、DLL hijackingと呼ばれるプログラムのDLL読み込みに関する脆弱性の概要および、関連するイベントを紹介しました。
Microsoftはセキュリティアドバイザリ2269637を公開するとともに、Security Research & Defenseブログに「More information about the DLL Preloading remote attack vector」というエントリを追加し、より詳細な技術情報を説明しています。このエントリのなかで、アプリケーション開発者向けのガイダンスとして作成された word 文書「Secure loading of libraries to prevent DLL preloading attacks」が提供されました。現在は、この文書の日本語抄訳「ライブラリを安全にロードして DLL のプリロード攻撃を防ぐ」も提供されています。
ガイダンス文書の構成は、以下のようになっています。
- DLLのプリロード攻撃の詳細
- LoadLibraryベースの攻撃
- SearchPathベースの攻撃
- ShellExecuteおよびCreateProcess
- ソフトウェア開発者向けの推奨されるステップ
- 開発者が行うべき4項目の作業
- 安全でないライブラリロードを行っていないかチェック
- 完全修飾パスを使ってライブラリロードするように修正
- 既定のDLLの検索順序からカレントディレクトリを削除
- SetSearchPathModeを使って安全なプロセス検索モードを有効化
- 安全でないライブラリロードの特定に関するガイダンス
ライブラリをロードするコード例として、攻撃に悪用される可能性のあるコード例と安全なコード例を示しています。
- 開発者が行うべき4項目の作業
- Process Monitorを用いて、安全でないロードを動的に検出する
アプリケーションの実行時に安全でないロードを行っているかどうかを、Process Monitorを使って観察する方法について簡単に説明しています。 - 追加のリソース
関連するブログ記事やMSDNの情報がリストされています。
今回はこの文書に基づいて、Windowsアプリケーション開発における対策方法をご紹介します。
DLL hijacking攻撃を受けないアプリケーションを開発するために
(1)LoadLibrary() の引数はフルパスで指定せよ
Windowsにおいて、ライブラリを動的にロードするために用意されているAPIはLoadLibrary()です。その引数として DLL ファイル名だけが指定されていた場合には、前編でも説明したように、あらかじめ定められているディレクトリから順番に探し出そうとします。攻撃者はその挙動を悪用して、攻撃コードを含むDLLを読み込ませようとするのでした。
そのような攻撃を受けないようにするには、完全修飾パスでDLLファイルを指定することが推奨されます。アプリケーションのユーザ側としても、決まったディレクトリにあるDLLが使われると分かっていれば、管理しやすいはずです。
(2)DLL探索リストからカレントディレクトリを削除せよ
LoadLibrary()呼び出しにおいて、カレントディレクトリを検索させないように設定する、という手段も有効です。これは、空文字列を引数に SetDllDirectory()を呼び出すことで実現できます。
ドキュメントファイルをダブルクリックすることでアプリケーションを起動するとき、カレントディレクトリはそのドキュメントファイルが置いてあるディレクトリになっています。そのため、一緒に置いてあるDLLが読み込まれてしまう危険があるのです。アプリケーションでLoadLibrary()を使っている場合には、アプリケーションの初期化処理のなかで、SetDllDirectory("")を呼び出しておきましょう。
(3)DLL探索にSearchPath()は使うな
Windowsでは、SearchPath()という関数を使ってドキュメントファイル(あるいはデータファイル)を探索することができます。この関数は LoadLibrary()とは別の探索ディレクトリリストを持っていることに注意が必要です。
MSDN(英語)のSearchPath()の説明にも、DLLファイルの探索に使うことは推奨しないとの注意書きが記載されています。
The SearchPath function is not recommended as a method of locating a .dll file if the intended use of the output is in a call to the LoadLibrary function. This can result in locating the wrong .dll file because the search order of the SearchPath function differs from the search order used by the LoadLibrary function. If you need to locate and load a .dll file, use the LoadLibrary function.
(http://msdn.microsoft.com/en-us/library/aa365527%28VS.85%29.aspx)
SearchPath()を使ってDLLファイルを探索し、見つけたDLLファイルをLoadLibrary()で読み込む、というような組み合わせになっているコードは危険です。SearchPath()を使わない形に修正する必要があります。
(4)Windows のバージョンチェックに LoadLibrary() を流用するな
アプリケーションによっては、Windowsのバージョンを確認するために LoadLibrary()を使用している場合があります。しかし、この目的のためには専用の関数があるので適切なものを使うべきです。しかも、Windowsのバージョン確認の目的が、特定の機能を使えるかどうかを調べるためであるならば、Windows バージョンをチェックするのではなく、その機能の存在を調べる正しいAPIを使うべきである、と述べられています。
実際、Firefoxなどはこの方法を使っていたようで、Mozillaセキュリティアドバイザリ MFSA2010-52には以下のような記述があります:
Firefoxはプラットフォーム判別の一環として起動時にdwmapi.dllを読み込もうとしますが、Windows XPなどこのライブラリが存在しないシステムでは、Firefoxは引き続き現在のワーキングディレクトリからライブラリを読み込もうとします。攻撃者はこの脆弱性を悪用して、HTMLファイルと悪質なdwmapi.dllのコピーをコンピュータ上の同じディレクトリにダウンロードし、そのHTMLファイルをFirefoxで開くようユーザを仕向け、その結果悪質なコードを実行させることが可能でした。攻撃者が被害者と同じネットワーク上にいた場合、UNCパスを通じて悪質なDLLを読み込ませることも可能でした。
(http://www.mozilla-japan.org/security/announce/2010/mfsa2010-52.html)
(5)実行可能ファイルの読み込みにも注意
DLL読み込みと同様の注意が必要な場面がもう一つあります。アプリケーションから別のアプリケーションを実行する場合です。このような機能のためにCreateProcess()やShellExecute()などの関数が用意されていますが、DLL読み込みと同様の注意が必要であるとされています。
オープンソースプログラムの修正事例
Windows版のオープンソースソフトウェアであるVLCでは、本脆弱性の修正が以下のように行われています(VideoLAN-SA-1005)。以下にそのパッチを引用します。
diff --git a/bin/winvlc.c b/bin/winvlc.c index 2d09cba..ac9b97c 100644 (file) --- a/bin/winvlc.c +++ b/bin/winvlc.c @@ -131,12 +131,19 @@ int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, if(h_Kernel32) { BOOL (WINAPI * mySetProcessDEPPolicy)( DWORD dwFlags); + BOOL (WINAPI * mySetDllDirectoryA)(const char* lpPathName); # define PROCESS_DEP_ENABLE 1 mySetProcessDEPPolicy = (BOOL WINAPI (*)(DWORD)) GetProcAddress(h_Kernel32, "SetProcessDEPPolicy"); if(mySetProcessDEPPolicy) mySetProcessDEPPolicy(PROCESS_DEP_ENABLE); + + /* Do NOT load any library from cwd. */ + mySetDllDirectoryA = (BOOL WINAPI (*)(const char*)) GetProcAddress(h_Kernel32, "SetDllDirectoryA"); + if(mySetDllDirectoryA) + mySetDllDirectoryA(""); + FreeLibrary(h_Kernel32); }
パッチで追加された行(+文字ではじまる行)からも分かるように、SetDllDirectoryA()を空文字列("")を引数に与えて呼び出すことで、DLLのサーチパスからカレントディレクトリを取り除く処理を追加していることが分かります。
終わりに
後編では、Microsoftが提供している開発者向けガイダンスにもとづいて、Windowsアプリケーション開発で心得ておくべき事項を説明しました。
皆さんも、まずは自分が書いたコードがDLL hijacking攻撃の影響を受けるかどうか、チェックするところから始めてみてください。
参考情報
- マイクロソフト セキュリティ アドバイザリ(2269637) 安全でないライブラリのロードにより、リモートでコードが実行される
- More information about the DLL Preloading remote attack vector
- Secure loading of libraries to prevent DLL preloading attacks(docxファイル)
- ライブラリを安全にロードして DLL のプリロード攻撃を防ぐ(docxファイル/上記の日本語訳)
- Mozilla Foundation セキュリティアドバイザリ 2010-52 Windows XP における DLL 読み込み脆弱性
- VideoLAN Project VideoLAN-SA-1005: DLL preloading vulnerability