良いニュースと悪いニュース
Michael Howard
Secure Windows Initiative
October 21, 2002 日本語版最終更新日 2002 年 10 月 29 日
概要:
Michael Howard 氏が、メモリ内の機密データを無効にするという鋭い指摘と、
HttpOnly cookie 拡張を使ってクロス サイト スクリプティングの問題点を軽減することについて詳しく説明します。
人々はありきたりのことしかしないものです!
「良いニュースと悪いニュースをどちらを先に聞きたいですか?」と尋ねられたら、
私は間違いなく悪いニュースを先に聞きたいと思います。
私は永遠の楽観主義者なので、
後から良いニュースを聞くと、
きっと悪いニュースは軽減されると思います。
そこで、まず悪いニュースからお話しましょう。
悪いニュース: メモリから機密を取り除く
一般的には、
機密データが必要なくなったときにメモリから機密データを削除することが推奨事例と考えられます。
その結果、機密データがページ ファイルやクラッシュ ダンプ ファイルに紛れ込む機会を削減するのに役立ちます。
次のコードを見てみましょう。
このコードを Microsoft Visual C++® .NET でコンパイルして、
このコードに欠陥が見つかるかどうか調べてみてください。
BOOL DoStuff() {
char pPwd[64];
size_t cchPwd = sizeof(pPwd) / sizeof(pPwd[0]);
BOOL fOK = false;
if (GetPassword(pPwd, &cchPwd))
fOK = DoSecretStuff(pPwd, cchPwd);
memset(pPwd, 0, sizeof(pPwd));
return fOK;
}
ヒントをあげましょう。
コードには何も悪いところはありません。
期待どおりに記述されています。
ユーザーのパスワードが取得され
(この例では、パスワードを取得する方法は問題ではありません)、
そのパスワードを使って何らかの重要な操作が実行されます。
作業完了後に、
パスワードの全バイトがクリアされます。
問題はコードではなく、"コンパイルされた" コードにあります。
では、この関数のアセンブラ出力を見てみましょう。
コードを読みやすくするために、不要な部分を削除し、いくぶん整理してあります。
?DoStuff PROC NEAR
; Line 14
sub esp, 68 ; 00000044H
mov eax, DWORD PTR ___security_cookie
xor eax, DWORD PTR __$ReturnAddr$[esp+64]
push esi
mov DWORD PTR __$ArrayPad$[esp+72], eax
; Line 19
lea eax, DWORD PTR _pPwd$[esp+72]
push 64 ; 00000040H
push eax
xor esi, esi
call ?GetPassword
add esp, 8
test eax, eax
je SHORT $L30117
; Line 20
push 64 ; 00000040H
lea ecx, DWORD PTR _pPwd$[esp+76]
push ecx
call ?DoSecretStuff
add esp, 8
pop esi
; Line 25
mov ecx, DWORD PTR __$ArrayPad$[esp+68]
xor ecx, DWORD PTR __$ReturnAddr$[esp+64]
add esp, 68 ; 00000044H
jmp @__security_check_cookie@4
$L30117:
mov ecx, DWORD PTR __$ArrayPad$[esp+72]
xor ecx, DWORD PTR __$ReturnAddr$[esp+68]
mov eax, esi
pop esi
add esp, 68 ; 00000044H
jmp @__security_check_cookie@4
?DoStuff ENDP
19 行目から 25 行目までのコードに注目してください。
残りは、関数のプロローグ コードとエピローグ コードで、
スタック フレームをセットアップし、解放し、
スタック ベースのバッファオーバーランが存在するかどうかを判断しています。
何か不足していませんか。
memset の呼び出しはどこにあるのでしょう。
オプティマイザが memset の呼び出しを削除してしまっています!
これはオプティマイザのバグだ!と叫んで通りに飛び出すのはちょっと待ってください。
そうではありません。
オプティマイザは自分の仕事をしているだけです。
オプティマイザは、
ローカル バッファ変数 pPwd に書き込まれていても、
その後その変数が読み取られていないことを認識するので、
単純に書込み操作を行う関数呼び出しを削除します。
これは古くから "使用されていない記憶域の削除" と呼ばれているもので、
Microsoft、Borland、GNU から提供されている多くの C コンパイラや C++ コンパイラでこの最適化を目撃してきました。
機密情報をメモリ内に残しておきたくないので、
いくつか解決策を見てみることにしましょう。
- memset 呼出し後に機密データに "アクセス" します。
- memset を最適化されないコードに置き換えます。
- このコードでは、最適化を無効にします。
memset 呼出し後に機密データにアクセスする
機密データに "アクセス" することは簡単です。
単純に、メモリ内からデータを読み取るコード行を関数に追加するだけですが、
それでもオプティマイザに注意する必要があります。
ポインタを volatile ポインタにキャストすることによって、
オプティマイザを欺くことができます。
volatile pointer はアプリケーションのスコープ外で操作されるので、
コンパイラはこれを最適化しません。
コードの最後の部分を以下に示すように変更することによって、
オプティマイザの処理を回避できます。
memset(pPwd, 0, sizeof(pPwd));
*(volatile char*)pPwd = *(volatile char *)pPwd;
return fOK;
memset を最適化されないコードに置き換える
Windows Security Push 中にこの問題が発見されたとき、
最適化されない新しいバージョンの ZeroMemory (memset のラッパー) が記述されました。
この関数は、新しいバージョンの WinBase.h で使用できます。
アップデートされた Platform SDK をお持ちでない方のために、
以下にコードを示しておきます (これは単なるインライン コードです)。
#ifndef FORCEINLINE
#if (MSC_VER >= 1200)
#define FORCEINLINE __forceinline
#else
#define FORCEINLINE __inline
#endif
#endif
...
FORCEINLINE PVOID SecureZeroMemory(
void *ptr, size_t cnt) {
volatile char *vptr = (volatile char *)ptr;
while (cnt) {
*vptr = 0;
vptr++;
cnt--;
}
return ptr;
}
このコードは、
コンパイラがコードを最適化しないように volatile 手法を使用しています。
すべての ZeroMemory または memset の呼び出しを単純にこのコードに置き換えないでください。
このコードは非常に低速です。
機密データまたは重要なデータを処理しているところを探し、
SecureZeroMemory を使ってそのデータを無効にします。
コードの最適化を無効にする
上記の 2 つの例で問題になるのは、
これらの解決策が現時点でのみ適切に機能することです。
オプティマイザの開発者は、
常にコードのサイズを小さくし、速度を向上することを考えています。
今後 3 年ぐらいで、
誰かが volatile ポインタ コードを安全に最適化する方法を考え出すかもしれません。
この問題を解決する簡潔な方法は、
データを無効にする関数を含むソース コード ファイルを 1 つ作成し、
そのファイルの最適化をオフにすることです。
Visual C++ でこれを実現する簡単な方法は、
ファイルの先頭に以下の行を追加することです。
#pragma optimize("g",off)
これは、
コンパイラがこのファイル内に別の #pragma 最適化ディレクティブを見つけるまで、
このファイルのグローバル最適化をオフにします。
グローバル最適化 -Og (Ox, -O1 および O2 によって示されます) は、
Visual C++ が使用されていない記憶域を削除するために使用するものです。
ただし、グローバル最適化は適切な最適化なので、
このファイル内のコードは最小限にとどめるようにしてください。
また、問題の関数呼び出しを optimize("",off) と optimize("",on) で囲むように配置することもできます。
悪いニュースはこれぐらいにして、
良いニュースを見てみましょう。
良いニュース: クロス サイト スクリプティングの問題点を軽減する
Windows Security Push 中に、
攻撃を軽減する方法、
または少なくとも攻撃の機会を減少させる方法、
および攻撃が行われても損傷の可能性を少なくする方法を議論するために多くのメンバが集まりました。
最も議論された話題の 1 つがクロス サイト スクリプティング攻撃 (XSS) でした。
私はここで XSS の問題を概説するつもりはありません。
この問題をご存知ない方は、
「出力が信用できなくなる時: クロスサイト スクリプティングの解説」 をお読みください。
私はそこで多くの正当な議論を聞くことになりました。
サーバー チームのメンバはブラウザ チームのメンバに対して、
「このようなスクリプト コードを実行すべきではない。」と指摘しましたが、
ブラウザ チームのメンバは「適格な出力だけを提供すべきだ。」と反論しました。
議論は延々と繰り返され、問題が解決することはありません。
率直に言えば、私は非難することには関心がありません。
危機に直面しているのは私たちの共通のクライアントです。
したがって、私たちすべてが問題を解決するために協力すべきです。
相手を指差し、非難するために費やしているすべての時間と労力を問題点の解決に費やしていれば、
このコラムは必要なく、
「プログラマのためのセキュリティ対策テクニック
(原題 Writing Secure Code)」 は誰の書棚にも存在しなかったでしょう。
Microsoft Internet Explorer チームは、
Cookie をスクリプトなしとしてマークする考えを提案しました。
Cookie とは何かを少し考えてください。
Cookie はサーバーが理解するあいまいなもので、
クライアントは理解しません。
したがって、"一般的に" クライアント コードは Cookie にアクセスすべきではありません。
すべてではありませんが、ほとんどの XSS 攻撃は、
Cookie データを公開することに関係しています。
したがって、
サーバーがクライアントがアクセスすべきでないことを示すことをマークでき、
クライアント ブラウザにこのポリシーを強制できる場合は、
問題を軽減するのに役立つでしょう。
これは、いい加減な XSS に悩ませられるコードを書き続けることができることを意味していないことに注意してください。
この Cookie オプションは、
情報開示の脅威を削減するにすぎない、小さな保証ポリシーと考えてください。
Internet Explorer 6.0 SP1 (Microsoft Windows® XP SP1 および Windows Update サイトから入手できます) は、 HttpOnly とマークされた Cookie であり、JavaScript のようなクライアント サイドのスクリプト コードで、その Cookie (例、document.cookie) に対する読み取りの試みを検出した場合に、Internet Explorer は空文字列を返します。
これにより、XSS 攻撃によって、悪意のあるコードが悪意のあるサイトに Cookie データを送り返すということを防ぎます。もちろん、通常どおり、Cookie は送信元のサーバーと受け渡しが行われます。スクリプト コードを使用するブラウザが Cookie を読み取れないだけです。
Cookie は HTTP レスポンス ヘッダーを使ってクライアントに設定されます。
以下にこのヘッダーで使用する構文を示します。
Set-Cookie: <name>=<value>[; <name>=<value>]
[; expires=<date>][; domain=<domain_name>]
[; path=<some_path>][; secure][; HttpOnly]
HttpOnly 属性は大文字と小文字を区別しないことに注意してください。
以下のように、
ASP ページや ASP.NET ページを更新することによって HttpOnly オプションを設定できます。
Response.AddHeader("Set-Cookie","Name=Michael; path=/; HttpOnly; Expires=" + CStr(Now))
および
HttpCookie cookie = new HttpCookie("Name", "Michael");
cookie.Path = "/; HttpOnly";
Response.Cookies.Add(cookie);
または、
以下のコードですべての送信 Cookie に対して自動的にタスクを実行する ISAPI フィルタを作成できます。
#include <windows.h>
#include <httpfilt.h>
#include "tchar.h"
#include "strsafe.h"
// HttpOnly 部分
DWORD WINAPI HttpFilterProc(
PHTTP_FILTER_CONTEXT pfc,
DWORD dwNotificationType,
LPVOID pvNotification) {
// ハードコードした Cookie 長 (2K バイト)
CHAR szCookie[2048];
DWORD cbCookieOriginal = sizeof(szCookie) / sizeof(szCookie[0]);
DWORD cbCookie = cbCookieOriginal;
HTTP_FILTER_SEND_RESPONSE *pResponse =
(HTTP_FILTER_SEND_RESPONSE*)pvNotification;
CHAR *szHeader = "Set-Cookie:";
CHAR *szHttpOnly = "; HttpOnly";
if (pResponse->GetHeader(pfc,szHeader,szCookie,&cbCookie)) {
if (SUCCEEDED(StringCchCat(szCookie,
cbCookieOriginal,
szHttpOnly))) {
if (!pResponse->SetHeader(pfc,
szHeader,
szCookie)) {
// 安全にできない - cookie を送信しません!
pResponse->SetHeader(pfc,szHeader,"");
}
} else {
pResponse->SetHeader(pfc,szHeader,"");
}
}
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
みなさんもご存知でしょうが、
あえてお話しておきます。
これは XSS の問題を解決しません!
脆弱性を一部軽減するだけです。
適切なサーバー側プログラミング技法に置き換わるものではありません。
ブラウザ 開発会社へのお願い
これはかなり低レベルの技術アプローチを使用して私たちの共通のユーザーにある程度の保護を提供するので、
Internet Explorer だけでなく、
その他のブラウザ 開発会社が HttpOnly Cookie 拡張を採用することをお願いします。
すべてのサーバー アプリケーションがすぐにこの問題を解決するとは期待できないので、
このことをお願いしています。
HttpOnly はこの巧妙な問題点の合理的で、効果のある解決策です。
セキュリティの欠陥を発見する
前回の記事のバグは皆さんが発見できたと思います。
それはバッファ オーバーランでしたが、
通常の脆弱性とやや異なっていました。
前回のコードは memcpy や strcpy などのバッファ コピー関数を使用するのではなく、
攻撃者は変数インデックスと d に基づいてメモリのどこにでもデータを書き込めました。
では、今月のコードの欠陥を探してください。
次のコードは、SYSTEM として実行されるサービスからのものです。
このコードはユーザーの代わりにファイル ベースの要求を行います。
bool WritePipeDataToFile(HANDLE hPipe) {
bool fDataWritten = false;
ImpersonateNamedPipeClient(hPipe);
HANDLE hFile = CreateFile(...);
if (hFile != INVALID_HANDLE_VALUE) {
BYTE buff[1024];
DWORD cbRead = 0;
if (ReadFile(hPipe,
buff,
sizeof(buff),
&cbRead,
NULL)) {
DWORD cbWritten = 0;
if (WriteFile(hFile,
buff,
cbRead,
&cbWritten,
NULL)) {
if (cbRead == cbWritten)
fDataWritten = true;
}
}
if (hFile) CloseHandle(hFile);
}
RevertToSelf();
return fDataWritten;
}
Michael Howard は、
Microsoft の Secure Windows Initiative グループの Security Program Manager であり、
「プログラマのためのセキュリティ対策テクニック
(原題 Writing Secure Code)」 の共著者です。
また、「Designing Secure Web-based Applications for Windows 2000」の著者です。
彼の人生における興味は、人々がセキュアなシステムの設計、構築、テスト、
およびドキュメント化を行えるように手助けをすることにあります。
お気に入りのセリフは、「ある人にとっての機能は、別の人にとっての悪用の手段となる」というものです。
|