先週末、まっちゃ445というセキュリティ勉強会にお呼び頂きました。未修正のPHP4/5の脆弱性やパネルディスカッションのパネラーとして話をさせて頂きました。関係者の皆様、いろいろ参考になりました。ありがとうございました。
この勉強会の主題の一つがWeb Application Firewall(WAF)でした。パネルディスカッションのテーマもWAFでした。進行役にはサイバー大学の園田さん、パネラーにはサーボーズラボの竹迫さん、HASHコンサルティングの徳丸さん、そして私の4人でパネルディスカッションを行いました。
3人ともWAFの効用や限界に意見の相違はなかったのですが、私と徳丸さんにはシステム開発に於けるブラックリストとホワイトリストの使い方には意見の相違があったと思います。うまく議論をまとめられなかったのでブログに書きます。
追記:ホワイトリストの基本中の基本は”デフォルトで全て拒否する”であることに注意してください。全て拒否した上で、許可するモノ、を指定しないとホワイトリストになりません。
出力時におけるホワイトリストの利用
議論を明確にするためにまず、ホワイトリストとブラックリストを定義します。
ホワイトリスト = 許可リスト
ブラックリスト = 拒否リスト
「リスト」ですので列挙可能なもので、アクセス/利用を可否を設定するものであれば、何でもホワイトリストかブラックリストで制限できます。
パネルディスカッションでは入力におけるホワイトリストとブラックリストの効用や使い方が議論になりませんでしたが、出力におけるホワイトリストの利用については意見が異なりました。
徳丸さんは出力におけるホワイトリストの利用は理解できない、ホワイトリストではない、と繰り返されていました。パネルディスカッションの議論からは
- エスケープ無しに出力する変数を列挙するのはホワイトリストではない
- ホワイトリストのチェック対象は変数の中身であり、変数自体の選別するものではない
- そもそも出力にホワイトリストと言う考え方が理解できないし、用語の使い方が間違っている
といったご意見だったと思います。
私の意見は「エスケープ無しでの出力を許可する変数の列挙」はホワイトリストだと考えています。
- 変数自体もリストとして列挙可能(変数の内容の列挙だけがリストではない)
- エスケープ無しで出力する変数は特権を持つ変数
- 先の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を利用したエスケープ方法も知っているべきだと考えています。抽象化ライブラリではバイナリ型のカラムに別のエスケープ方式を使えなかったり、某フレームワークのアクセス抽象化ライブラリでは正しくエスケープせず、攻撃が可能となるようなエスケープを許していたりしました。