SQLの検査方法について書いた勢いで、SQLの識別子の扱いについて書いてみます。
議論としては、1年以上前に結論が出ている話ですw
徳丸さんの記事では、テーブル名が外部から指定可能な設定で説明がされていますが、少々エッジケース気味なので、今日の日記では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年以上前の徳丸さんをはじめとした方々とほぼ同じだと思います。