危険な脆弱性であっても名前を付けないとなかなか認知してもらえない、といったことがよくあります。「データ検証をしないアプリ仕様」は危険で脆弱な仕様ですが、基本的な対策を実装しない危険性はあまり認知されてないと思います。例えば、とんでもないGDPR制裁金を支払うといったリスクが高くなります。
名前が必要なのかも知れません。
実は新しく考えなくても既にOWASPが名前を付けています。
- Unvalidated Input (未検証入力)
OWASP TOP 10 A1:2004 Unvalidated Inputとして第一番目の脆弱性として挙げています。
2007年版から消えていますが、これはセキュアコーディングとの兼ね合いで省略したのだと思われます。「2007年版から消えているので脆弱性ではなくなったのだ」とする自分勝手な解釈の解説を目にしたことがありますが、その2007年版には「リストから消したが変わらず重要な対策で、本年度版の脆弱性対策の手法としても記載」とする解説が書かれています。
標準的なセキュリティ対策の概念では「入力データ検証は非常に重要な脆弱性対策」です。
未検証入力、昔と今
セキュアコーディング原則は今も広く実践されているとは言えない状態です。現状(2004年も2018年も)を考えると未検証入力データは、14年前も今も「未検証入力」はかなり強力な脆弱性と言わざるを得ません。
それぞれ
- Struts2の脆弱性はHTTPヘッダーのContent-Type/Content-Disposition/Content-LengthにOGNL言語のプログラムが書けてしまい、実行されてしまった
- Drupalの脆弱性は入力データとなる配列キーにプログラムが書けてしまい、実行されてしまった
という問題です。
Content-Lengthや配列のキーにはそもそも「任意の文字列」を設定する物ではありません。「決まった形式の文字列」が設定されるべきデータであり、入力データ検証を行っていれば攻撃できなかった問題です。
実際、(不十分であっても)脆弱性対策として入力データを検証する、が対策として適用されています。
これらはフレームワーク/ライブラリが悪いのか?
「フレームワーク/ライブラリが悪い」とするのは半分正解です。外部入力がデータソースとなるデータを取り扱う場合、検証してから使う対策が必要だからです。(フェイルセーフ対策 として必要)
しかし、半分は外れです。フレームワークやライブラリといったアプリケーション内部の奥深くで
- Content-Length(数字のみが妥当)にプログラムが書いてあった
- 配列キー(普通は英数字のみが妥当)にプログラムが書いてあった
これらを検証&排除しても遅すぎです。
こういったデタラメなデータはフェイルファースト原則を用い、アプリケーション入力処理の段階で検証し、妥当な入力データ以外は排除しなければなりません。(セキュアコーディングの第一原則かつセキュアなソフトウェアに必要な構造)
個人的にはContent-Type/Content-Disposition/Content-Lengthといった、当たり前に使われるHTTPヘッダー内容の最低限必要なバリデーションは、Webアプリケーションフレームワークがバリデーションし、無効なリクエストはフレームワークが勝手に排除すべき、と思っています。(フェイルセーフ対策として)
しかし、ライブラリやフレームワークはアプリケーションとは異なり、出来る限り幅広い用途で使えるよう、出来る限り無用な制限がなくなるように設計&構築されています。フレームワークやライブラリがHTTPヘッダーをバリデーションせず、デタラメなデータでも何事も無かったように動作してしまう、といった仕様も理解できます。
アプリケーションとフレームワーク/ライブラリ、どちらがデタラメなデータを排除する(=妥当な入力のみを受け入れる)バリデーションを行うべきでしょうか?
特定用途用に作られるアプリケーションが”アプリケーション仕様”、つまりアプリケーション入力仕様に責任を持つことになります。
例えば、Content-Lengthですが正の整数なら妥当なデータと言えます。しかし、アプリケーションが1MBまでしか許可しないなら、2^64でも2^32でもなく、1MBまでが妥当なデータです。どのサイズまでが妥当なのか?汎用ソフトウェアであるフレームワーク/ライブラリには判りません。専用ソフトウェアであるアプリケーションが責任を持って、妥当な入力データであることを検証&保証する必要があります。
Content-TypeやContent-Dispositionも同様で、text/plainだけが妥当である、といった判断はフレームワーク/ライブラリ開発者にはできません。RFCに準拠した”緩いバリデーション”までが限界です。
アプリケーションは未検証入力の脆弱性に責任を持つ
少し考えると未検証入力によるセキュリティ問題の責任を全てフレームワーク/ライブラリに押し付けることは不可能だと解ります。責任を押し付けることが不可能なモノに責任を押し付けるのは無責任です。
ソフトウェアの作り方の特性(フレームワーク/ライブラリは汎用、アプリケーションは専用)から、入力データをバリデーションしない「未検証入力脆弱性」の責任はアプリケーションが持つことになります。
もちろんフレームワーク/ライブラリでも”フェイルセーフ対策”として「任意コードが実行される」といった致命的なセキュリティ問題が発生しないような”多層防御”も必要です。フェイルセーフ対策、多層防御の下層、に頼りきるような設計はソフトウェアのセキュリティ設計に限らず、どの分野のセキュリティ設計でもダメな設計です。こうならないようにする必要があります。
繰り返しになりますが、フェイルファースト原則で対処する必要があります。
未検証入力脆弱性の誤解
入力データバリデーションほど基本的かつ重要なセキュリティ対策はないのですが、重要なのにこれほど誤解されているセキュリティ対策も他にあまりないです。
CERTやMITREといった情報セキュリティの権威中の権威といった組織に所属するコンピューターサイエンティストかつセキュリティ専門家から、未検証入力脆弱性に対するおかしな解説を見たり聞いたりしたことはありません。(オープンソース型で開発している資料などでは一部おかしな記述があったりすることもありますが)
しかし、”セキュリティ専門家”と言われる方にはおかしな解説をしていることが結構あります。(これは国内外、似たような感じですが海外ではセキュリティ専門家というくくりではなく、脆弱性研究家と分類される方に多いと思います)
このスライドは全体として「入力データバリデーションは重要なセキュリティ対策ではない」(もっというと、セキュリティ対策ではない)という強い主張を感じます。
バリデーションを最初の最初にすべき、とコンピューターサイエンティストがする最大の理由は
- 妥当なデータでないと、何をやってもダメな結果となる
からです。プログラムは「受け入れ可能な妥当なデータ」以外のデータを処理してしまうと、何をしても正しく処理できません。(サニタイズでダメなデータをとにかく使える状態にする対策も可能ですが、基本としてこの方式はアンチプラクティスです。OWASP TOP 10 A10:2017脆弱性でもあります。)
同時に「受け入れ可能な妥当なデータ」以外のデータの受け入れによって、数えきれない量の脆弱性が作られていることもその理由ですが、副次的な理由です。コンピューターサイエンティストは半世紀以上前から「正しく動作するプログラム」(=セキュリティ問題もないプログラム)の研究をしていますが、「正しく動作するプログラム」には
- 妥当な入力データ
- 正しいコード
の両方が必須であると解りきっているからです。
デメリットは根本的解決でないこと?!
実は初めて徳丸さんに会った時に「入力バリデーションはセキュリティ対策ではない」と主張されてとても驚いたことを覚えています。その際にも「入力対策は入力対策、出力対策は出力対策で両方するもの、多層防御ですよ」と説明したと思います。
私の言葉でなくてもCERT Top 10 Secure Coding Practices(セキュアコーディング10原則)にも誤解が無いよう解りやすく書いてあります。
第7位 他のシステムに送信するデータを無害化する
コマンドシェル、リレーショナルデータベースや商用製品コンポーネントなどの複雑なシステムへの渡すデータは全て無害化する。攻撃者はこれらのコンポーネントに対してSQL、コマンドやその他のインジェクション攻撃を用い、本来利用してない機能を実行できることがある。これらは入力バリデーションの問題であるとは限らない。これは複雑なシステム機能の呼び出しがどのコンテクストで呼び出されたか入力バリデーションでは判別できないからである。これらの複雑なシステムを呼び出す側は出力コンテクストを判別できるので、データの無害化はサブシステムを呼び出す前の処理が責任を持つ。
セキュアコーディングを理解していれば、上記のスライドのような説明には天地がひっくり返ってもならないはずです。
少し古い資料だったので最近のモノでいうと、同類の解説が2016年の資料、PHPカンファレンス2017の講演資料にも見られます。
出力対策も根本的解決ではない
プリペアードクエリが完全かつ根本的なセキュリティ対策であるかのような解説がされています。しかし、これは明らかな間違いです。
プリペアードクエリ/プレイスホルダだけではデタラメなデータの挿入を防止できないからです。
徳丸さんはISO 27000など関係ない、という主張なのでISO 27000のセキュリティ定義を持ち出しても意味がないと思いますが、一応ISO 27000ではセキュリティ対策として以下の要素を保護するようにとしています。
機密性・完全性・可用性・信頼性・真正性・否認防止(責任追跡性)
壊れた文字エンコーディング、制御文字を含む文字列、といったデタラメなデータが保存できてしまうようなアプリケーションには「完全性」も「信頼性」もありません。ライブラリなどに散在するバリデーションで「可用性」も損なわれる場合もあります。(iPhoneで時々起きる文字エンコーディング問題)
SQLインジェクションの場合だけなく、出力時点でデタラメなデータが残っている状態になるが、本来は異常事態です。出力対策(プリペアードクエリだけなど)でセキュアなアプリケーションは原理的に作れません。
入力バリデーションはしていますよ!といった場合でも…
よくあるRailsのモデルの文字列型データのバリデーションに
validates :name, presence: true
といった感じのバリデーションがあります。文字列型のバリデーションですが、「データが存在する」ことだけを検証しています。
これではISO 27000やCERTが求めるセキュアコーディングにはなりません。
ISO 27000やCERTが求める入力データバリデーション方法は既に書いているので、そちらを参照してください。
未検証入力は脆弱性
全ての入力データが妥当であることはプログラムの正しい動作の必須条件です。
正しい動作に必須 = 誤作動させないために必須 = セキュリティ問題の対策として必須
そもそも絶対に必要な処理が入力データバリデーションです。さらに、現在のWebアプリの大半がまともに入力データバリデーションを行えていません。
OWASP TOP 10 A10:2017はこの状況を変えるべく、導入された脆弱性です。ISOやCERTが求める入力バリデーションをしなければA10:2017には対応できません。
入力データ検証はISO 27000で求められるセキュリティ対策です。これがないことが原因でプライバシー情報漏洩が起きた場合、GDPR制裁対象になってしまう可能性も十分にあると言えます。(EUはISO 27000などの標準が大好きです)
日本の法律に於いても、国際/国内標準で当たり前にすべきとする対策が実施されていなかった、として裁判で不利になる可能性が高いです。
「自分はセキュリティ脆弱性とは考えない」「自分はセキュリティ脆弱性として重要ではない」とする考え方とは無関係に、法律では実装しなかった際には責任を負わなければならない、そういった脆弱性が未検証入力です。責任ある専門家であれば、未検証入力データは脆弱性と捉えて対策しましょう、となると思います。