昨日のエントリ「SQLインジェクションゴルフ - 認証回避の攻撃文字列はどこまで短くできるか?」にて、認証回避の攻撃文字列が5文字にできる(「'OR'1」)ことを示しましたが、@masa141421356さんと、やまざきさん(お二人とも拙著のレビュアーです)から、idとpwdにまたがった攻撃例を示していただきました。やまざきさんの例は、MySQL限定ながら、なんと3文字です。これはすごい。
SELECT * FROM users WHERE id =''OR' AND pwd = '>''
このままだと分かりにくいので、WHERE句の式を以下にわかりやすく示します。
id ='' OR ' AND pwd = ' > ''
これは、列idが空文字列(長さ0の文字列)か、文字列「 AND pwd = 」が空文字列よりも大きい場合となりますので、後者が必ず真で、WHERE句は常に成立することになります。
第1のパラメータでリテラルを終端して、途中の式を文字列リテラルの中に入れてしまっていますね。これはXSSではよくやる方法ですが、SQLインジェクションに応用した例は珍しい気がします。
SELECT * FROM users WHERE id =''' AND pwd = '|''
WHERE句を分かりやすく表示すると以下の通りです。
id =''' AND pwd = ' | ''
青地の中は、「' AND pwd = 」ですね。これと '' (空文字列)とで、| (ビット単位の論理和)の演算をしています。この結果は 0 になります。
この結果、先のSQL文は、「SELECT * FROM users WHERE id=0」と同じです。これを実行してみましょう。
やまざきさんの解は、MySQLの「暗黙の型変換」の仕様を巧妙に利用したものと言えます。
同時に、あらためてSQLの暗黙の型変換は危険だなとも思いました。暗黙の型変換の危険性については、以前の拙稿「SQLの暗黙の型変換はワナがいっぱい」を参照下さい。
@masa141421356さんの攻撃例
@masa141421356さんのツイートを引用します。@ockeghem大抵のDBでid=''OR' AND pwd='>' ' が通ると思います(id側に「'OR」, pwd側に「>' 」で6文字)。長さ0の文字列がNULL扱いされないDBなら最後のスペースを消して5文字です。ここで「長さ0の文字列がNULL扱いされ」るDBというのはOracleを指します。出題ではMySQLとPostgreSQLを対象としていてどちらも「長さ0の文字列がNULL扱い」しないので、5文字パターンでいけます。すなわち、idに「'OR」、pwdに「>'」です。この場合のSQL文は下記となります。
— masa141421356 (@masa141421356) June 24, 2013
SELECT * FROM users WHERE id =''OR' AND pwd = '>''
このままだと分かりにくいので、WHERE句の式を以下にわかりやすく示します。
id ='' OR ' AND pwd = ' > ''
第1のパラメータでリテラルを終端して、途中の式を文字列リテラルの中に入れてしまっていますね。これはXSSではよくやる方法ですが、SQLインジェクションに応用した例は珍しい気がします。
やまざきさんの攻撃例
次に、@ymzkei5さんのツイートです。@masa141421356@ockeghem MySQLで、id=''' AND pwd='|''; (idに「'」、pwdに「|'」の計3文字)(|の代わりに&や^も)が動いてしまって悩む。id=0の意味?idに「'」、pwdに「|'」の計3文字です。この場合のSQL文は下記となります。
— やまざきkei5 (@ymzkei5) June 24, 2013
SELECT * FROM users WHERE id =''' AND pwd = '|''
WHERE句を分かりやすく表示すると以下の通りです。
id =''' AND pwd = ' | ''
青地の中は、「' AND pwd = 」ですね。これと '' (空文字列)とで、| (ビット単位の論理和)の演算をしています。この結果は 0 になります。
なぜ0になるかというと、MySQLは数値が要求される文脈に文字列があった場合は数値(浮動小数点数)に暗黙の型変換され、数値として正しくない文字列の場合は 0 になるからです。すなわち、| の左右のオペランドは、どちらも 0 になります。mysql> select ''' AND pwd = '|'';
+--------------------+
| ''' AND pwd = '|'' |
+--------------------+
| 0 |
+--------------------+
1 row in set, 2 warnings (0.00 sec)
この結果、先のSQL文は、「SELECT * FROM users WHERE id=0」と同じです。これを実行してみましょう。
つまり、'tanaka'=0と解釈されているわけですが、MySQLの場合(他のDBもですが)、文字列と数値の比較の場合、暗黙に文字列を数値に変換してから比較します。そして、MySQLに限り、数値に変換できない文字列は 0 に変換するからです。この結果、'tanaka' = 0 は真になります。mysql> SELECT * FROM users WHERE id=0;
+--------+--------+
| id | pwd |
+--------+--------+
| tanaka | a2f9hy |
| yamada | sn6s3n |
+--------+--------+
2 rows in set, 2 warnings (0.00 sec)
やまざきさんの解は、MySQLの「暗黙の型変換」の仕様を巧妙に利用したものと言えます。
まとめ
昨日紹介したSQLインジェクションゴルフの解答例として、@masa141421356さんとやまざきさんの解答を紹介しました。どちらの解も、SQL文の一部を文字列リテラルに閉じ込めるという技を披露してくださっていて、興味深いものです。特に、やまざきさんの3文字解には脱帽です。同時に、あらためてSQLの暗黙の型変換は危険だなとも思いました。暗黙の型変換の危険性については、以前の拙稿「SQLの暗黙の型変換はワナがいっぱい」を参照下さい。