脆弱性を呼ばれた側の責任にする、は常に通用する考え方ではありません。
- ライブラリに脆弱性があるなら、ライブラリの脆弱性を直す(呼ばれた側の責任にする)
- 外部アプリケーションに脆弱性があるなら、外部アプリケーションの脆弱性を直す(呼ばれた側の責任する)
は一見正しく見えます。しかし、通用しません。
理由は簡単で、セキュリティ上困った動作をするライブラリやプログラムであっても、それが仕様で脆弱性ではないことがあります。
そもそもライブラリやユーティリティプログラムは幅広い用途に利用できるよう、用途を限定しないよう汎用的に作ってある場合が多いです。こういった仕様の場合、脆弱性を呼ばれた側の責任にする、ではセキュリティ問題を作ってしまいます。
コマンド実行のリスクから学ぶセキュリティ対策
少し前にコマンド実行時にコマンドと引数を分離するAPIを使っても、コマンド実行は完全に防げないことを紹介します。
このブログに「コマンド(ls.sh)に脆弱性がある」とコメントを頂きました。
呼ばれた側のコマンド(ls.sh)に脆弱性が問題であり、pcntl_exec(PHPのexecveシステムコールを使ったコマンド実行関数)の問題ではない、と指摘したかったのだと思います。(最初、ジョークコメントだと思いましたが)
わざと呼び出されたコマンドが、更に脆弱な方法で別のコマンドを実行するように作ってあったのですが、正当な仕様である場合もあります。
コマンド実行時にコマンドと引数を分離するAPIを使っても、実行したコマンドがそもそも”正当な仕様”として以下のような処理をするケースも少くありません。
- 別のコマンドを実行する
- 別のコマンドを用いてファイルの中身を取り出す
- 別のコマンドを用いてファイル内容を改変する
呼び出されたコマンドが渡されたパラメーターを使って更に上記のような処理すると、最初のコマンド実行時にコマンドと引数をAPIで分離してもセキュリティ対策の意味はありません。
正当な仕様として実装されているコマンドに対して「仕様が脆弱である」することにも意味がありません。「出力先パラメーターのコンテクスト」を考えないとセキュリティ対策として意味がないです。
コマンド実行の場合、実行するコマンドが引数をどのように扱うのか?が「出力先パラメーターのコンテクスト」になります。「出力先パラメーターのコンテクスト」に対して、問題なく動作するような値を渡す責任は呼出側にあります。
呼ばれた側の責任にする、という考え方は安全なコマンド実行の実装として通用しません。
SQL文実行のリスクから学ぶセキュリティ対策
SQL文実行でもSQL命令とパラメーターを分離するプリペアードクエリがあります。しかし、プリペアードクエリでパラメーターを分離しても、「出力先パラメーターのコンテクスト」に対して、問題なく動作するような値を渡す責任は呼出側にあります。
RDBMSは単純な1つの値だけを保存する物ではありません。保存するデータとしてJSON、配列、XML、ストアドプロシージャーなどもサポートしています。これらがパラメーターの場合、プリペアードクエリだけでは安全性を保証できません。
PostgreSQLの場合、JSONと配列にはコードを記述できませんが、余計な値がインジェクションされないようにしないと管理者フラグといったセキュリティに大きな影響を与える設定を追加される可能性があります。(Railsプログラマならマスアサイメントと同類と考えると解りやすい)
XMLだとXPathクエリがサポートされているので命令も実行できます。パラメーターの内容自体の安全性を保証しないと、XMLデータを丸ごと盗まれる可能性があります。
ストアドプロシージャーは、コマンド実行で解説したような仕様である場合もあります。ストアドプロシージャーが渡されたパラメーターをどのように扱うか、は実装次第であり”正当な仕様”としてリスクがある処理を行うこともあります。
これらの問題を呼ばれた側のPosetgreSQLの責任にすることはできません。
プリペアードクエリが渡すパラメーターがどのようなコンテクスト(JSON、配列、XMLなど)で評価されるのか理解し、安全にSQL文とパラメーターを渡す責任は呼び出す側にあります。
呼び出された側に責任がある、は脆弱性を作る考え方
上記のように、呼び出された側(callee)に責任がある、は脆弱性を作る考え方です。
安全性が高いく効率が良いプログラムの構築手法である契約による設計では正反対の考え方を用います。
- 関数を正しい使い方で呼び出す責任は呼び出す側(caller)にある
構造化設計でセキュリティ対策を呼び出された側に抽象化することに慣れた開発者にとっては違和感があるかも知れません。しかし、問題となるパラメーターを呼び出された側(callee)の責任して抽象化するより、呼び出す側(caller)の責任にした方がより安全かつ効率良いプログラムを作れます。
- 問題となるパラメーターはそもそもプログラムでは正しく処理できない(セキュリティ問題のほとんどが問題となる不正なパラメーターで発生する)
- 問題となるパラメーターをソフトウェア奥深くに抽象化し対策すると、問題となるパラメーターの検証が繰り返し何度も行われる(処理性能が悪化する。例:PCREのUTF-8エンコーディングバリデーション。数百倍以上の性能劣化となる場合も)
- 問題に対応する抽象化部分が分散し、ブラックリスト型の対策となる(ブラックリスト型は仕組的に脆弱でさけるべき対策)
また問題となるパラメーターはプログラミングの基本であるFail Fast原則に則り対応すべきです。呼び出された側(callee)で対応するとこの原則にも反しています。
出力対策は”コンテクスト”が重要 – 特に複数コンテクストを持つ場合
結局、何らかのパラメーターを何らかの機能に渡す場合、”全てのコンテクスト”を考慮した処理でないと安全に処理できません。単純に
- 最初に呼び出した時”だけ”パラメーターを安全に処理
しても
- その先のコンテクストで安全に処理
できなければ致命的なセキュリティ問題の原因になります。セキュアコーディングの第7原則である「全ての出力を無害化する」を実践し、「全ての”多重化”した出力コンテクストに対しても無害化する」を行わなければなりません。
コマンド実行なら
- 最初にコマンドを実行するコンテクストのみでなく、実行したコマンドが処理するコンテクストまで考慮し無害化する
SQL文実行なら
- プリペアードクエリで渡すSQLパラメーターコンテクストのみでく、渡されたパラメーターが処理されるコンテクストまで考慮し無害化する
このような処理はWebアプリではHTML文書中の
- URI属性の中のパラメーター
- JavaScript内の文字列パラメーター
- CSS内のパラメーター
に在るので、Webアプリ開発者にとっては馴染みが深い物だと思います。これらと同じように、出力したパラメーターが複数コンテクストで処理される場合、全てのコンテクストで問題が発生しないように無害化して出力しなければなりません。
まとめ
「脆弱性を呼ばれた側の責任にする」を適用できる場合もありますが、原理的/仕様的に適用できない場合があります。
このため、単純に「呼ばれた方が何とかして安全に処理してくれる/しなければならない」と考えていると、思わぬ致命的なセキュリティ問題を作ってしまう場合があります。
パラメーター(データ)が安全に処理されるよう無害化する責任は「呼びだす側の責任」にしないと、データ処理がスパゲティー化しまったり、どうしようもないので遅すぎる段階でエラーにしなければならなくなる場合があます。
例えば、古いWebブラウザはWebサーバーが壊れた文字エンコーディングを送って来ても、何とかして出力しよう、としていました。しかし、上手く処理できないので今のブラウザは何も出力しないようになっています。つまり、正しく処理できるデータを送ってくる責任はデータを送るWebサーバーにあります。攻撃用データで”真っ白”のページが表示されても構わないWebサービスなら問題なし、と言えますが多くのWebサービスでは許容できない問題だと思います。
そもそも、壊れたデータ/出鱈目なデータ、を取り扱いって保存や出力までしてしまうソフトウェアは根本的に壊れている、とされても仕方ありません。
「脆弱性を呼ばれた側の責任にする」が通用する場合もありますが、通用しない場合も少なくないです。壊れた文字エンコーディングは呼ばれた側ではどう頑張っても正しく処理することが不可能です。
「データ」に着目した構造(セキュリティ対策)が入力と出力の両方で欠かせません。