2008年6月1日日曜日

そろそろSQLエスケープに関して一言いっとくか~SQLのエスケープ再考~

補足

この記事は旧徳丸浩の日記からの転載です。元URLアーカイブはてなブックマーク1はてなブックマーク2
備忘のため転載いたしますが、この記事は2007年11月26日に公開されたもので、当時の徳丸の考えを示すものを、基本的に内容を変更せずにそのまま転載するものです。
補足終わり


本稿ではSQLインジェクション対策として、SQLのエスケープ処理の方法について検討する。
最近SQLインジェクション攻撃が猛威を振るっていることもあり、SQLインジェクションに対する解説記事が増えてきたようだが、対策方法については十分に書かれていないように感じる。非常に稀なケースの対応が不十分だと言っているのではない。ごく基本的なことが十分書かれていないと思うのだ。
SQLインジェクション対策には二通りある。バインド機構を使うものと、SQLのエスケープによるものだ。このうち、SQLのエスケープについて、十分に書かれているテキストが見当たらないのだ。このため、自分で書いてみようと思う。
IPAの「安全なウェブサイトの作り方改訂第三版」ではSQLのエスケープについて以下のように説明されている。
1)-2 バインド機構を利用できない場合は、SQL 文を構成する全ての変数に対しエスケープ処理を行う
解説 これは、根本的解決 1) のバインド機構を利用した実装ができない場合に実施すべき実装です。
 利用者から入力されるパラメータや、データベースに格納された情報などに限らず、SQL 文を構成する全ての変数や演算結果に対し、エスケープ処理を行ってください。エスケープ処理の対象は、SQL文にとって特別な意味を持つ記号文字(たとえば、「'」→「''」、「\」→「\\」 など)です。
 なお、SQL 文にとって特別な意味を持つ記号文字は、データベースエンジンによって異なるため、利用しているデータベースエンジンに応じて対策をしてください。データベースエンジンによっては、専用のエスケープ処理を行うAPIを提供しているものがあります(たとえば、Perl ならDBIモジュールのquote()など)ので、それを利用することをお勧めします。

引用した部分は、一般的な内容を網羅しているものの不満もある。短い文章の中で「など」が3回も現れることに象徴されるように、あいまい性の残る文章となっている。これはデータベースの製品依存のところでやむを得ないというのはよく理解できるのだが、結果として、読者に「データベースエンジンのマニュアルを読め」と言っているの等しい。それでいて、読者には、マニュアルのどこをどう調べたらよいかまでは示していない。「エスケープ処理の対象は、SQL文にとって特別な意味を持つ記号文字」と書かれているが、この説明だと、「;」や「=」、空白までエスケープしなければならないと誤解する読者が出てくるかもしれない。実際には、これらの文字をエスケープする必要はないし、SQLの標準規格には、そもそもこれらの文字をエスケープする手段が用意されていない。エスケープの必要がないので手段も用意されていないのだ。
そこで、「安全なウェプサイトの作り方」を補完するような形で、もう少しSQLエスケープについて書き足してみたい。

SQLインジェクションのおさらい

SQLインジェクション攻撃では、SQLに渡すパラメータ部分にSQL断片を挿入し、SQLの意味を書き換えることによって行われる。 バインド機構を使わずに自前でSQLを組み立てる場合、SQLに対するパラメータはSQLのリテラル(定数)の形で渡される。
リテラルには複数の型があるが、通常問題になるのは数値と文字列である。
数値リテラルの場合:
SELECT * FROM XXXXX WHERE NNUM=●●●      -- ●●●は数値、例えば 123
文字列リテラルの場合:
SELECT * FROM XXXXX WHERE EID='■■■'      -- ■■■は文字列、例えば S853
なぜリテラルを構成する文字列(●●●や■■■)を操作することでSQLの構文まで変わるのか。それは特殊記号などでリテラルを終端させ、その後にSQL断片を埋め込むからだ。
数値リテラルを終端させるには、数値以外の文字を加えればよい。
例: 123OR TRUE         -- 数値リテラル123の後に、OR TRUE が続く。123 OR TRUEと同じ
文字列リテラルを終端させるには、単一引用符「'」を加えればよい
例: A'OR'A'='A

この例を先のSQLに適用すると、

SELECT * FROM XXXXX WHERE EID='A'OR'A'='A'

となる。つまり、WHERE句は常に真となる。

SQLインジェクション対策の基本的な考え方

すなわち、SQLインジェクション対策の方針としては、リテラルを勝手に終端させないようにすることが必要なのだ。そして、この処理は数値リテラルと文字列リテラルとでは、方法が異なる。
数値リテラルの場合は、数値以外の文字が出てきた時点でリテラルの終端となるので、数値としての妥当性検証を行うことになる(数値項目に対するSQLインジェクション対策のまとめ参照) 。
一方、文字列リテラルの場合は、文字列リテラルのエスケープを行えばよい。SQL92などSQLの標準規格で規定しているのは、単一引用符「'」のエスケープであるが、データベースの種類によっては、円記号(バックスラッシュ)「\」のエスケープも必要となる。

商用データベースの場合

商用データベースの場合は、SQL標準に従い、単一引用符「'」を「''」と重ねる処理を行う。OracleSQL ServerIBM DB2についてはリファレンスと動作の両方で確認した。

オープンソース・データベースの場合

オープンソースのデータベースとして広く普及しているMySQLの場合、文字列リテラル中にC言語風の「\」を使ったエスケープシーケンスが記述できる。従って、文字列リテラル中に「\」自体を記述する際には、「\\」とする必要がある。一方、単一引用符「'」は「''」としてもよいし、「\'」としてもよい。その他、二重引用符「"」が利用できるなど自由度が高い。
オープンソース・データベースのもう一方の雄PostgreSQLの場合は事情が少し複雑となるが、デフォルトではMySQLと同じで、単一引用符と円記号(バックスラッシュ)の両方をエスケープする必要がある。しかし、設定パラメータstandard_conforming_stringsがonの場合(デフォルトはoff)は、Oracleなどと同じ挙動となる(現実にはもう少し複雑だが稿をあらためて説明したい)。
まとめると以下のようになる。

データベース元の文字エスケープ後
Oracle
MS SQL
IBM DB2
'''
MySQL
PostgreSQL
''' または \'
\\\

なお、念のため補足すると、エスケープ処理はセキュリティ対策のために行うものでは元々なく、与えられたパラメータに対して正しく処理を行うために必要な処置である。入力に「'」や「\」が使えないと不便で仕方がないし、現実的に不具合が生じるだろう。

その他のデータベース製品の場合はどうしたらよいか

今回説明した内容にもっとも近い記述があるドキュメントとしては、佐名木智貴氏の近著「セキュアWebプログラミングTips集(ソフト・リサーチ・センター)」がある。同書では、SQLエスケープの基本として
「'」は、「''」(シングル・クォート2個)にエスケープ処理することで、SQLインジェクションから防御することができる(同書P210)
とした上で、
mySQLとPostgreSQLの場合のSQLインジェクション対策として、入力データをSQL文の文字列リテラルとして使う場合、「'(シングル・クォート)」と「\」をSQLエスケープすること(同書P213)
と指摘している。
非常に丁寧な仕事ぶりで好感を持った。ただ、同じページの以下はいただけない
筆者の知らないデータベース・ソフトウェアでは、SQLが拡張され、それ以外のメタキャラクタもあるかも知れない。ぜひ読者諸氏には今一度、自分の使っているデータベース・ソフトウェアのSQLリファレンスを通読することを推奨する(同書P213)。
「SQLリファレンスを通読」とは・・・無茶言うなよと思う。
実際には通読する必要はなく、「リテラル」、「定数」、「文字列」などのキーワードを手掛かりに、文字列リテラルの項を探すとよい。本稿で引用したリファレンスもこのようにして探したものである。
Oracleの場合を例にマニュアルの見方を説明しよう。題材として、Oracle10gのオンラインマニュアルを利用する。
まず、目次から「リテラル」を探すと、「2 Oracle SQLの基本要素」に「リテラル」や「テキスト・リテラル」という項が見つかる。「テキスト・リテラル」の項を読むと、
  • cは、データベース・キャラクタ・セットの任意の要素です。リテラル内の一重引用符(')の前には、エスケープ文字を付ける必要があります。リテラル内で一重引用符を表すには、一重引用符を2つ使用します
  • ''は、テキスト・リテラルの始まりと終わりを示す2つの一重引用符です。
このように、エスケープの必要な文字は一重引用符「'」であること、エスケープの仕方は一重引用符を2つ使用することであることがわかる。他のデータベース・ソフトウェアでも、同様に探すことができるだろう。
本稿を参考に正しいSQLインジェクション対策を実施していただきたい。

続く(徳丸浩の日記 - SQLインジェクション対策 - SQLエスケープにおける「\」の取り扱い)
参考:WASForum Conference 2008講演資料「SQLインジェクション対策再考」

追記(2010/03/26)

このエントリを書いた後、IPA非常勤研究員として、SQLインジェクションの正しい対策方法について調査・検討しました。その成果は「安全なSQLの呼び出し方」という冊子(安全なウェブサイトの作り方別冊)という形にまとめられました。ぜひご活用いただければと思います。ダウンロードはこちらから。

本日のツッコミ(全3件)

_ 佐名木 (2008年06月24日 21:59)
mySQL での「\」のエスケープについて知ったのは、(私にとっては無知の知の領域にある)mySQL のリファレンスを通読している時でした。私には衝撃的でした。無意味だと思うからです。「'」→「''」だけで十分なのに、「\」を導入する必要性を感じないからです。
なので、読者の人が使っているかも知れない私の知らない DB については、DB 開発者が自由に拡張しているかも知れないから注意してね。
というつもりなのです。

出版までにちゃんとまとめなくて中途半端だったので、次回は正規表現(Like演算子)の時のエスケープについてお願いします。
といっても正規表現の使用場面を考えると、そもそもインデックスを張っていないカラム対象が多いのでそれほど問題にはならないとは思いますが、プログラミング書法という観点でもお願いしたいですね。

_ momo (2010年05月06日 21:24)
クオートをエスケープした内容をSQLに格納するのはいいが、次にDBからその内容を利用するとき、エスケープは消え、クオートは裸のままであることをおわすれなく。

_ Nkzn (2010年05月27日 14:08)
↑セカンドオーダーSQLインジェクション?っていうんでしたっけ?

0 件のコメント:

コメントを投稿

フォロワー

ブログ アーカイブ