結論
セキュリティ対策の一環としてSQLの数値リテラル(が期待されるパラメータ)をシングルクォートでくくる(文字列扱いする)手法は使用すべきでない
理由
少なくともMySQLでは標榜されている通りの動作はしない上、意図しない副作用が発生する。
解説
数値リテラルをシングルクオートでくくる手法は、これを推奨する大垣氏によると次のような効果があるとされています。参考:なぜPHPアプリにセキュリティホールが多いのか? 第14回 減らないSQLインジェクション脆弱性
- 攻撃目的の入力が行われてもSQLインジェクションが発生しない
簡単に攻撃用の文字列が検出でき
る
これらはどちらも数値型が期待されるパラメータに不正なデータが渡されるとクエリエラーが発生することが前提となっています。例として、
SELECT * FROM table WHERE id = $VALUE; --ここでidは整数型
というSQLがあった場合に、$VALUEに"1 OR 1 = 1"という入力がされていた場合、WHEREが無効になってしまいますが、
SELECT * FROM table WHERE id = '$VALUE';
というSQLであれば、$VALUEが数値として解釈できなければクエリエラーになる(であろう)ということです。
しかし、MySQLの場合、データベース側で文字列を可能な限り数値に変換するという動作をするため、クエリエラーが発生しません。どのような変換がされるかはこれだけは覚えておきたい!!MySQL の6つの自動変換 - sakaikの日々雑感~©編に詳説されていますが、簡単に言えば
- 頭から読んでいって数値と解釈できなくなったら後ろは無視する(例:123abc456→123)
- 頭から数値と解釈できなければ0とみなす(例:abc123→0)
というものです。これは、数値(が期待される)パラメータを文字列扱いすればクエリエラーになる、という考えとは相容れません。
実際に試してみると、以下の通りwarningは出ていますがエラーにはなりません。
mysql> SELECT * FROM test; +------+------+ | id | text | +------+------+ | 0 | 000 | | 1 | 111 | +------+------+ 2 rows in set (0.00 sec) mysql> SELECT * FROM test WHERE id = '1a'; +------+------+ | id | text | +------+------+ | 1 | 111 | +------+------+ 1 row in set, 2 warnings (0.00 sec) mysql> SELECT * FROM test WHERE id = 'hoge'; +------+------+ | id | text | +------+------+ | 0 | 000 | +------+------+ 1 row in set, 2 warnings (0.00 sec)
このような変換によってSQLインジェクションを成立させることは難しいとおもいますので、SQLインジェクションを防止するという点については機能しているといえるかもしれません。しかし、大垣氏はこのように主張されています。
整数型にキャストすればよいのでは? と考えられた方もいると思います。しかし,キャストする方法は2つの理由でベストプラクティスとは呼べません。キャストした場合,不正な攻撃目的の入力が行われてもクエリエラーが発生しない可能性があります。
この手法の前提が「不正入力があればクエリエラーが発生する」というものである以上、SQLインジェクションが成立しないのは単なる偶然と言うべきだと考えます。また、クエリエラーが発生しないので、簡単に攻撃用の文字列が検出でき
るという機能も実現されていません。
また、クエリエラーが発生しないだけでなく、開発者の意図しない検索条件の成立や異常データの挿入が行われるという問題があります。上記の例の通り、"1a"→1、"hoge"→0として検索条件が成立していますが、これはほぼすべての場合において開発者が意図したものではないはずですし、常識的な動作とも異なります。
なお、この手法を用いなかった場合には、MySQLにおいてもクエリは失敗します。
mysql> select * FROM test WHERE id = hoge; ERROR 1054 (42S22): Unknown column 'hoge' in 'where clause' mysql> SELECT * FROM test WHERE id = 1a; ERROR 1054 (42S22): Unknown column '1a' in 'where clause'
これはおそらく開発者の意図通りでしょう。
もっと根本的なレベルでこの手法を用いるべきでない理由があります。このような問題がこの手法を推奨する側から全く示されていないことです。本記事の内容は、MySQLという超メジャーなデータベースについてすら全く検証が行われていないことを示しています。文字列→数値の変換については、数値リテラルをシングルクォートで囲むことの是非 - ockeghem(徳丸浩)の日記において一部のデータベースについて検証された結果が記載されていますが、データベースによってかなりばらつきがあります。パラメータやバージョンの違いによっても動作が変わるかもしれません。実際手元のMySQLはこの表とは違う動作をしました。
以上の通り、この手法は
- 少なくともMySQLにおいては標榜されている機能は全く実現されない
- 他のデータベースにおいても慎重に検証が必要だが、その分の労力はパラメータの検証やテストを確実に行う方向に費やすべき。
- 仮に検証の結果ある時点では動いたとしても何らかのきっかけで意図した動作をしなくなる可能性がある
という理由で全く使うべきではありません。


最近のコメント