2007-09-24 変数に型のない言語におけるSQLインジェクション対策に対する考察(5)
●数値項目に対するSQLインジェクション対策のまとめ
一連の議論では、以下の条件におけるSQLインジェクション対策について議論している。
- SQLインジェクション対策において、バインド機構が利用できない(したくない)
- 変数に型のない言語(Perl、PHP、Rubyなど)を使用している
- 数値型の列の場合
この場合の対策としては、以下の二種類が機能する。
- SQL文組み立ての前に、数値としての妥当性検証を行う
- 数値項目もシングルクォートで囲み(クォートし)、文字列リテラルと同様のエスケープを行う
数値項目もクォートする方法
このうち、後者の積極的な推進者として大垣靖男氏がおられる。例えば、以下のような記事
すべての変数をエスケープする対策
この方法はすべてのデータベースに利用できる対策です。文字列,整数などデータ型に関わらず変数すべてを文字列としてエスケープすることにより,SQLインジェクションを100%防ぐことが可能となります。例えば,PostgreSQLのSQL文を生成する場合,以下のようにすべてのパラメータを文字列して処理・生成します。
$sql = "UPDATE user SET name = '".pg_escape_string($_POST['name'])."', age = '".pg_escape_string($_POST['age'])."' WHERE id = '".pg_escape_string($_SESSION['USERID'])."';";
[なぜPHPアプリにセキュリティホールが多いのか?:第5回 まだまだ残っているSQLインジェクション(2ページ目)より引用]
上記の「"', age = '".pg_escape_string($_POST['age'])."' WHERE id = '"」の部分がそれである。age欄が「24」の場合、SQLの関連部分は以下のようになる。
UPDATE user SET name = 'ほげほげ', age = '24' where 【後略】
一方、age欄が「';DROP TABLE user--」の場合は、
UPDATE user SET name = 'ほげほげ', age = ''';DROP TABLE user--' where 【後略】
このようにこのSQLはageという数値項目に、数値でない文字列をセットしようとしているので、実行時にエラーとなる*1が、SQLインジェクションは回避される。
SQLにおける暗黙の型変換の是非
数値をクォートして文字列にすると、SQL実行時には、数値型の列と文字列リテラルとの間に「暗黙の型変換」が発生する。先の例では、age列が整数型だとして、set ... age = '24' ...という式の実行時に、文字列('24')から、数値(24)への暗黙の型変換が発生する。
一般に、プログラミングの分野では「暗黙の型変換」が有害であると言われる場合があるが、SQLの場合はどうだろうか。たとえば、Oracle Technology Network(OTN)には、以下のようなドキュメントがある。
暗黙的なデータ変換と明示的なデータ変換
次の理由から、暗黙的な変換または自動変換ではなく、明示的な変換を指定することをお薦めします。
- 明示的なデータ型変換ファンクションを使用すると、SQL文がわかりやすくなります。
- 暗黙的なデータ型変換(特に列値のデータ型が定数に変換される場合)は、パフォーマンスに悪影響を及ぼす可能性があります。
- 暗黙的な変換はその変換が行われるコンテキストに依存し、どんな場合でも同様に機能するとはかぎりません。たとえば、日時値からVARCHAR2型へ値を暗黙的に変換すると、NLS_DATE_FORMATパラメータに指定されている値によっては、予期しない年が戻される場合があります。
- 暗黙的な変換のアルゴリズムは、ソフトウェア・リリースやOracle製品の変更によって変更されることがあります。明示的な変換を指定しておくと、その動作は将来的にも確実になります。
[Oracle SQLの基本要素より引用]
このように、暗黙の型変換は、実装依存であるため動作が不確実になることや、性能面の不利などで推奨されていないことがわかる。
興味深いデータがある。ockeghem(徳丸浩)の日記 - 数値リテラルをシングルクォートで囲むことの是非を書いて以来、筆者のブログに「ORA-01722」の検索キーワードで訪れた人が累計74人もおられる。同じキーワードにより、Gooleなどで検索してみると、、同エラーコードに対する対処法の質問や、対処法FAQの説明などがいくつもヒットする。FAQができるくらい「暗黙の型変換」にはまる人が多いことになる。筆者のブログへの来場者も、現実に「ORA-01722」のエラーで困り果てた末にやってきて、がっかりして(なんだ、SQLインジェクションの説明か)立ち去った人が大半ではないだろうか。すなわち、現実に暗黙の型変換は、予期しない問題に遭遇する可能性が高く、書法的に推奨されない。
数値をクォートすると性能が悪化するケース
次に性能面ではどうか。筆者自身は、数値をクォートすることで性能劣化するパターンを見つけられていなかった*2が、最近奥 一穂のお仕事ブログに興味深いデータが示された。
鴨志田さんに教えていただいたのですが、MySQL のクエリは数値をクォートしない方が高速になるらしいです。たとえば以下の例では、160万件の整数から4の倍数を数えていますが、数値をクォートしないほうが約50%も高速になっています。
[Kazuho@Cybozu Labs: MySQL の高速化プチBKより引用]
筆者も手元のノートPCで追試を試みたところ、50%もの差はでなかったが、数値をクォートしない方が約20%高速になるという結果が確認できた*3。
また、SQLインジェクション対策に限らず、SQLにおける暗黙の型変換という広い文脈でとらえると、既に報告の通り、インデックスが利用されなくなるために性能が大幅に低下する場合がある。
ではどうしたらよいか
今まで見てきたように、数値をクォートしてエスケープする方法は、数値項目に対するSQLインジェクション対策としては機能するが、副作用が多い。筆者としてはSQLを組み立てる直前での数値チェックをお勧めしたい。以下のような仕様の関数を作ればよいだろう。
- 入力パラメータを数値として妥当性チェックする
- 妥当の場合は数値をそのまま返す
- 妥当でない場合は例外を発生させる
この関数は、pg_escape_stringなどと同じように使える*4ので、置き換えなどもしやすいであろう。先にあげた「暗黙の型変換」のデメリットが解消されるだけでなく、SQL実行前にエラー(例外)にできるので、アプリケーションの信頼性という点でも少し有利であろう。
なお、このエントリーはSQLインジェクション対策のための数値チェックであるが、アプリケーションの信頼性向上のための「入力値検証」は、上記とは独立に実施されることをおすすめする*5。
自作コンパイラの部屋 > Web Application Security(WAS)に関する話題の目次 - SQLインジェクション > 数値項目に対するSQLインジェクション対策のまとめ
*1 ockeghem(徳丸浩)の日記 - 数値リテラルをシングルクォートで囲むことの是非参照
*2 SQLの「暗黙の型変換」で実行速度が遅くなるのはどのような場合か参照
*3 奥氏の環境はMyISAMだが、筆者の環境はInnoDBというエンジンの違い、奥氏はキー指定をしていないが、徳丸はキー指定をしているなどの違いに依存するものと思われる
*4 数値のクォートは外す必要がある
404方面でも絶賛されていたPHP逆引きレシピを購入した。本書はとても丁寧な仕事で素晴らしいと思ったが、セキュリティに関しては若干残念な思いをしたので、それを書こうと思う。 目次は以下のようになっている。 第1章 準備 第2章 PHPの基本構文 第3章 PHPの基本テ
このエントリでは、SQLにおいて「暗黙の型変換」を使うべきでない理由として、具体的な「ワナ」をいくつか紹介します。 数値項目に対するSQLインジェクション対策のまとめにて説明したように、RDBの数値型の列に対してSQLインジェクション対策をする方法として、以下の三..