出力対策だけ行なえばOK!と考えてしまう原因は出力対策に3つの役割があることに気付かないからでは?と思ってこのエントリを書いています。
出力対策は”必須である場合”と”フェイルセーフ対策”である場合があります。
出力対策の基本はこちらです。
”妥当な出力データ”を無害化して出力
”妥当なデータ”でも出力時にそのまま出力して問題が起きないデータにならない場合があります。例えば、自由に入力できるコメントデータはバリデーションしても何もしないで出力すると、SQLインジェクションやコマンドインジェクションなどのインジェクション攻撃が可能になります。
妥当なデータを出力する場合も出力先の仕様に合わせて、無害な形にして出力しなければなりません。その方法には2つの方法があります。
- 出力先のエスケープ仕様に合わせてエスケープする
- 出力先の出力APIに無害化して出力するAPIがある場合、APIを利用して出力する
無害化して出力するには条件があります。単純にコレをやっておけばOK!と勘違いしていると脆弱になります。
- 複数のコンテクストがある場合、出力先の複数コンテクストの仕様に合わせ、全てのコンテクストで無害であること保証しなければならない
- 出力先が妥当な形式のデータであることを期待している場合がある
これらは結構よく見逃されているので注意が必要です。※SQL、※コマンド、※文字エンコーディング
APIを使ってさえいれば良い、と思っているのもNGです。
エスケープする場合:
- エスケープ仕様がない(例:XPath 1.0や識別子)
- エスケープ仕様があってもエスケープAPIが無い(例:MySQLにはSQL識別子エスケープ仕様はあるがAPIはない)
- エスケープAPIがあってもデータ形式が妥当である必要があるモノ(例:文字エンコーディングが正しい等)
エスケープせずに無害化できるAPIがある場合も注意が必要です。そもそもエスケープせずに無害化できるAPIが用意されていないケースが多いです。無害化APIを使おうとするより先に、エスケープ仕様を理解してから使う方がよいです。そうでないと複数コンテクストがある場合の対応を見逃してしまいます。
エスケープ無しの無害化APIを使う場合:
- エスケープする場合と同じく、複数のコンテクストがある場合は全てのコンテクストに対応したデータ形式にしてから出力する
SQL文が無害であること保証する方法、コマンドが無害であることを保証する方法、は既に書いています。
データバリデーションでデータ形式を厳格にバリデーションした妥当なデータであっても色々と注意することがあります。コレだけやればOK!といった方法には欠陥がある場合も多いので、間違えないようにしましょう。
”不正な出力データ”を無害化して出力
データバリデーションが甘い場合は出力データが”妥当ではない”(=不正なデータ)である場合があります。こういった場合にも「”妥当なデータ”を無害化して出力」と同じ方法が適用できます。
無害化すれば大丈夫!ではありません。例えば、SQLiteのカラムにはデータ型が定義できますが、基本的に全て文字列として保存してしまいます。
無害化するエスケープしても無害化するAPIを使って「”不正な出力データ”を無害化して出力」してしまうと、インジェクション攻撃ができなくなっても、出鱈目なデータを保存/利用してしまう問題が残ります。
たとえインジェクション攻撃ができなくても、出鱈目なデータの保存/利用は問題の原因になります。このため
- 「”不正な出力データ”を無害化にして出力」は「フェイルセーフ対策」
となります。
フェイルセーフ対策は「万が一の時の為の保険」のような対策です。たとえ保存できてしまっても「ダメなデータはダメなまま」です。基本的には”不正な出力データ”とならないよう、予めデータを入力バリデーションとロジックバリデーションで検証しておく必要があります。
”不正な出力データ”を無害化して失敗
データバリデーションが甘いと不正な出力データの保存/利用に失敗する場合があります。SQL文を実行しようとしてデータ型のミスマッチでエラーになる、などです。
- 「”不正な出力データ”を無害化して失敗」は「フェイルセーフ対策」
エスケープもできず、無害化APIも使えない、場合もあります。この場合は3種類あるバリデーションの3番目の出力時のデータバリデーションが必要になります。出力時点までのデータバリデーションが甘いと、”不正な出力データ”をバリデーションして失敗させる”ことになります。
- 出力時点でのバリデーション失敗も「フェイルセーフ対策」になります。
「”不正な出力データ”を無害化して出力」の場合と同じく、基本的には”不正な出力データ”とならないよう、予めデータを入力バリデーションとロジックバリデーションで検証しておく必要があります。
フェイルセーフ対策は必要だが頼ってはならない
出力対策”だけ”では「フェイルセーフ対策」になってしまう場合があります。
「フェイルセーフ対策」は多層防御です。多層防御はセキュリティ対策に必須といえる対策です。特に間違いが多い場合のフェイルセーフ対策は欠かせません。
しかし、フェイルセーフ対策に頼る構造/コードにするのはNGです。
残念ながら現在のWebアプリはフェイルセーフ対策となる出力対策だけにセキュリティ維持を頼っているコードがかなり多く含まれています。
フェイルセーフ対策が機能した場合、開発者の半分負け
本来、フェイルセーフ対策は機能してはならない機能です。フェイルセーフ対策は名前の通り、仮に防御に失敗しても、それが致命的な問題とならない様にする機能です。
無効なデータでフェイルセーフが機能してしまった場合、開発者は半分負けてしまっている(=バグがある)状態と言えます。フェイルセーフ対策その物が攻撃に利用される場合もあります。半分負け、とするよりは8割負けくらいの状態、と言っても構わないと思います。
APIでも外部出力でも、基本的にフェイルセーフ対策は動作しないように作る必要があります。
API利用のデータエラーも問題の原因
たとえ外部出力や他人が書いたフレームワーク/ライブラリのコード/APIでなく、自分(自分達)が書いたコード/APIに対する出力であっても、不正なデータを出力してエラーになるのは問題です。
中途半端に処理されてしまったデータを巻き戻すのは困難であったり、不可能な場合があります。一般的なアプリケーションはRDBMSのトランザクションのように、既に処理してきたモノを無かったことにしてロールバックさせる機能を持っていません。
インジェクション対策に入力バリデーションは必須
OWASP TOP 10のインジェクション攻撃の対策をよく読むと「入力データをバリデーションする」と書いてあります。OWASP Secure Coding Practices – Quick Reference Guideには一番目の項目として「入力バリデーション」が書かれています。
SANS TOP 25の”怪物的セキュリティ対策”には第一位の対策として「入力バリデーション」が記載されています。
CERT TOP 10 Secure Coding Practicesには第一原則として「入力をバリデーションする」を挙げています。
ISO 27000はこのCERTなどのセキュアプログラミング技術の採用を要求しています。
アプリケーションレベルの入力バリデーション(=アプリケーションソフトウェア信頼境界限界でのバリデーション)をせずにインジェクション攻撃でデータ漏洩などがあると訴訟やGDPR制裁対象になった場合にかなり不利だと考えられます。
ソフトウェアセキュリティの専門家が第一にやっておくこと、必ずやっておくこと、とする対策をせずにいて不利にならないハズがありません。
例: 入力バリデーションの不備で致命的な脆弱性を作った例
- Drupal – コード実行が出来てしまう脆弱性。結局、入力の”サニタイズ”で対応。本来はバリデーションして無効なデータを含むリクエストを拒否すべき。
- Struts2 – コード実行が出来てしまう脆弱性。Content-Length、Content-Type、Content-Dispositionなどにコードが記述可能。バリデーションで防止できた脆弱性。
Drupal/Struts2はこれらの脆弱性の対策として、無効な値を無視して対策済み、とする修正を行いました。しかし、2017年版OWASP TOP 10ではこのように、攻撃用の無効なデータを無視するアプリケーションは脆弱なアプリケーションであると定義しています。
基本的にフレームワークやライブラリは汎用的に作ってあります。無効なデータ/攻撃用データを無視する仕様はある程度仕方がない仕様だと言えます。無効なデータによるエラー自体が脆弱性の原因にもなるからです。これらを使ってアプリケーションを作る場合、データセキュリティ(データの妥当性)を保証/維持する責任はアプリケーションにあります。
※ とは言っても、フレームワーク、特にCMSのようなシステムにはデータセキュリティに必要なバリデーションをサポートする機能は在って欲しいです。残念ながらあまり良いモノは何時まで経っても出てきません。PHP用の入力データバリデーション用フレームワークは作ってGitHubに置いてあります。
まとめ
出力対策が「フェイルセーフ対策」になってしまわないよう、確実に入力をバリデーションしましょう。
コードとデータのセキュリティは別のモノとして設計しないと上手く行きません。
データのセキュリティ維持には契約プログラミングの考え方が参考になります。