SQL識別子の扱い

SQLの検査方法について書いた勢いで、SQLの識別子の扱いについて書いてみます。

議論としては、1年以上前に結論が出ている話ですw

間違いだらけのSQL識別子エスケープ | 徳丸浩の日記

徳丸さんの記事では、テーブル名が外部から指定可能な設定で説明がされていますが、少々エッジケース気味なので、今日の日記ではORDER BY句のカラム名が外から指定可能な前提で、どんな攻撃が可能なのか考えてみます。

例としてSNSサイトのユーザ検索機能のSQLをとりあげます。

SQL文は下記のようなイメージです。

SELECT nickname, prefecture, greeting
FROM members
WHERE nickname like ... AND prefecture=...
ORDER BY {$order}, nichname

検索結果画面にはSELECTした結果のニックネーム、都道府県、挨拶文のみが表示されます。このSNSでは、氏名や誕生日等の詳細なプロフィールは、許可した相手以外には表示されない仕様だとします。

この場合にどんな攻撃が可能か?という話です。

ちなみにSQL文の「$order」の部分は外から指定可能になっていますが、エスケープなり、入力チェック(「\A\w+\z」など)なりがされるとします。

攻撃の例としては「order=birthday」みたいなパラメータを与える攻撃が考えられます。membersテーブルにbirthdayカラムがあるなら、ユーザを誕生日順に並び替えることができます。

並び替えができるなら、あるユーザ(誕生日がわかっているユーザ)と、別のユーザの誕生日を比較することができます。攻撃者自身がダミーのアカウントを登録すれば、バイナリサーチなどを使うことで、楽に特定の人の誕生日を割り出せることになります(画面には直接表示はされませんが、大小関係から値を詰められる)。

誕生日だけでなく「order=telnumber」とか「order=salary」とか「order=password(_hash)」とかも嫌な感じです*1。これが商品検索の機能であればダメージは少なそうですが「order=cost(原価の意味)」とかは少し嫌な感じかもしれません。

上記の例でわかるように、この手の攻撃が問題になるのは、SELECT対象のテーブル内(JOIN対象も含む)に、秘密にすべき情報が含まれている場合です。またフォームのパラメータ名などから、カラム名が推測可能であることが前提となります。

ただし、カラム名の推測は不要な場合もあります。これは、$orderが半角英数等のチェックのみがされ識別子としてクォートされずにSQL文に入る、かつSELECT対象のカラムに秘密情報が含まれている(典型的には「SELECT * FROM ...」のケース)、の2条件がそろう時です。そんな場合は、「order=1」「order=2」といった番号指定が可能になります。*2

開発側の対策としては『要件として「どのカラムでのソートを許すか」を決めて、それをホワイトリストで実装する。望ましくは「1」->「preference」のようにカラムをコード化する』ということになるでしょう。ソートを許すカラムは、通常は値を見せて良いカラムのみです。要件検討の結果「全カラムOK」ということもあるでしょうが、制限が必要な場合もあるはずです。

結論としては、1年以上前の徳丸さんをはじめとした方々とほぼ同じだと思います。

*1:Password_hashの場合は、Saltが付きならば攻撃の意味がなさそうです。

*2:大抵のSQL教本に書いてある構文で、どのメジャーDBMSでも使えます。攻撃側としては、番号とカラムのマッピングを調べていく作業がめんどいと思いますが。