| « ホワイトリストとブラックリスト - Firewallの場合 | 乱数生成器の取扱い - PHPとPython » |
ホワイトリストと出力
先週末、まっちゃ445というセキュリティ勉強会にお呼び頂きました。未修正のPHP4/5の脆弱性やパネルディスカッションのパネラーとして話をさせて頂きました。関係者の皆様、いろいろ参考になりました。ありがとうございました。
この勉強会の主題の一つがWeb Application Firewall(WAF)でした。パネルディスカッションのテーマもWAFでした。進行役にはサイバー大学の園田さん、パネラーにはサーボーズラボの竹迫さん、HASHコンサルティングの徳丸さん、そして私の4人でパネルディスカッションを行いました。
3人ともWAFの効用や限界に意見の相違はなかったのですが、私と徳丸さんにはシステム開発に於けるブラックリストとホワイトリストの使い方には意見の相違があったと思います。うまく議論をまとめられなかったのでブログに書きます。
Follow up:
出力時におけるホワイトリストの利用
議論を明確にするためにまず、ホワイトリストとブラックリストを定義します。
ホワイトリスト = 許可リスト
ブラックリスト = 拒否リスト
「リスト」ですので列挙可能なもので、アクセス/利用を可否を設定するものであれば、何でもホワイトリストかブラックリストで制限できます。
パネルディスカッションでは入力におけるホワイトリストとブラックリストの効用や使い方が議論になりませんでしたが、出力におけるホワイトリストの利用については意見が異なりました。
徳丸さんは出力におけるホワイトリストの利用は理解できない、ホワイトリストではない、と繰り返されていました。パネルディスカッションの議論からは
- エスケープ無しに出力する変数を列挙するのはホワイトリストではない
- ホワイトリストのチェック対象は変数の中身であり、変数自体の選別するものではない
- そもそも出力にホワイトリストと言う考え方が理解できないし、用語の使い方が間違っている
といったご意見だったと思います。
私の意見は「エスケープ無しでの出力を許可する変数の列挙」はホワイトリストだと考えています。
- 変数自体もリストとして列挙可能(変数の内容の列挙だけがリストではない)
- エスケープ無しで出力する変数は特権を持つ変数
- 先の2つの特性により、出力時に変数を選別するホワイトリストを適用可能
出力における変数のホワイトリスト化は正規表現や許可リストの実体を作成するのではない事(エスケープされていない変数がホワイトリストに載っている変数(許可した変数)と考える)、ホワイトリスト=入力チェックと考えていると私の意見は非情に分かり辛かったと思います。
出力時にホワイトリストを適用するメリット
ソースコード監査を行っている方なら理解されていると思います。データを出力する際に、出力先のシステムが誤作動しないようにエスケープ処理をしていないデータが沢山あると安全性を保証し辛くなります。例えば、HTMLやSQLの出力にエスケープ無しで
print $data1 . $data2 . $data3 . $data4 . $data5;
と記載されていたとします。コード監査ではセキュリティホールの検出も行う場合がほとんどなので、$data1から$data5全てについてデータの起源から加工のされ方を検証しなければならなくなります。
この検証作業は結構手間がかかります。データベース、ファイル、システムが自動的に初期化したデータなどに保存されているデータであっても、起源が外部システムからの入力であったり、バリデーション済みでHTMLに直接出力しても大丈夫であっても、実際には途中で改変可能あったりする、などのケースが無いか調べなくてはなりません。
出力時にエスケープしない特権的な変数をホワイトリスト化し、最小限の特権的変数以外の変数全てを出力先の仕様にあったエスケープを行えば、コード監査が非常に行い易くなります。変数が保持するデータの起源や変更の可能性を検証する必要性が無くなるので当然です。
コード監査が容易になるということは、安全性の確保が容易になる事、セキュリティレベルの向上を意味します。
ブラックリストよりホワイトリスト
セキュリティの基本に「最小権限の原則」と呼ばれるコンセプトがあります。詳しく解説しませんが、ブラックリストを作成するよりホワイトリストを作成した方が最小の権限を与えやすいです。
インターネットのコンテンツを利用したり場合と違って、システムを構築する場合はほとんどの事項を制御下に置く事ができます。自分で制御できる場合はホワイトリストによって「最小権限の原則」を維持できるようにする事が基本です。
ホワイトリストが適用できない場合(リソース的な制限を含む)に限ってブラックリストを利用するようにしなければなりません。
ブラックリストは無用ではない
ブラックリストよりホワイトリストの作成が優先されるべきですが、ホワイトリストだけでシステムは作れません。繰り返しセキュリティ脆弱性を攻撃したり、DoS攻撃を仕掛けてくるようなIPアドレスはブラックリストを作成して対処すべきです。
おまけ
懇親会でSQL文を作成する場合に全てのパラメータを文字列としてエスケープするのは、気持ちが悪いし不必要なのでは?という意見も頂きました。この話題のQ&Aを書いておきます。一部話していない事も追加しています。
Q: 全てのSQLをパーサでこのような書き方がサポートされるのか?
A: DBMSがサポートしていないなら仕方ありませんが、オープンソース系DBMSはサポートしています。
Q: 整数型の場合、整数にキャストで十分(sprinf, (int)でキャスト)では?
A: データベースの場合、IDは通常64bit変数であることが多い。フロント系のWebサーバではint型が符号付き32bit整数である事が多く、著しく狭い範囲しか表現できないので好ましくない、と言うよりキャストで処理すべきでない。
Q: エスケープせずにバリデーションすれば良いのでは無いか?
A: バリデーションでもOKです。しかし、できるだけ単純なセキュリティ対策がないか考慮し、その結果全てのパラメータを文字列として取り扱う事をお薦めしています。
Q: 数値を文字列をして扱う単純なSQL文を使ってベンチマークすると、数値として記述するよりかなり遅いシステムもある。他のシステムでも遅くなるはずだが?
A: セキュリティ対策はオーバーヘッドが必要です。0.01秒が0.02秒や0.05秒になってもより安全性の高いコーディングの方が好ましいと考えています。
Q: DBの処理が遅くなることは大規模システムでは問題では?
A: そもそもクエリで最高のパフォーマンスを求めるのであれば、動的にクエリを生成するのではなく、プリペアードクエリを利用すべきだと考えています。
Q: 文字列として扱う事が気持ち悪いのですが?
A: HTML出力の際に、直接出力を許可した変数以外全てをエスケープすべき、と意見した時も同じような反応でした。慣れの問題だと思います。
Q: PDOのようなアクセス抽象化ライブラリを使った方が良いのでは?
A: 別に構いませんが、基本的なAPIを利用したエスケープ方法も知っているべきだと考えています。抽象化ライブラリではバイナリ型のカラムに別のエスケープ方式を使えなかったり、某フレームワークのアクセス抽象化ライブラリでは正しくエスケープせず、攻撃が可能となるようなエスケープを許していたりしました。
11 comments
「おまけ」に関して、小生の意見は以下のブログに書いておりますのでご覧ください。お忙しいようであれば、一番最近の「まとめ」をごらんいただければよろしいかと。
数値項目に対するSQLインジェクション対策のまとめ
http://www.tokumaru.org/d/20070924.html#p01
SQLの「暗黙の型変換」で実行速度が遅くなるのはどのような場合か
http://www.tokumaru.org/d/20070614.html
数値項目に対するSQLインジェクション対策
http://d.hatena.ne.jp/ockeghem/20070503/1178208149
数値リテラルをシングルクォートで囲むことの是非
http://d.hatena.ne.jp/ockeghem/20070502/1178042280
変数に型のない言語におけるSQLインジェクション対策に対する考察(1)
http://d.hatena.ne.jp/ockeghem/20070501/1178039408
それを「ホワイトリスト」と呼ぶから意味がわからない、と言われるのではないでしょうか。
ホワイト(ブラック)リストというのは、「素性のわからない何か」に対して、それがリストに含まれるかどうかで「ホワイト(ブラック)」か判断するもの、というのが一般的な意味でしょう。
変数というのはプログラマが自分で作った「素性のはっきりしたもの」で、その変数をどう扱うかを決めるのもプログラマなので、その扱いを決めるためのリストがあったとしてもそれは「ホワイト(ブラック)リスト」ではないと思います。
> A: バリデーションでもOKです。しかし、できるだけ単純なセキュリティ対策がないか考慮し、その結果全てのパラメータを文字列として取り扱う事をお薦めしています。
「バリデーションでもOK」ではなくバリデーションを行った上でこの手法をとるならわかりますが、バリデーションの代わりにはなりません。クエリの失敗は想定していない状況でのみ起こるようにすべきで、単純な入力値の誤りでクエリの失敗が引き起こされるべきではありません。
セキュリティ以前の問題として、クエリの失敗がユーザーのtypoなのか致命的な別の問題なのか、いちいちデータベースが返すエラーメッセージを解析するんでしょうか。そんなことをするよりはバリデーションの段階ではじいた方が確実です。
そもそも、大前提として今や「変数を埋め込んだSQL」を用いること自体が間違いですが。変数は全てバインドパラメータで渡すべきです。より「単純な」方法なのになぜこちらを強調されないのでしょうか。
(当然この場合でも入力値の検証は必要です)
ホワイトリストとはリストの作り方(列挙の手法)のコンセプトです。出力の場合、特権(直接出力)を認める変数を列挙すればホワイトリストと考えれば自然だと思います。変数の扱いをホワイトリストの定義に従って決めれば良い、という単純な考え方です。
分り辛ければリストに従って出力をエスケープする関数がある事を想定すれば分り易いかも知れません。
とにかくアプリケーションにダメダメなコードが多い理由の一つは
>「素性のはっきりしたもの」で、その変数をどう扱うかを決めるのもプログラマ
といった考えに基づいて作っていたからです。数えきれないほどの失敗事例があります。
プログラマが勝手に決めるのでは無く、直接出力が必須かつ確実に安全な変数だけ直接出力するよう、プログラマの頭の中で変数が「白」なのか「黒」なのか良く考えてから直接出力する変数の列挙方法がホワイトリストの作り方と同じと考えています。
この考え方に従って、もっと分り易く、適切な用語(XSSよりJavaScriptインジェクション、HTMLインジェクションのように)があるのであればその用語を使っても良いのかもしれませんがより適切な用語は、今の所思い付きません。
懇親会のQ&Aなので当たり前の事は省略しています。フェールセーフやフールセーフ対策として安全に動的に生成されたSQLを実行する方法を議論していまた。
当たり前のセキュティ対策は既に行っている事が前提です。セキュリティの基本コンセプトである多重のセキュリティとして、クエリ実行前にチェックしてもOK、としています。
要するに入力バリデーションは当たり前で既に必要な対策済みである事が大前提、それでもチェック漏れや勘違い等でエスケープやバリデーションしないとSQLインジェクションが成功してしまうケースも考えられるので、多重のセキュリティとして出力前にチェックしてもOK、という事です。
懇親会の時も私が明確にフェールセーフ/フールセーフ対策と言っていなかったので理解されていない方もいらしたかもしれませんが、フェールセーフ/フールセーフを突き詰めて考えると、全部エスケープかプリペアードクエリが最良だと考えています。
これでもテーブル名やフィールド名の問題が残ります。ORMでも注意が必要になるケースもあります。これらは仕方ないので教育で対処するしか無いでしょう。
ではなおさら"バリデーション*でも*OK"というのは意味がわかりません。バリデーションは当たり前のことではないのでしょうか。さらにそれにつなげて"しかし~"と書かれているので「バリデーションではじく代わりに使え」と読めてしまいます。さらに言うと、
http://gihyo.jp/dev/serial/01/php-security/0014
ではこれが"攻撃目的の入力の検出に使える"と書かれています。
当たり前の事をやった上でのプラスアルファであれば、検出できるのは攻撃目的の入力ではなく入力値の検証漏れではないでしょうか。前者であれば「検出できて良かったね」ですが後者では「プログラムに致命的なバグがある」ということになります。起きたことは同じでも意味は大違いです。
> とにかくアプリケーションにダメダメなコードが多い理由の一つは
> >「素性のはっきりしたもの」で、その変数をどう扱うかを決めるのもプログラマ
> といった考えに基づいて作っていたからです。
という話をするから議論がかみ合わなくなるのでは。
> この考え方に従って、もっと分り易く、適切な用語があるのであればその用語を使っても良いのかもしれませんがより適切な用語は、今の所思い付きません。
だからといってホワイトリストと呼ぶのは適切ではない、という話です。
「全部エスケープしてから出力」という手法について主張されたいのであれば、ホワイトリストという誤解を招く言葉を使わない方がいいでしょう。
「変数を列挙したものもホワイトリストと呼んでもいいではないか」という主張をしたいのであれば、現在の方法への批判とかどうセキュリティに貢献するかという話はされずに、ホワイトリストとはなんぞやという定義に絞って話をされたほうがいいでしょう。
どこでどれくらいのエラー検出をするのか? という問題はシステム設計の問題です。
SQL文生成の場合、既にバリデーション済みの変数と想定されるもの(例えば、IDなど)に再度同じバリデーション処理(例えば、IDの場合、符号無し整数であること)を行うのはオーバーキルだ、と考えて良いシステムの方が多いです。出力の場合は「バリデーションではじく代わりに使え」(全部エスケープする)で十分でしょう。不十分と考えられるシステムであれば出力時もバリデーションすればよいでしょう。
フェイルセーフとフェイルディテクトは別の考え方になります。私が常に言っているのは入力時に厳しいバリデーションを完全に行いましょう、そして出力時には万が一バリデーションが不十分であったり、予想していない経路で改変された変数があっても、セキュリティホールにならない様に出来る限り安全に失敗するフェイルセーフを考えましょう、という事です。ほとんどのシステムでは出力時の不正な入力の検出はオプション項目だと思っています。
>> >「素性のはっきりしたもの」で、その変数をどう扱うかを決めるのもプログラマ
>> といった考えに基づいて作っていたからです。
>という話をするから議論がかみ合わなくなるのでは。
何故有用なのか理解されていないように感じたので事実を書いたまでです。
>> この考え方に従って、もっと分り易く、適切な用語があるのであればその用語を使っても良いのかもしれませんがより適切な用語は、今の所思い付きません。
>だからといってホワイトリストと呼ぶのは適切ではない、という話です。
明確にホワイトリストの定義を行い、出力時にも同じくその考え方が使え、ホワイトリストの考え方を適用した方が安全なコードになる、と言っています。
どうして「出力時にホワイトリストの考え方を適用するのが適切ではない」と考えられるのですか?
用語として利用方法がおかしいと思われるのであれば、ホワイトリストの定義を書かれて、何故適切でないか書かないと議論は進まないと思います。
そんな話はしていませんよ。用語の使い方についてしか述べていないのに、「手法」について反論されるので話がかみ合わないのです。
>そんな話はしていませんよ。用語の使い方についてしか述べていないのに、「手法」について反論されるので話がかみ合わないのです。
もともと「用語の正しい定義と利用」について異論があるなら「用語の正しい定義]を示し、正しい用法を例示しなければなりません。それをしないで「同じ手法」で「出力もより安全にできる」と解説してことがおかしいと言われても困ります。
これらを示さずに「間違っている」と言われても議論が噛み合ないのは当然ではないでしょうか?
間違っている、と指摘されているのですから、どこがどう間違っているのかご指摘頂けると助かります。この件に関する議論で明確な用語の定義や抽象的でない論理的なご意見はまだ頂いていません。