Oracle+JavaでPreparedStatement

安全なSQLの呼び出し方
非常に細かい所なんですが、Oracle + JDBC の場合は、文字列リテラルを使った式とプレースホルダを使った式が等価では無い場合があるので、プログラムを書き換える時には注意が必要です。

具体的な事例として java-houseでの例を紹介しておきます。

ではどうすればいいのか考えるというのがここでの話題です。

等価にならない条件ですが、これははっきりしていて、

  1. テーブルの列が固定長文字列として宣言されている
  2. JDBCの標準のパラメータ設定用メソッド PreparedStatement#setString(int, String) でパラメータ値をセットしている
  3. Oracleの固定長文字列の比較の「末尾のスペースの有無の無視」が有効であることが前提となるような検索条件である

という3つの条件を全て満たしている場合に限られます。
(http://java-house.jp/ml/archive/j-h-b/027640.html 参照)

3つの条件が揃うと発生する問題なので、この条件のどれかが絶対に満たされないような方式を採用することで回避が可能になります。

1.テーブルの列の型を変更して可変長文字列(VARCHAR2等)にする。
 可能であればこの方法をお勧めします。
2.WHEREの条件にJDBCの PreparedStatement#setString(int, String)で値をセットしたプレースホルダを使うのをやめる
 例1:無名PL/SQLブロックやストアドプロシージャでも実装できる処理であれば、プレースホルダの値を一旦PL/SQLの固定長文字列変数に代入してから WHERE の条件式で使う
 例2:プレースホルダへの値のセットに OraclePreparedStatement#setFixedCHAR を使う
3.末尾のスペース無視が無効でも正しく検索が行われるような条件を指定する
 例1:末尾にスペースの含まれるデータを格納しないという制約(もしくは運用上のルール)のもとで運用する。・・・設計段階でそのように作りこんでおく必要があります。
  例2:列の長さと検索条件が一致するようにスペースを補う。・・・どうしても対応できない場合の回避策です。ただし、Oracleでは長さ0の文字列は NULL と等価なので、「式 = ''」は常に NULL なのに対し、後ろにスペースを補って比較すると全桁スペースと比較した結果が TRUE になってしまうので、あらかじめ長さ 0 の文字列だけ別扱いしておく必要があります。

まだ書き足りない気がするけどとりあえずここまで。

実際のところ

DBがOracleJavaでアプリケーションを書いている場合に、プレースホルダを使うと検索にかからなくなってしまうが、かといってテーブルの列の型を変えることも出来ないという理由で、結局何のエスケープもせずに文字列リテラルを生成してSQLインジェクションが可能になってしまっている事例は結構多いのではないかと思います。

2010/04/22追加

末尾にスペースが無いことが保障されていれば回避策不要だったのを書き忘れていました

2010/04/23修正

末尾にスペースが無い話と自力で末尾にスペースを補うのはどちらも「末尾のスペース無視でも正常に検索できるようにする」ということなのでちょっと整理。