僕が「ホワイトリスト」を採用しなかった訳
ホワイトリストという用語はセキュリティの分野では非常に基本的な用語ですが、セキュアプログラミングという文脈では意外に曖昧な使われ方がされているように見受けます。本エントリでは、ホワイトリストという用語の意味を三種類に分類し、この用語の実態に迫ります。拙著体系的に学ぶ 安全なWebアプリケーションの作り方(以下、徳丸本)では、ホワイトリストという用語を一度も使っていませんが、その理由に対する説明でもあります。
許可されたものの一覧表(第一種ホワイトリスト)
ホワイトリストというくらいですから、本来のホワイトリストは列挙されたものと考えます。そのような説明・定義の例は、IT用語辞典などに見ることができます。以下は日経パソコン用語事典2009からの引用です。
ホワイトリスト
http://itpro.nikkeibp.co.jp/word/page/10032299/
whitelist
ユーザーや管理者などが信頼できると判断したURLやIPアドレス、メールアドレスなどの一覧表のこと。ブラックリストの反対。例えば、迷惑メール対策ソフトが備える「受信許可リスト」がホワイトリストに該当する。受信許可リストにあらかじめ登録したアドレスからのメールは、内容にかかわらず受信する(迷惑メールとは判定しない)。そのほか、URLフィルタリングソフトなどもホワイトリストを備える。
上記は文句のつけようのないホワイトリストの説明ですね。徳丸本では、ホワイトリストという言い方はしていませんが、このような列挙の例は出てきます。同書P136には、様々な列でソートが指定できるSQLの組み立て時に、列名をホワイトリストでチェックする例が出ています。
$sort_columns = array('id', 'author', 'title', 'price'); $sort_key = $_GET['sort']; if (array_search($sort_key, $sort_columns) !== false) { ...
$sort_columnsはホワイトリストを保持する配列ということになります。
ホワイトリストのもっと良い例は、同書P266に、アップロード機能で指定するファイル名の拡張子チェックの箇所にあります。
if ($ext != 'gif' && $ext != 'jpg' && $ext != 'png') { die('拡張子はgif、jpg、pngのいずれかを指定ください');
この箇所は、アップロードしたファイルをPHPやASPなどのスクリプトとしてサーバー上で実行されることを防ぐ方法の説明ですが、拡張子phpやaspを避けるというブラックリストではなく、ホワイトリストによりチェックしています。ただし、この箇所の文脈では、アプリケーション要件として拡張子をチェックしている意味合いが強いです。詳しくは徳丸本を参照ください。
ともあれ、第一種ホワイトリストの概念は徳丸本にも登場しますが、ホワイトリストという用語は使わずに説明しています。
セキュリティ上安全と考えられる書式(第二種ホワイトリスト)
セキュリティ上の要求から決まる安全な書式のことをホワイトリストと読んでいる場合があります。そのように明確に定義している例は少ない*1のですが、ホワイトリスト方式を前面に出している金床本には以下の記述があります(同書P39)。
このような場合に助けとなるのがホワイトリスト方式によるアプローチだ。処理系に関する詳しい知識を持ち合わせなくとも、例えば「アルファベットもしくは数字のみから成り立つ、8文字以内の文字列のみ」のような厳しい基準を設ければ、事実上安全性を保つことができる可能性が高くなる。
この説明は、第二種ホワイトリストに該当します。
徳丸本にも、第二種ホワイトリストは登場します。例えば、同書P238。
ファイル名の文字種の仕様を英数字に限定すれば、ディレクトリ・トラバーサル攻撃に用いる記号文字が使えなくなるので、ディレクトリ・トラバーサルの対策になると考えられます。
同じくP298。
◆evalに与える外部からのパラメータを英数字に限定する
外部から与えるパラメータを英数字に限定できれば、スクリプトの注入に必要な記号文字(セミコロン「;」のほか、コンマ「,」、引用符など多種)が使えなくなるので、スクリプト注入はできなくなります。
似たような説明は、OSコマンドインジェクション脆弱性の保険的対策のところにもあります(P256)。いずれも、ホワイトリストと言う用語を使っていませんが、意味するところは、この第二種ホワイトリストです。
アプリケーション仕様として許可された書式(第三種ホワイトリスト)
ネットや書籍などで見かける「ホワイトリスト」の指す意味がはっきりしない場合も多いのですが、つきつめて考えると、ホワイトリスト=アプリケーション仕様と理解するしかないという場合も多いです。このパターンを第三種ホワイトリストと呼ぶことにします。第三種ホワイトリストは、アプリケーション仕様として認められる書式を示して、その完全一致マッチでチェックするという方式です。
典型的にはメールアドレス、電話番号、郵便番号、ユーザIDなどの書式チェックですが、氏名や住所などの自由書式の入力欄も含まれます。
第三種ホワイトリストの例として、OpenPNE3 セキュアコーディングガイドラインという文書から引用します。
ホワイトリストを使用した入力値検証については可能な限り積極的におこなうようにしてください。ホワイトリストにより入力値をプログラムが期待するもののみに限定できます。
http://www.openpne.jp/developer/secure-coding-guideline/#id7
例によって「ホワイトリスト」の定義はされていませんが、「プログラムが期待するもののみに限定」という表現があることから、ホワイトリスト=アプリケーション仕様であることが分かります。
さらに、OSコマンドインジェクション対策の項にもホワイトリストが出てきます。
他の手段で代替することができず、どうしても実行する外部コマンドをユーザ入力値を利用して構築しなければならない場合、ホワイトリストを用いて実行するべきコマンドをできる限り固定なものにしてください。
http://www.openpne.jp/developer/secure-coding-guideline/#id11
こちらの「ホワイトリスト」は、おそらくアプリケーション仕様ではないようです。「できるだけ固定なものに」とあることから、第一種ホワイトリストを指すような気がしますが、第二種かもしれません。比較的短い文書内でホワイトリストの定義が揺れていますが、このような揺れは珍しいことではありません。
第三種ホワイトリストの次なる例として、CWE(Common Weekness Enumrati、統一的なソフトウェアの欠陥の一覧を定めるプロジェクト)のSQLインジェクション(CWE-89)の説明には、脆弱な例6として以下が紹介されています。
$userKey = getUserID();
http://jvndb.jvn.jp/ja/cwe/CWE-89.html
$name = getUserInput();
# ensure only letters, hyphens and apostrophe are allowed
$name = whiteList($name, "^a-zA-z'-$");
$query = "INSERT INTO last_names VALUES('$userKey', '$name')";
whiteList関数の引数、"^a-zA-z'-$"がホワイトリストを定義する正規表現です*2。リストに続いて以下の説明があります。
【前略】さらに、このホワイトリストでは、データとコマンドの区別をするアポストロフィも許可されています。ユーザが名前にアポストロフィを入力すると、ステートメント全体の構造が変わったり、プログラムの制御フローが変更され、場合によっては機密情報にアクセスされたり、改ざんされる可能性があります。
http://jvndb.jvn.jp/ja/cwe/CWE-89.html
この場合では、ハイフンやアポストロフィは名前として使用される正規の文字として許可するよう要求されています。データやコマンドの誤解釈を避ける代替策として、既存のステートメントを使用するか、全てのデータ及び命令の誤った解釈を防ぐルーチンのエンコーディングの適用が望ましいでしょう。
この説明ではホワイトリストは「安全な文字の集合」ではなく、アプリケーションの仕様ととらえる方が自然です。とくに、「この場合では、ハイフンやアポストロフィは名前として使用される正規の文字として許可するよう要求されています」という箇所が、ホワイトリストがアプリケーション仕様であることを明確に示しています。
徳丸本には、第三種ホワイトリストは頻繁に登場します。「4.2入力処理とセキュリティ」では、入力値のバリデーションをアプリケーションの仕様を基準として受け入れるべき文字種と文字列長の上限を定め、正規表現の完全一致マッチによるチェックを推奨しています。
また、ヌルバイト攻撃やHTTPヘッダインジェクション攻撃への対策は、通常はヌルバイトや改行文字のチェック(すなわちブラックリスト)や除去を推奨している教科書が多いと思いますが、徳丸本では、この第三種ホワイトリストによる検査を勧めています。
ヌルバイト攻撃対策の典型例として、PHP逆引きレシピのP612では、以下のような正規表現によりヌルバイトをチェックすることを勧めています。これはブラックリストですよね。
if (preg_match('/\0/', $array)) { die('不正な入力です'); }
一方、徳丸本は、ヌルバイト攻撃の説明はしています(P77〜P78)が、ヌルバイト攻撃に特化した対策は勧めていません。同書P82には以下のように説明しています。
住所欄や氏名欄などは、通常文字種の制限がなく、文字数のみが指定されている場合が多いと思います。文字種の制限がない場合でも、制御文字が混入していないというチェックをするべきです。これによりヌルバイト攻撃も防ぐことができます。以下に示すスクリプトの正規表現では、[:^cntrl:]というPOSIX文字クラスにより「制御文字以外」という指定をしています。
これに続くサンプルは以下の通りです。
if (preg_match('/\A[[:^cntrl:]]{1,30}\z/u', $addr) == 0) { die('30文字以内で住所を入力してください(必須項目)。改行やタブなどの制御文字は使用できません'); }
「制御文字以外」という文字クラスを用いることで、「制御文字は原則認めないが、改行とタブは例外として認める」というルールを指定しやすくなります。先のサンプルに続いて以下のリストを紹介しています。
preg_match('/\A[\r\n\t[:^cntrl:]]{1,400}\z/u', $comment)
これは、、改行・タブ以外の制御文字を禁止し、1文字から400文字以内という意味になります。
このように、徳丸本では、第三種ホワイトリストを全面的に採用しています。
WAFにおけるホワイトリスト
Webアプリケーションセキュリティ分野の中で、とくにホワイトリストという用語が出てくるのがWAF(Web Application Firewall)においてでしょう。WAFの業界でいう「ホワイトリスト」は、この第三種ホワイトリストに該当します。
以下は、ネット上で見つけたWAFの解説からの引用です。
図3は、SecureSphere WAFでホワイト・リストを設定している画面です。URLごとに、それぞれ許可するHTTPメソッドを定義しているほか、パラメータごとに、それぞれ許可する文字タイプ/長さ/特殊文字を定義しています。現在の多くのWAFがそうであるように、SecureSphereもまた、ホワイト・リストを自動学習します。
http://thinkit.co.jp/story/2010/11/15/1872?page=0,2
ホワイト・リストを用いて前述のSQLインジェクションを防御するやり方は、パラメータの特殊文字として「=」や「>」や「'」を許可しなければよいのです。しかし、ホワイト・リストだけでセキュリティを保つためには、前提としてホワイト・リストの記述内容が適切であることが必要です。適切でない場合は、正常な通信も含めて遮断(誤検知)してしまうからです。
最初のパラグラフでは、ホワイト・リストの自動学習と言う言葉が出てきますが、これは、アプリケーションの仕様を学習するという意味でしょう。このような言い方はWAF全体で共通して見られるものであり、WAFの世界では、ホワイトリストはアプリケーション仕様を指すものと思われます。すなわち、第三種ホワイトリストということになります。
興味深いことに、第2パラグラフに『ホワイト・リストを用いて前述のSQLインジェクションを防御するやり方は、パラメータの特殊文字として「=」や「>」や「'」を許可しなければよい』とあります。この考え方は、発想としてはブラックリストそのものですね。すなわち、ホワイトリストによる対策と呼んでいるものが、必ずしも「安全とはっきりしている文字の集合(第二種ホワイトリスト)」とは限らない良い例となっています。現実問題として、入力値のアプリケーション仕様が「安全な文字」だけで構成されるとは限りません。
ホワイトリストの混乱した説明の例
ホワイトリストの3パターンを説明し終えたところで、明確な区別なしにホワイトリストと言う用語を使っている例を示します。Ajaxセキュリティという書籍のP112には、以下の記述があります。
ホワイトリストフィルタのルールは、甘過ぎても厳格過ぎても困ります。厳格すぎるパターンは、攻撃者としては付け入る隙を見つけにくくなるため、アプリケーションのセキュリティという観点からは容認されます。しかし利便性の観点からは、問題となります。正当なユーザーの実在する電子メールアドレスが拒否された場合、そのユーザーはサイトを利用できなくなり、大きな損害を被るおそれがあります。
試行錯誤の末、電子メールアドレスについては、次のようなルールに落ち着きます。
- アドレスの名前部分は英数字を基本とし、オプションとしてハイフンとピリオドも使用できる。ハイフンまたはピリオドの次には、英数字が続かなければならない
- 名前部分の次には、@記号が続かなければならない
- @記号の次には、アドレスのドメイン部分が続かなければならない。この部分は、最低でも1つ、最高で3つのテキストブロックで構成される必要がある。各テキストブロックは、英数字を基本都市、オプションでハイフンも使用でき、最後がピリオドで終わる。ハイフンの次には英数字が続かなければならない。
- アドレスの最後には.com、.net、.orgなどの有効なトップレベルドメインが、1つだけ置かれなければならない
やれやれ、電子メールアドレスのように単純に思えるものでも、ずいぶん複雑なルールとなりました。
【略】
ここに書かれているホワイトリストは、基本的にはアプリケーション仕様を指していますが、セキュリティの都合により、仕様の方を変えてしまうと言う本末転倒なことをやっています*3。よく「セキュリティを強化すると利便性が犠牲になる」という表現を目にしますが、アプリケーションのセキュリティに関して言えば、正しい対策をとっている限り、セキュリティの都合で利便性を犠牲にしなければならない局面は滅多にないと思います。私は、「セキュリティと利便性が両立しない」というのは、間違った脆弱性対策の結果生まれた神話だと考えています。セキュリティの都合で仕様を制限するケースは、ブログや掲示板で入力できるHTMLタグの制限など、特殊なケースに限られます。
さらに興味深いことに、この部分に以下の(訳注ではない)脚注がついています。
電子メールアドレスを構成する異なる部分に使用できる文字の種類については、RFC822そのほかの標準に詳細がある。
メールアドレス欄はRFC822そのほかの標準に従うとすれば、これはアプリケーション仕様の話ですね。RFCの話題が出てきたところで、メールメッセージについての最新の仕様であるRFC5322に準拠しつつ「危険な」メールアドレスがあり得ることを紹介します。
RFC準拠のメールアドレスには「危険な」ものもある
RFC5322に適合したメールアドレスの中には、SQLインジェクション攻撃が可能なメールアドレスがあり得ます。たとえば、以下がそれです。
'or'1'='1'#@tokumaru.org
このメールアドレスはメールのRFCに準拠しているだけでなく、実際に送受信が可能であることを確認しました。Gmail→Postfix→Dovecot→Becky!というルートでメールを送ってみましたが、特にエラーなくBecky!で受信できました*4。受信メールをBecky!で表示している様子を以下に示します。
すなわち、メールアドレスのように比較的「安全」と見なされているデータ項目であっても、SQLインジェクションという文脈では「危険」となり得ることが分かります。
「ホワイトリスト」を巡る誤解
ここまで見てきたように、ホワイトリストという用語は意外にも幅の広い使われ方をしているため、単に「ホワイトリスト」と言っただけでは、その指す内容は不明確です。
また、「何に対してホワイトか」ということを意識して使う必要があります。最後の例で見たように、RFC5322に対してホワイトなメールアドレスが、SQLインジェクションに対してもホワイトであるとは限りません。これは少し考えれば明白なことで、メールアドレスの仕様を検討する上で、SQLの都合は考えていないからです。データが「安全」か「危険」かは、データが使用される文脈で変わるのです。
まとめ
ホワイトリストのさまざまな使われ方から、この用語を3種類に分類しました。よく「ホワイトリストにより検証すれば確実」などの表現を見かけますが、そもそも第何種のホワイトリストを指すかが明確でない場合が多い上に、第二種の場合ホワイトリストの場合は「安全な文字の定義」が必要である(「英数字に限定すれば安全」など)にもかかわらず、それもない場合が多いようです。
このような状況であるので、「ホワイトリストならば安全」というのは神話に過ぎず、正しい表現のためにはホワイトリストの指す内容と、安全である根拠を明確に示す必要があります。
徳丸本で「ホワイトリスト」という用語を一度も使わなかった理由は、前述のような曖昧性を避けるためですが、ホワイトリストの指す概念自体は一種・二種・三種ともに出てきています。
結論としては、「ホワイトリスト」は今や曖昧な用語であり、「許可する拡張子の一覧」(第一種)とか、「英数字に限定する」(第二種)、「アプリケーション仕様に従ってバリデーションする」(第三種)などと明確に表現することが、よりホワイト(安全)であると私は考えます。
*1:ホワイトリストと言う用語はセキュリティ屋にはあまりにも馴染みのある用語であるせいか、定義して使っている文書をあまり見かけません。しかし、ホワイトリストと言う用語が幅の広い使われ方をしているため、未定義で使うことにより混乱の原因になっています。
*2:正規表現としてはかなりおかしいと思いますが、突っ込まないでおきます。原文 http://cwe.mitre.org/data/definitions/89.html が元々間違っています。
*3:同書のホワイトリストに関する混乱については、書籍『Ajaxセキュリティ』に関する残念なお知らせ - ockeghem(徳丸浩)の日記でも言及しています。
*4:Gmailだけでなく、Yahoo!メール、Docomoケータイ、auケータイからも送信できましたが、Gooメールの場合は、「TOアドレスが不正です。」というエラーになりました。