エスケープは出力時に

CGIプログラムは、HTMLを出力とすることが多い (というか殆ど) ため、入力データに対して HTML エスケープを施すものが多く見られます。
入力部
cgi    =CGI::new()
title  =CGI::escapeHTML(cgi.params['title'])
artist =CGI::escapeHTML(cgi.params['artist'])
asin   =CGI::escapeHTML(cgi.params['asin'])
出力部 (eRuby)
<table>
<tr>
<th>タイトル</th>
<td><%=title%></td>
</tr>
<tr>
<th>アーティスト</th>
<td><%=artist%></td>
</tr>
<tr>
<th>購入</th>
<td><a href="http://www.amazon.co.jp/dp/<%=asin%>">Amazon.co.jp<a></td>
</tr>
</table>
実行結果 (HTML出力)
<table>
<tr>
<th>タイトル</th>
<td>American McGee's &quot;ALICE&quot; (Original Music Score)</td>
</tr>
<tr>
<th>アーティスト</th>
<td>Chris Vrenna</td>
</tr>
<tr>
<th>購入</th>
<td><a href="http://www.amazon.co.jp/dp/B00005OB0J">Amazon.co.jp<a></td>
</tr>
</table>

HTMLのタグや属性の記述に使用される <, >, &, " といった特殊文字をエスケープすることで、クロスサイトスクリプティングなどの不正な出力を防止しているわけです。 このような、入力時にエスケープ処理を施す手法は多くの書籍・サイトなどで紹介され、実際に公開されている Web アプリケーションにおいても広く用いられています。 しかしながら、この手法には大きな問題点があります。

データベースに格納する場合

例えば、このCGIプログラムが、入力値をHTMLとして出力するだけでなく、データベースに格納するような仕様になっている場合、どんなことが起こるでしょう? (下のコード例では PostgreSQL に、Ruby::DBI を使用して格納)

dbh.do(
'INSERT INTO album(title, artist, asin) VALUES (?, ?, ?');',
title, artist, asin)

この操作によって生成されるSQLコマンドは、以下に示す文字列となります。(SQL では、文字列定数はシングルクォート ' で囲む。文字列定数にシングルクォートが含まれる場合は、これを二つ重ねることでエスケープする。)

INSERT INTO album(title, artist, asin) VALUES ('American McGee''s &quot;ALICE&quot; (Original Music Score)', 'Chris Vrenna', 'B00005OB0J')

SQL では、文字列定数中でダブルクォート " をエスケープする必要はありませんが、入力時にHTMLに合わせてこれをエスケープしてしまったため、データベースには、アルバムのタイトルとして、

American McGee's &quot;ALICE&quot; (Original Music Score)

という値が記録されてしまうこととなります。

出力時エスケープ

エスケープを出力時に行うようにすれば、出力形式に応じて適切なエスケープ手法を選択することができるようになります。

入力部
cgi    =CGI::new()
title  =cgi.params[title]
artist =cgi.params[artist]
asin   =cgi.params[asin]
出力部 (eRuby)
<table>
<tr>
<th>タイトル</th>
<td><%=CGI::escapeHTML(title)%></td>
</tr>
<tr>
<th>アーティスト</th>
<td><%=CGI::escapeHTML(artist)%></td>
</tr>
<tr>
<th>購入</th>
<td><a href="http://www.amazon.co.jp/dp/<%=CGI::escapeHTML(asin)%>">Amazon.co.jp<a></td>
</tr>
</table>

このようにすれば、先の例のように、入力値をSQLに埋め込む場合でも、余計なエスケープをかけずに、正しい値をデータベースに保存することができるようになります。

まとめ

「出力の形式」が指定されなければ、「入力値をどのようにエスケープすべきか」という問いは無意味です。 このことを理解せずに、入力に対して一律にHTMLエスケープを施してしまうと、HTML 以外の形式 (SQL, CSV等) での出力に不具合をきたしかねません。
しかしながら、先に述べたように、この手法は「サニタイズ (sanitize)」として多くの書籍・サイトで紹介されているため、すでに広く認知され、普及・採用されてしまっています。 この「サニタイズ」が多くの人に受け入れられている理由として、以下のことが考えられるでしょう。

  • HTML以外の出力を考慮していない。
  • 「入力時」にまとめてエスケープ処理を施すことで、チェックの手間が減り、コードの複雑さが低減されると誤解している。
  • 何も考えずに、書籍などの解説を鵜呑みにしている。

いずれにせよ、「入力時のエスケープ処理」は本質的に正しくない手法であり、バグや脆弱性の原因となるので、これを採用することは避けるべきです。

関連記事: 「サニタイズ言うな」キャンペーン

また、「サニタイズ」という呼称を与えることで、これが本質的に正しい対策だと勘違いされたり、異なる手法を同じ「サニタイズ」という名前で呼ぶことで議論に齟齬を生じるケースが多いので、「サニタイズ」という言葉そのものを使うな、という意見もあります。

担当: 成田 (配列全体にエスケープをかける機能など必要ない)