ソフトウェアのセキュリティ問題の多くは「ソフトウェアの誤作動」です。つまり、正しく動作するソフトウェアならセキュリティ問題の大半は発生しません。1
コンピュータープログラムが正しく動作するには「正しいコード」と「正しいデータ」の両方揃う事が必須条件です。これはプログラムの動作原理に基づく条件なので、誰にも変えることは出来ません。
正しく動作するプログラムには正しい(妥当な)データが必須
プログラムに対するインジェクション攻撃は攻撃用の入力データが原因です。そして攻撃用入力データの大半が不正/無効なデータです。まず入力が「正しい(妥当な)データ」であることを保証する事からセキュリティ対策が始まります。そもそもプログラムは「正しい(妥当な)データ以外」では正しく動作しません。このため入力データの妥当性確認は2000年から国際ITセキュリティ標準(ISO 17799)にセキュアなソフトウェア開発の必須事項として要求されてきました。
出力対策の前にいろいろセキュリティ対策が必要ですが、ここでは省略します。
出力対策は出力によりプログラムが誤作動することを防ぐ対策です。出力のセキュリティ対策は「出力先に対して無害なデータ」にする対策でしかありません。出力先に対して「正しい(妥当な)データ」を保証する対策ではないです。
攻撃用でプログラムにとって無効なデータは、何をしても正しく取り扱うことが出来ません。そもそも無効なデータを処理しようとすることが間違いです。
問題ないデータに(サニタイズ)すればOKでは?と思ったかもしれません。しかし、サイニタイズ(無効なデータを取扱える形に変換)し、攻撃用データを無視する動作はOWASP TOP 10では脆弱性です。2 基本的には無効なデータを無視してはなりません。
無効なデータを受け入れ処理しても、無効なデータは無効なままです。無効なデータはそもそもプログラムが正しく動作できないデータなので当然です。プログラムが正しく動作することは有り得ず、問題の原因であり続けます。
※ サニタイズは決して実施してはならない処理ではないですが、サニタイズをすると”サニタイズ仕様の違い”などによって発見が困難な問題を作ってしまうことがあります。基本的に無効なデータはバリデーションで拒否し、サニタイズすべきではありません。注)”ユーザーが編集したHTMLを整形処理(サニタイズ)するプログラム”などの場合、壊れたHTML構造を持つデータ、使用が許可されていないタグなどを含むデータは”無効なデータ”ではありません。アプリケーションが仕様として予め受け入れ予定している妥当なデータです。
出力対策が持つ2つの意味
まず、出力対策だけでは十分なセキュリティ対策に絶対になりません。
出力のセキュリティ対策は2つの目的で行います。
- 正しい(妥当な)データが出力先に対して無害であることを保証する
- 壊れた(攻撃用)データが出力先に対して無害であることを保証する
2は万が一の場合の「フェイルセーフ対策」です。そもそも2のケースは発生してはならず、出力時点で2のケースが発生した場合はプログラムのバグです。
「攻撃用データが出力先に対して無害」であっても、その壊れたデータがその先どのように使われるか、は分かりません。攻撃用のデータはエスケープしたり、データとして分離しても、攻撃用データであり続けます。
「攻撃用データであり続ける」ことは攻撃者にとっては非常に喜ばしい限りです。攻撃者はパズルのピースを埋めるように「攻撃用データであり続ける」動作を利用し、システム内部の奥深くシステムレベルのライブラリ等の脆弱な動作や仕様を見つけることができます。そして運が良ければ実際の攻撃に使えるセキュリティ問題を見つけることができます。
データのセキュリティ対策
データのセキュリティ対策が無い/不十分なアプリケーションは脆弱です。データのセキュリティ対策を信頼境界線上で行っていないアプリケーションも脆弱です。
データのセキュリティ対策を全て忘れているとプログラムは正常系データでしか動作しないので、完全に忘れている訳ではありません。
しかし、現実に、データセキュリティを軽視している、必要な検証を忘れているWebアプリケーションがほとんどです。
OWASP TOP 10はこういったアプリケーションは脆弱性なアプリケーションだとしています。
スライドにより詳しく書いています。データのセキュリティ対策を考えないソフトウェアセキュリティ対策はあり得ないです。
データセキュリティ構造(設計)はプログラム機能設計とは別に考えなければ上手く機能しません。
データセキュリティ設計とプログラム機能設計を分離しなければならない理由
データセキュリティ設計とプログラム機能設計は根本的な部分で互換性がありません。
- データセキュリティ設計 ー できる限り上流で「正しい(妥当な)データ」であることを保証するように設計
- プログラム機能設計 ー 巧く機能を抽象化し下流で「正しく動作」することを保証できるように設計
プログラムの機能は全ての入力を利用する訳ではありません。一部だけを使い、使う場所は何処で使われるかも分かりません。分らない事はないのですが、リファクタリングや仕様変更でコードの位置や実行パスは不定です。
基本的には、一度バリデーションを行ったデータに同じバリデーションを実施する意味はあまりありません。バリデーションは実行オーバーヘッドとなるため、バリデーション済みのデータは「既にバリデーション済みで信頼できるデータ」として下流の関数/メソッドでは省略されることが多いです。
全ての信頼できない入力をバリデーションし、入力データが正しい/妥当であるものでないと、プログラムが正しく動作することを保証できません。少なくともデータ形式的に妥当でないと動作しません。これはプログラムの機能とは無関係に必要な前提条件です。
※ 強いデータ型を持つコンパイル型言語でデータ型が異なると動作しないのと同じ。コンパイルエラーがで動作するバイナリが作れないのは、そもそも動作しないからです。
プログラムの機能と関係ないデータバリデーションを、プログラム機能の中に無理矢理入れても、あまり上手く行かない事は論理的にも実証論的にも証明されています。データバリデーションが無い/不十分であることが原因で攻撃可能になってしまったアプリケーションは数えきれません。
データバリデーションには三種類あり、それぞれ役割が異なります。一番忘れられているのは
全ての信頼できない入力データをバリデーションする
です。
アプリケーションへの入力データはインターネット標準とアプリケーション仕様によって決まっていま。つまりデータ仕様=データ形式は決まっています。
データ形式の妥当性はプログラムの機能とは無関係に必要な前提条件でプログラム機能とは独立してバリデーション可能です。
プログラム機能と関係なくバリデーションできる入力データ形式バリデーションを、プログラム機能の中に入れるのは入れるのはOK3だとしても、責任を持たせる設計とするのは良い設計とは言えません。
プログラムの機能設計とデータセキュリティのバリデーション設計は別のモノとして考えて設計しないと、どこかで破綻します。
必須:データバリデーションには三種類あり、役割の理解が必要