まず結論から。タイトルの通り「出力対策だけのセキュリティ設計は設計ミス」です。
なぜ「出力対策だけのセキュリティ設計は設計ミス」なのか?
基礎概念から間違っている例
まず最初にセキュアコーディングの考え方から間違っている例を紹介します。
このセキュアコーディングの解説をしようとしているスライドの中では、入力の信頼境界で「無害化」フィルタを使うことがわからない、としています。
入力の信頼境界で「無害化」を行う、とするセキュアコーディングはありません。もしあるとするなら、少なくともそれはISMS/ISO 27000やPCI DSSが要求するセキュアコーディングではありません。
セキュアコーディングで行う入力信頼境界のセキュリティ対策は「入力の妥当性検証」です。その結果として無害化されるパラメーターが多数生まれますが、無害化は入力妥当性検証の目的ではありません。当たり前の事ですが間違えないよう注意しましょう。セキュアコーディングで入力妥当性検証が必須とされている科学的な理由は以下のブログを参考にしてください。
出鱈目な出力を許してしまう(=OWASP的に脆弱なアプリ)
出力対策には「これをやれば十分」となる3原則、エスケープ/API1/バリデーション、があります。出力処理の3原則のどれかを使って出力を無害化すれば”インジェクション攻撃”は防止できます。しかし、”インジェクション攻撃”を防ぐだけではセキュアなアプリケーションとは言えません。
「$UserIDはアプリケーションが設定した符号付き64ビット整数で表せる自然数」とします。
<?php $UserID = '<script>alert(XSS)</script>'; ?> <div> UserID: <?php echo htmlspecialchars($UserID); ?>は見つかりません。 </div>
出力
<div> UserID: <script>alert(XSS)</script>は見つかりません。 </div>
「$UserIDは符号付き64ビット整数で表せる自然数」なのでこの出力は正しくないです。自然数なのだから整数型にすればよい、と考えるかも知れません。
<?php $UserID = '<script>alert(XSS)</script>'; ?> <div> UserID: <?php echo htmlspecialchars((int)$UserID); ?>は見つかりません。 </div>
出力
<div> UserID: 0は見つかりません。 </div>
例1よりは随分マシですが、
‘<script>alert(XSS)</script>’ が 0
に変換される動作は正しい動作でしょうか?
新しい2017年版OWASP TOP 10のA10「不十分なログとモニタリング」脆弱性は動的攻撃ツールからのリクエストを検出、ログ、対応できないアプリケーションは脆弱なアプリケーションであるとしています。
‘<script>alert(XSS)</script>’ が 0
は正しい動作とは言い難いだけでなく、脆弱な動作である、と言えます。
この脆弱な動作も仕様として正当化することも可能です。しかし、OWASPの定義ではこのような動作するアプリケーションは脆弱なアプリケーションです。2
またセキュリティ問題は、インジェクション攻撃問題だけではありません。機密性/可用性/完全性/信頼性/真正性/否認防止がITシステムも求められるセキュリティの要素です。出鱈目な動作、攻撃者からのDoS攻撃を許す動作、脆弱性探索を容易にする動作なども脆弱な動作です。
機密性/可用性/完全性/信頼性/真正性/否認防止 はISO 27000が定めるITセキュリティ要素です。出鱈目な出力を行うシステムを信頼性があるとは言い難いです。ISO 27000的にもセキュアなシステムとは言えないでしょう。3
“管理できない”脆弱性の原因になる
例えば、壊れた文字エンコーディングを放置するシステムが誤作動することがあります。HTMLコンテクストの出力を無害化するためにエスケープ処理が行われますが、実は意味を持つ特殊文字(<, >, &, “, ‘など)をエスケープするだけでは不十分です。
PHPのHTMLエスケープ関数は文字エンコーディングバリデーションを行い、壊れた文字エンコーディングによるインジェクション攻撃を防止しています。文字エンコーディングバリデーションをエスケープ関数が行うので、これだけで十分に無害化できているように見えますが、不十分です。
PHPのHTMLエスケープ関数は文字エンコーディングバリデーションに失敗すると空文字4を返します。つまり、本来表示されるべきページやページの一部が表示されなくなります。壊れた文字エンコーディングがデータベースやファイルなどに保存されていると、恒久的に表示に問題があるページができてしまいます。
HTMLエスケープ関数が文字エンコーディングをバリデーションするから悪いのだ!と思うかも知れません。しかし、Chromeは壊れた文字エンコーディングデータを渡すと真っ白なページを表示します。
壊れた文字列を出力時に安全に処理する(=正しく動作させる)ことは出来ません。出力対策だけではDoS攻撃の原因になります。
※ DoS攻撃は文字エンコーディングを使ったブラウザやページに対する攻撃に限りません。ありとあらゆるDoS攻撃パターンが考えられます。壊れたデータは直接/間接インジェクション攻撃にも利用できます。間接SQLインジェクション攻撃が有名ですが、ありとあらゆる間接攻撃のパターンも考えられます。間接攻撃は出力対策だけでも対応可能ですが、入力対策が甘いと、壊れたデータが存在してしまうことに変りありません。(在るべきでないデータが存在=システム上の不具合=正常動作を目的とするセキュアコーディング違反)
出力対策だけでも大方の”攻撃”は防げますが、対処/発見が難しいDoSやインジェクション攻撃が可能な穴、そもそも存在してはならないデータを許してしまいます。
参考:SQLiteのカラムは整数型などと定義しても文字列を保存可能
参考:文字エンコーディングは入力処理の時点で妥当な物だけ、を受け入れないと正しく動作するシステムは作れない。
開発者は変更を完璧に管理できない
最も深刻な問題は開発者は変更を管理できないことです。
「変更を管理できない開発者が悪い!」と考える方も居るかも知れません。変更を完璧に管理して、完璧な脆弱性廃除を出来ていたなら、今迄一度も環境問題や自分の書いたコードに脆弱性がなかったハズです。この様な人は世界中に誰一人いません。証明は簡単です。今のOSやブラウザは穴だらけですが、使っているハズです。同じようにライブラリなどに含まれている脆弱性を全て廃除して使っている人は1人もいません。※
※ ライブラリには”仕様”として脆弱な仕様のモノが当たり前にあります。安全に使う責任がアプリケーションにあるからです。このような仕様のライブラリは無くすことが出来ません。必要性があってそうなっているモノだからです。
システムを安全に保つにはアップデートが欠かせません。アプリケーションのみでなく、アップデートにはOS、言語、ライブラリ、フレームワークのアップデートも含まれます。新しい物に未知のセキュリティ脆弱性が潜んでいることはよくある事です。未知の脆弱性を見つけられるように管理するのは膨大なコストが必要になり、現実的ではありません。というより絶対に無理です。
参考: Python 2.7のセキュリティアップデート
幸いにもほとんど全てのインジェクション型攻撃は”不正なデータ”が送られてきた時に発生します。ソフトウェアは”妥当なデータ”が送信されてきたときに”正しく動作”するように作られています。アプリケーションにはそもそも不必要かつ有害な”不正なデータ”を入力処理時点で廃除してしまえば、比較にならない程高い安全性を達成できます。
入り口対策のないアプリセキュリティ対策は、
- 戸締りをしないで泥棒に「盗める物があるなら盗んでみろ!」
と言う事とあまり変りがありません。
ローレベルのライブラリなど環境全てを含めた完璧なセキュリティ管理を実施し「オレのシステム完璧だ!どこからでも来やがれ!」と言える人以外にはこの方法はオススメできません。
どう設計すべきか?
出鱈目なデータを出力時に無害化することは可能ですが、正しく動作させることは不可能です。これはソフトウェアの動作原理に起因する”原理”です。原理を変えることは出来ません。どうアプリケーションを設計すべきでしょうか?
これも2017年版OWASP TOP 10に記載されています。A1の「インジェクション」脆弱性5では
Almost any source of data can be an injection vector, environment variables, parameters, external and internal web services, and all types of users. Injection flaws occur when an attacker can send hostile data to an interpreter.
ほぼ全てのデータソースがインジェクション攻撃の原因になるとし、対策として
Use positive or “whitelist” server-side input validation. This is not a complete defense as many applications require special characters, such as text areas or APIs for mobile applications.
サーバー側でホワイトリスト型の入力バリデーションを行いなさい、としています。
つまり、アプリケーションを入力処理、ロジック処理、出力処理に分けた場合、以下のような設計にすれば良い設計となります。
- 入力処理: データバリデーションをホワイトリスト型で厳格に行う
- 出力処理: 出力処理の三原則で無害化を行う
入力バリデーションをしていないと脆弱になる例は数え切れないくらいあります。数え切れないくらいあるので、マトモなセキュリティ専門家は入力バリデーションを第一のソフトウェアセキュリティ対策としています。
- 1 CERT Top 10 Secure Coding Practices
- 2 OWASP Secure Coding Practices
- 3 CWE/SANS Top 25 Monster Mitigation
- 4 ISO27000/ISMS
出力対策だけ、ではどうしようもない
SQLでも、コマンドでも、他の出力でも命令を構成する要素(識別子など)がユーザーにより制御可能な場合、出力対策だけではどうしようもない場合が多いです。(出力時点でバリデーションする方法も可能だが現実的ではない)
SQLなら以下のようなクエリ
pg_query_params(“SELECT “. pg_escape_identifier($_GET[‘col’]). ” FROM “. pg_escape_identifier($_GET[‘tbl’]));
コマンド実行なら
pcntl_exec($_GET[‘user_selected_command’], $_GET[‘user_specified_params’]);
といったコードを見ればどうしようもないケースがあることが分かります。Railsの脆弱性問題も参考になります。
アプリケーションとライブラリでは「役割と責任」が異なります。自分で全てのライブラリを作って何とかする方法もありますが、効率的ではありません。
出力対策だけではどうしようもない問題は他にも色々とあります。
まとめ
OWASP TOP 10に記載されている対策に従っていれば、壊れた出力や壊れたデータを保存する脆弱なアプリケーションではなくなります。
因みに、OWASPと同じことは10年以上前からセキュアコーディング原則としてCERTが推奨し、現在ではISO 27000/ISMSの要求事項6になっています。MITRE(米のコンピュータセキュリティ標準を作る機関であるNISTなどを管轄)も同じ事を10年以上前か啓蒙しています。
セキュリティ専門家はずっと前、防御的プログラミングから数えると1990年頃から一貫して
- 入力処理: データバリデーションをホワイトリスト型で厳格に行う
- 出力処理: 出力処理の三原則で無害化を行う
と設計するように啓蒙しています。残念ながらセキュリティ専門家のアドバイスはほとんど全てのWebアプリケーションで活かされていません。
※ アプリケーション設計とライブラリ設計では原則が異なります。アプリケーション設計では必ず適用すべき入力・出力処理の原則が適用されていないライブラリは多くあります。しかし、これらのライブラリ全てが脆弱ではありません。ライブラリでは入力処理でバリデーションを行わないのは妥当かつ正しい設計である場合が多いです。例えば、ほとんどのJavaScriptのグラフライブラリ(バーグラフ、折れ線グラフなど)は入力バリデーションも行いませんし、出力の無害化も行いません。これは自由にグラフ出力を行うために必要かつ妥当な設計です。7
-
- エスケープが必要ないAPI。例)プリペアードスクエリ ↩
-
- 出力でもバリデーションが使える、というよりOWASP TOP10 A10に対応する為には「出力でもバリデーションを行わなければなりません」。どの出力を出力をバリデーションするかは”セキュリティポリシー”で決めて構わないです。いずれにしても全ての入力をバリデーションしている場合、ほとんどのケースで出力バリデーションが必要なくなります。 ↩
-
- 信頼性/真正性/否認防止はISO 27000:2013から追加されたセキュリティ要素です。しかし実はISO 27000の前、1996年公開の国際情報セキュリティ標準であるISO 13335には信頼性/真正性/否認防止も情報セキュリティ要素であると定義されていました。初期のISO 27000は簡略化した機密性/完全性/可用性の3つに簡略化し「情報セキュリティの要素はCIA」であると言われてきましたが、セキュリティ門家にとっては馴染みの深いセキュリティ要素です。2000年代はISO 13335(マネジメントよりの標準)とISO 27000(セキュリティ認証よりの標準)の2つの情報セキュリティ標準がありましたが、現在ではISO 27000に統一されています。 ↩
-
- 壊れた部分を除去したり、置換でもできますが、これ問題の原因になるので行ってはいけません。 ↩
-
- A1はSQLインジェクション攻撃を念頭した記述が多いですが、インジェクション攻撃”全般”の脆弱性と対策の解説です。過去のOWASP TOP 10にはSQLインジェクションが脆弱性として登録されていたこと、現在でもSQLインジェクション脆弱性は無視できず、致命的結果を生むことから、SQLインジェクションを念頭に置いた記述が多いと思われます。コマンドインジェクション、LDAPインジェクション、XPathインジェクションなど、その他全てのインジェクション攻撃は等しく防止されなければ安全なアプリケーションとは到底言えません。A1をSQLインジェクション専用の解説・対策だとするのは間違いなので注意しましょう。 ↩
-
- ISO 17799から数えてISO27000は入力処理については2000年から、出力処理も合わせてセキュアプログラミングについては2013年から要求事項に入れています。 ↩
- PCRE(Perl互換正規表現)はUTF-8をサポートしています。UTF-8文字列(u修飾子)を使用した場合、UTF-8文字エンコーディングのバリデーションで数百倍、それ以上の実行時間が必要となることがあります。そして文字エンコーディングバリデーションはデフォルトでは有効になっています。数百倍も遅くなる文字エンコーディングバリデーションはアプリが入力バリデーションを実施すれば必要ありません。 ↩