続:SQLのバインド機構は「エスケープ処理された値」をはめ込むのか

前回のエントリSQLのバインド機構は「エスケープ処理された値」をはめ込むのか - ockeghem(徳丸浩)の日記に対応して、mi1kmanさんのブクマ経由で、訂正が出ていることを知った。

訂正内容
1ページ目を下記のように変更いたしました(2個所)。

バインド値はエスケープ処理した後にプレースホルダにはめ込むので、悪意あるSQL文が挿入されても、その実行を阻止することができる(図1-2)。

SQL文のひな型とバインド値は個別にデータベースに送られ、構文解析されるので、バインド値に悪意あるSQL文が挿入されても、その実行を阻止することができる(図1-2)。

http://www.impressit.co.jp/inside/?p=791

なんとなく私のエントリの断片が散りばめられているのを別にしても、『悪意あるSQL文…の実行を阻止することができる』というあたりに、サニタイズ的発想が依然として感じられる。悪意があろうがなかろうが、淡々と「どんな入力に対しても正しく対応する」というプログラムを書きさえすればSQLインジェクションは防げるのであるから、『実行を阻止』などという仰々しい表現を用いる必要はないのだ。こういう表現はIPSやWAFに対して用いるべきだろう。

 また、私が指摘していなかった箇所についても変更が行われている。指摘があったのだろう。

しかし、LIKE句を含むSQL文などについてはバインド機構の適用に注意を要する。これは、「%」や「_」といったワイルドカード文字がバインド機構によってエスケープされないため、割り当てる変数の内容によっては予想外の問い合わせ結果が返ってくる可能性があるからだ。この場合、バインド機構に変数を割り当てる前に、エスケープ処理を使用して、変数に格納されている文字列に含まれるワイルドカード文字をエスケープする必要がある。

SQLステートメントを書く時に必ずバインド機構を使用すれば、バインド機構に不具合がない限り、SQLインジェクションは不可能だと考えられる。

ただし、データベースの種類によっては、バインド機構を使用した場合、LIKE句やユーザー定義関数などの引数にバインド値を指定すると、開発者の思惑どおりの動きをしない場合がある。そのため「%」や「_」といったワイルドカード文字がバインド値として送られないように値検証処理でエラーにしたり、エスケープしたりする必要がある。

有効な対策方法はデータベースの種類によって異なるのでベンダからの情報を参照していただきたい。

http://www.impressit.co.jp/inside/?p=791

 元の表現よりはマシだが、まだ誤解を招きやすいと思う。
 LIKE句のワイルドカードエスケープする理由は、「%」などがワイルドカードとしての「%」なのか、検索文字列の一部としての「%」という文字なのかを区別するためだ。だから、『ワイルドカード文字がバインド値として送られないように』という表現はおかしい。ユーザにワイルドカード指定を許しているサイトであれば「%」などのエスケープは不要の場合もあるし、許していない場合はescapeなどによりエスケープする必要がある。これはアプリケーション仕様によるところなので、「おまかせ」にはできないのだ。
 また、『有効な対策方法はデータベースの種類によって異なる』のは確かだが、『SQLインジェクション大全』と大きくでるからには、主要DBについては、ベンダー固有情報にも踏み込むべきではなかったか。

その他の指摘

ついでなので、その他の指摘をいくつかしておきたい。

(1)図1のSQLのWHERE句にシングルクォートが残っているのがおかしい。

(2)エスケープを実行する場所

エスケープ処理は、SQL文を生成する直前に実施するのが定石である。入力直後にエスケープ処理を行うと、SQL文を生成する直前に別の文字列操作が行われた場合、SQLインジェクションが成功する文字列が完成してしまう可能性があるからだ。

 このような場合もあるだろうが、それ以前に、プログラムの内部では入力データそのままで扱わないと不便で仕方ないだろう。処理のたびにアンエスケープしなければならない。たとえば、表示する場合はどうするのか。近藤氏は、「攻撃を受けない」ことばかり考えているようだが、アプリケーション開発で考慮すべきことはセキュリティ以外の方がずっと多いのだ。


(3)エスケープ対象の文字

なお、エスケープ処理の必要がある文字列はデータベースサーバーによって異なるので、利用環境に合わせて実施しなければならない

これは、『文字列』ではなく『文字』


(4)エラーメッセージ
 2ページ目。

アプリケーションで作成したエラーメッセージを表示するのはよいが、エラーメッセージに必要以上の情報を表示したり、状態に応じてエラーメッセージの種類や表示内容を変えたりしてはいけない。攻撃者はそれらの情報を観察し、ブラインドSQLインジェクションを仕掛ける可能性があるからだ。ブラウザ上には、処理が失敗したことが分かる程度の簡単なエラーメッセージを表示し、詳しくはログファイルなどに出力し、ユーザーに見せないようにすべきである。

概ねよいのだが、『ブラウザ上には、処理が失敗したことが分かる程度の簡単なエラーメッセージを表示』という部分がよくない。処理が『失敗した』ことが分かれば、攻撃に対するヒントになり、ブラインドSQLインジェクションに利用される場合もあるだろう。処理が失敗したのか、入力値検証ではじいたのかを含めて、もっと大雑把なメッセージにしたほうがよい。


(5)都道府県コード

あるいは都道府県を「01〜48」のコードで表している場合は、「01〜48」の文字列のみを有効とするなどである。

 日本には、47の都道府県があるのだから、説明なしに「01〜48」とすると読者は面食らう。私の身の回りでは、48番目の県について「海外を示す」とか「いや、ぷりぷり県だ」とか、さまざまな憶測があったが、説明がないので本当のところは不明だ。


(6)マルチバイト文字について

例えば、MySQLPostgreSQLは「\」をエスケープする必要がある。

 デフォルトの挙動としてはそうなのだが、PostgreSQLの場合standard_conforming_stringsというパラメータをonにすると、「\」のエスケープはしなくてもよい。SQLインジェクション対策という点では、onの方が安全なので、『「\」をエスケープする必要がある』と断言するのではなく、standard_conforming_stringsについても言及するべきではないか。
 一方、MySQLにもNO_BACKSLASH_ESCAPESという同種のオプションがあるが、比較的最近追加されたオプションで、私は試したことがない。こちらは、実績があまりなさそうで、推奨まではできにくそうだ。


(7)5C問題が発生する文字エンコーディング

このような現象はShift_JISにのみ起きる訳ではなく、すべての文字コードにおいて発生しうる

 『このような現象』の指す内容が曖昧だが、直前に指摘していた0x5Cの問題のことを指すのであればShift_JIS系列の文字エンコーディング(cp932などを含めて)に固有の問題といっていい。EUC-JPやUTF-8などではこの問題はないのだ。そして、それが文字エンコーディングとしてShift_JISを避けたほうがよいことの根拠になっている。
 技術文書の一般論として、曖昧な内容を「すべて…発生しうる」と断言するのは控えるべきだろう。


 まだ指摘したい内容はあるが、意見の違いに属するようなものは排除し、根拠が明確に示せるものに絞った。読者の参考になれば幸いである。

参考:WASForum Conference 2008講演資料「SQLインジェクション対策再考」