はじめに
Webアプリケーションの脆弱性対策では、脆弱性が発生するのはデータを使うところであるので、データを使う際の適切なエスケープ処理などで対処するのがよいと言われます。しかし、処理内容によってはエスケープができない場合もあり、その場合の対処についてはまだ定説がないと考えます。エスケープができない場合の例としては、以下があります。
- SQLの数値リテラルを構成する際に、入力に数値以外の文字が入っていた
- メール送信しようとしたが、メールアドレスに改行文字が入っていた
- 入力されたURLにリダイレクトしようとしたところ、URLに改行が入っていた
これらの場合、エスケープはできないので、以下のいずれかの対処をすることになります。
- バリデーション:受け付けられない文字があった場合はエラーとして処理を終了する
- サニタイズ:受け付けられない文字を削除するか、他の文字に置き換えて処理を継続する
ここで前提として、入力値のバリデーションはしているのだが、何らかの原因でバリデーション処理に漏れがあって、そのデータを使う(たとえばメール送信)際に、上記が分かったというシナリオです。つまり、本来上記のような異常値は来ない前提だが、セーフティネットとして、処理の段階で対処をしておくということです。
私は従来、このような場合はバリデーションがよいと考えていました。サニタイズは入力値を改変することになるので、いかなる場合でも好ましくないと考えていました。
奥一穂氏との会話
そこで、大垣さんの寄稿記事「なぜPHPアプリにセキュリティホールが多いのか? 第44回 セキュリティ対策が確実に実施されない2つの理由」に関連して、大垣さんの寄稿に肯定的なコメントとして、facebookで以下の投稿をしました。徳丸 浩 出力時のバリデーションが必要な局面は確かにあって、(1)(好ましくはないが)SQLを動的に組み立てる際に、数値パラメータに数値以外の文字がある場合、(2)メール送信時に、メールアドレスや件名に改行が含まれていた、ようなケース。とくに(2)のケースは、メール送信用のラッパー関数を作って改行やメールアドレスの改行チェックをするとよい。本来は入力時のバリデーションで弾かれているはず(べき)だが、万一漏れていた場合の安全装置として出力時のバリデーションも組み込んでおくと効率よく安全性を高めることができるこれに対して、奥一穂氏から以下のコメントを頂戴しました。
Kazuho Oku 安全装置なら処理を停止よりはサニタイズすべきなのかなと思ったりします。昔、Twitter ライクなサービスが出力時バリデートしてて、ある人が不正な UTF-8 シーケンスを含むツイートを投稿した結果、多くのユーザーのタイムラインが表示 (ry私はこの「事件」の詳細を知らないのですが、以下の現象だったと憶測しています。
- 某Twitterライクなサービスはtwitterのように、フォローしている利用者のつぶやきが時系列的に表示される
- ユーザのH氏が誤って(?)不正なUTF-8シーケンスを含むツイートを投稿した
- そのつぶやきは入力時のバリデーションを通過してDBに格納された
- そのつぶやきを表示する際に、不正UTF-8シーケンスが含まれているため、例外が発生した
- 例外処理の結果、当該のツイート以降空となり、多くのユーザのタイムラインが影響を受けた
H氏が、自身の不正なツイートで不利益を受けるのは仕方ありませんが、他のユーザにまで影響が及ぶことは問題です。入力時のバリデーション不備が根本原因ではありますが、フェールセーフのつもりの例外処理があだとなって、影響が大きくなってしまいました。奥氏のコメントの意図は上記のようなものでしょう。
これに対する私のコメントと奥氏のリプライ。
徳丸 浩 奥さん、この問題を引き続き考えていたのですが、要約すると、処理が走り始めてから入力値に問題があったことがわかった場合、処理を中途半端に打ち切るのではなく、サニタイズしてでも最後まで走らせた方が安全(な場合が多い)ということでしょうか? 例外を発生させることとの選択になると思いますが
Kazuho Oku はい。そのように考えます。例えば、上のコメントにあげたケースは、処理を最後まで走らせずに例外をあげた結果、サービスにDoS脆弱性が発生していた、と理解すべき事象だと思います。はせがわさん(@hasegawayosuke)も参入
Yosuke Hasegawa 「例外の粒度」的な観点ていうのはないんでしょうか。粒度というかスコープというのか。上の例でいえば、例外によって、不用意な発言した人の該当tweet *だけ* が消え去るのなら問題ないんでしょうけど、現実には広告を含め該当tweet以降のHTMLがすべて消えちゃったから問題になったわけで。
Kazuho Oku 不正なUTF-8シーケンスをうっかりtweetしちゃうとかハッカーこわいwww というのはさておき、「例外の粒度」という観点は同意です。ただ、例えばツイート単位で消しちゃうと、そのデータを UI から編集したり削除したりすることもできなくなるので、できるだけ小さな規模での「書き換え」(つまりサニタイズ)が望ましいと僕は考えます。
徳丸 浩 とても刺激的なお話です。便乗してもう一つお聞きしたいのですが、前提として、DB更新が途中まで走った後でもロールバックすればいいという教科書的な話に対して、現実の大規模な環境だと、ロールバックによって、きれいに「なかったことにする」ことも難しいのだろうと想像しているのですが、そういう理解であっていますか?
Kazuho Oku 現実には入力値検証を完了する前にDB更新を始めることはないでしょうから、脆弱性の話とはずれる気がしますが、大規模な環境だと「きれいに「なかったことにする」」のが難しいことは多いと思います(ただ、そのような回収もれのゴミはユーザーからは見えないようにするべきですが)。
徳丸 浩 ありがとうございます。前から気になっていました。そう言う前提がないと、(セキュリティはさておいたとしても)入力値検証を絶対やりなさいと言う動機付けという点で柔い気がしたものですから上記のような具合に、私のウォールで豪華メンバーに参入いただき、楽しいディスカッションとなりました。
先のTwitterライクなサービスの場合で言うと、以下のような対処案が考えられます。
1.例外を発生させる(元々の仕様)
表示時に異常データがあれば処理を打ち切り、ファイルクローズなど必要な後始末の後プログラムを終了する。この方法だと、先述のように、関係ないユーザまで被害を受けるので、よくありません。
2.当該のツイートのみ表示しない
異常なデータを含むツイートの単位で表示しないという方法です。前項よりはよいですが、UIから異常ツイートを削除するのが不便であり、画面上も不自然な表示になる可能性があります。
3.異常データのみを削除するか、他の文字(「〓」など)に書き換えて表示する
(狭義の)サニタイズです。異常データの影響はもっとも軽微になります。はせがわさんから指摘されていた「例外の粒度」はもっとも細かくなりますが、粒度が細かいことが好ましい例と言えます。
まとめ
結論は以下の通りです。- 処理が始まる前のチェック処理では、厳格なチェック処理と即座の停止をして良い(利用者に適切なメッセージを表示する前提で)
- 処理(更新、表示など)が始まってから異常が発覚した場合は、後々の影響を考えた軟着陸をする必要がある
- その際にサニタイズが有効な場合もある