昨日はWebシステムの入力について触れましたが、今日は出力部分について触れます。
出力の形態
Webアプリを作るに当たって、外部に出力するものもいろいろとあります。
- DBへの問い合わせ
- OSコマンドの呼び出し
- メール送信
- 外部APIの呼び出し
- HTMLなどの生成
システム内部にある間には、データとコマンドの区別は基本的にはっきりしていますが(例外は後述)、これらのように他のシステムへデータを持ち出すと、単なるデータがコマンドに誤認されるというリスクが生じるため、他のシステムに渡す出力についてはきちんと管理する必要があります。
出力形態ごとの分析
SQLの場合
RDBMSはSQLというプログラミング言語で操作しますので、プログラムからSQLを生成する必要が生じてきます。その過程で、うっかりデータを混ぜ込めばSQLインジェクションとなってしまいます。
純粋な値のデータ→バインド機構が使える
元来、プリペアドステートメントは「あらかじめSQL文の枠組みだけ送ってサーバサイドで準備しておくことで、本体のデータ処理を効率化すること」が役割なのですが、副産物として「値は値として送信されるので、値とSQL構文を混同してしまうことがない」というメリットもあって、セキュリティ的な意味合いでも使われています(サーバが対応しないエミュレーションでは、後者の効果のみとなります)。
それ以外→エスケープ、あるいはホワイトリストでの検証
ただし、「SQL文の枠組み」を送るという性質上、あとから供給できるものは値だけで、列名などのキーワードはバインドの対象とできません。また、値の中でも、LIKE
で使う検索用の値や正規表現など、値自身が機能性を持つ場合も、そちらのエスケープが必要となります。
- 列名や
ASC
/DESC
など…テーブル構造は決まっているはずなので、ホワイトリストと照らし合わせて検証 -
LIKE
の引数→適切なエスケープ関数を使う
OSコマンドの場合
理想論をいえば、他の手段で可能なことは他の手段でしてしまったほうがいいでしょう。それでも使わざるを得ない場合には、
-
system
系のシェルを介する関数ではなく、できるだけexec
のような、実行ファイルを直接実行する方法を使う - 処理に使うファイルの名前は、可能な限り「英数字+拡張子」ぐらいにして、余計な文字で変な挙動を起こさせないようにする
- 一時ファイルは競合状態でも介入が不可能な、安全な手法で生成する
などの緩和策を取っておきましょう。
メール送信の場合
メールを送信する場合、ヘッダの途中に改行コードを入れると、そこで別のヘッダだと認識されるメールヘッダ・インジェクションが発生します。
本文はともかく、メールヘッダとして入れる文字列として改行は不適切なので、きちんと弾いておきましょう。
HTML出力する場合
HTMLもタグを出力しないといけませんが、想定外のものをタグとして出力してしまうとXSSを起こします。もちろん「適宜エスケープする」という手段もありますが、確立したHTML手法があるのであれば、それに乗ってしまうのも1つの手法です(Railsでの例)。
なお、HTMLの中にJavaScriptやCSSなど別な言語で解釈されるコードが入る場合、その言語で見て安全性を確保しないといけませんので要注意です。
その他
言語内でも、正規表現などで「文字列で動作をコントロールする」場面があります。問題となった例としてRubyのKernel.#open
がありまして、これは開く文字列の先頭が|
だと、コマンドを実行してそのプロセスとのパイプを開くという仕様になっています。このような挙動が必要な場合は稀でしょうし、開くものの種類も通常は事前にわかるでしょうから、ファイルならFile.open
、URLならOpenURI.open_uri
、本当にプロセスとパイプしたい場合はIO.popen
など、機能特化したメソッドを使いましょう(参考、Rubocopからの警告)。