まずホワイトリストの基本中の基本は”デフォルトで全て拒否する”であることに注意してください。全て拒否した上で許可するモノを指定しないとホワイトリストになりません。
例えば、CSPはホワイトリストで不正なJavaScriptの実行を防止する仕組みです。2016年のGoolgeの調査によると95%のCSP定義が、実際にはJavaScriptインジェクション脆弱性の防止に役立っていない、との調査結果を発表しています。原因はホワイトリストの作り方の基本、全て拒否した上で許可するモノを指定する、を理解していないことにあると考えられます。1
私はいつも基本的に能動的なセキュリティ対策2を選択するようにお勧めしています。能動的なセキュリティ対策とは全ての入力値の厳格なバリデーション処理であり、出力時に過剰とも言えるエスケープ処理です。入力/出力の両方にホワイトリスティング対策をお勧めしています。3
ホワイトリストの作り方
ホワイトリストの作り方はシンプルです。
- デフォルトで全て拒否する
- 許可して良いモノだけを許可する
ホワイトリストの作り方(バリデーションの作り方)にはケースバイケースで幾つもの指針が有りますが、最も重要と言える指針は
- 許容する入力・出力は必要最小限だけに留める。
- 許容した入力・出力は確実に安全である事を保証する
- もし不正な入力があった場合は必ず記録し、必要であればプログラムの実行を停止する
です。出力の多くはバリデーションでなく、エスケープやエスケープが必要ないAPIを利用することで対応できます。しかし、中にはバリデーションでしか完全な安全性を保証できないモノもあります。代表例はOSコマンドのエスケープです。
入力として安全であると保証できるモノ、つまり入力として妥当なモノ、が出力時に安全である必要はありません。出力時の安全性確保は出力コードが責任を持ちます。
稀に誤解されるので書きますが「プログラムの実行を停止する」とは入力間違いで実行を停止するのではなく、不正な入力で実行停止する事を意味します。
もう少し具体的に、テキスト出力に対するXSS対策で有れば、出力するテキストの文字エンコーディングが正しい事を検証し、かつ文字エンコーディングに合ったエスケープ方法で全ての特殊文字をエンコーディングする事です。HTMLの属性をユーザが指定可能にするのであれば、その属性に対して許可する値を厳格に定め、それ以外の入力は拒否する事です。同じHTML出力するから、といって同じように取り扱えません。テキストならテキスト、属性ならその属性、スタイルならスタイル、JavaScriptならJavaScript、E4XならE4X、DBMSならDBMS、メールシステムならメールシステムの為の「正しいホワイトリストの作り方」があります。
参考:OWASPセキュアコーディング習慣 – クイックリファレンスガイド
このエントリであえて「ホワイトリストをどう作るか?」書いていない理由は技術革新の早いWebシステムで具体的なホワイトリストの作成方法を書いても無意味となる可能性があるからです。さらに、一つのシステムについてどのようにホワイトリストを作るか解説しても片手落ちです。それよりも最も防御が難しいXSSの攻撃方法を紹介したXSS Cheat Sheatを参照し、自分で最新の攻撃方法を知った上で自分でホワイトリストの作り方を知った方が何倍も価値があり、時間の経過と共に訪れる情報の陳腐化も防げると考えているからです。
WAF(Web Application Firewall)とホワイトリスト
WAF(Web Application Firewall)をホワイトリスト形式で設定してWebアプリケーションの安全性を向上させる事も可能ですが本末転倒です。アプリケーションの入出力バリデーションをホワイトリスト化する方が筋であり、順番が逆です。アプリケーションがホワイトリスト方式でのバリデーションを行えば、WAFなどはブラックリスティングのみで利用しても構わないですし、高価なWAFを購入して速度を落とす必要もありません。簡易WAFと動的フィルタリングを組み合わせる方がコスト的にも合理的です。
PCIDSS(Payment Company Industry Data Security Standard)では「WAFまたはコード監査」をコンプライアンスの必要条件としています。WAFの利用が認められているのは、コード監査が間に合わないユーザに対する救済措置の意味合いがかなり強いと考えています。
WAFはゼロデイ攻撃からシステムを防御する仕組みとして欠かせません。私もWAFの必要性は認めています。しかし、PCIDSSで「WAFおよびコード監査」の両方を要求していません。これはPCIDSSを策定したセキュリティ専門家も私と同様にWAFの効果を限定的と評価している事の現れでしょう。(将来的には両方必要要件になるかも知れません。しかし、その場合はWAFの機能要件は緩和される可能性が高いと思います)
コード監査+簡易WAF
コード監査+簡易WAFの方がコストメリットが高い、とする意見に疑問を持たれた方の為に記載します。まずコード監査だけではコストが高く付くケースもあります。例えば、アプリケーション設計自体に問題がありセキュアな設計になっていない場合、コード監査後の修正と検証にWAF導入より多くのコストが必要となる場合があります。
しかし、一旦セキュアな設計+コード監査+簡易WAF(ブラックリスティング方式)でシステム構築を行えば、わざわざ設定ミスがありがちなホワイトリスティング方式によるWAF設定など必要なくなります。ホワイトリスティング方式よるWAFの場合、アプリケーションのちょっとした改修でもWAF設定の修正と設定の検証が必要になります。ホワイトリスト方式のWAFは導入すればOKという物ではなく、常にメンテナンスが必要なシステムです。しかも、このエントリでも紹介しているようなメンテナンス時のちょっとしたミスでホワイトリストがブラックリストになってしまう危険性もあります。
総合的なコストが”セキュアな設計+コード監査+簡易WAF”と”ホワイトリスティング方式のWAF”の導入とどちらが低くなるかはケースバイケースですが、直感的に多くのシステムで”セキュアな設計+コード監査+簡易WAF”の方が長期的なコストは確実に安く、リスクも少なくなる可能性が高い、事が解ると思います。
この追記に対応するエントリも作りました。こちらもどうぞ。
投稿当初のエントリ
(以下から元のエントリです)
スクリプトインジェクション(XSS)防止にブラックリストが機能しない事は明らかです。ホワイトリストはどう作れば良いか参考となるリンクです。どう作るか書いておいても古くなる可能性が高いので、どこを参考に作れば良いか参考URLを書いておきます。
以下のリンクの情報からスクリプトのインジェクションがどのように行えるかを参考にホワイトリストを作れば概ね間違いないと思います。
XSS Cheat Sheet
https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
スクリプトインジェクション手法の中でも有名な手法を集めているサイトです。XSSロケータと呼ばれている文字列はスクリプトインジェクション脆弱性検出に重宝します。よくある脆弱性であればこの文字列で簡単に検出できます。
ロケータ1
';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//--></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>
ロケータ2
'';!--"<XSS>=&{()}
sla.ckers.orgのXSSフォーラム
http://sla.ckers.org/forum/list.php?2
今気が付いたのですが日本のXSSコンテストの情報も掲載されていますね。
いろいろな方が集まって変わった手法でスクリプトを実行する方法を記載しています。例えば、現時点で以下のスレッド
http://sla.ckers.org/forum/read.php?2,15812,20302#msg-20302
の最後にはdata URIを使用したサンプルが載っています。
Data urls tricks:-
<a href=data:text/html;base64,PHNjcmlwdD5hbGVydCgvWFNTLyk8L3NjcmlwdD4>test</a> <a href= data:text/html;base64,PHNjcmlwdD5hbGVydCgvWFNTLyk8L3NjcmlwdD4>test</a> <iframe src=data:text/html;base64,PHNjcmlwdD5hbGVydCgvWFNTLyk8L3NjcmlwdD4>This one shows that FF reads base64 and ignores every character after until the payload:-
<a href=data:text/html;;base64a,PHNjcmlwdD5hbGVydCgvWFNTLyk8L3NjcmlwdD4>Test</a>Another example:-
<a href=data:text/html;;base64ANYTHING,PHNjcmlwdD5hbGVydCgvWFNTLyk8L3NjcmlwdD4>Test</a>
スクリプトインジェクションに多少詳しい方ならdata URLを使ったフィルタすり抜けのテクニックはよくご存知だと思います。
正規表現は控えめに
下手な正規表現だとホワイトリストのつもりでもスクリプト実行が出来てしまう場合があるので注意が必要です。可能な限り厳格かつ間違いを防ぐため正規表現の使用は控えめにすると良いと思います。
どのようなデータが利用されるか決まっている場合は、データリストを作成してリストと一致するか確認するようにします。例えば、CSSスタイルならstyle_a, style_b, style_cだけ許可するなら名前のリストを作って一致するものだけ許可するようにします。
正規表現を使ってこのスタイルをチェックするために、”style_” + 任意の一文字となる
style_.
を使うとスクリプトインジェクションに脆弱になってしまう可能性があるのは言うまでもありません。
そもそも、任意の一文字なら
style_?
でしょう。とか、aからcだけなので
style_[a-c]
でしょう、と考えられるかも知れません。コード監査時には正規表現も重点的な監査対象です。正規表現は少ないに越したことはありません。「デフォルトでエスケープする」と同じで監査し易いコードはより安全なコードになります。
最初に書いたプログラマは正規表現(+セキュリティ)のエキスパートで[a-c]と書いておいても、いつ他の正規表現に不慣れなプログラマが「.」に書き換えてしまうか分かりません。「.」くらいならまだ良いですが、
style_.*
「.*」に書き換えられたらどうしようもありません。
- このエントリは比較的よく読まれているので、解りやすいように修正しています。当初このエントリの書き方はあまり親切ではありませんでした。「ホワイトリストはどう作る?」と題して、その書き方を解説していなかったからです。しかし、それは実際にXSS Cheat Sheetを読み、理解してもらい、その上で安全なホワイトリストはどう作るか自分で考えて欲しかったからです。当初のエントリには誤解が多く、その理由にはホワイトリストとして作ったチェックが、簡単な変更でブラックリストになってしまう事を紹介したことが一因でしょう。 ↩
- 能動的なセキュリティ対策とは、問題がある(あった)から対策する、のではなく、問題がないように対策する、ことです。ホワイトリストによる全ての入力値のバリデーション、デフォルト出力エスケープが能動的なセキュリティ対策にあたります。 ↩
- 過剰過ぎるエスケープ処理はセキュリティ問題の原因になるので多重エンコーディングやエスケープはしないように。念のため。 ↩