※ 本記事は2017年6月29日に公開されたものです。

ここまではアプリケーションが発行するSQL文の構文がほぼ決まっているケースを紹介しました。しかし、アプリケーションによっては、検索条件列を自由に組み合わせられるなど、動的にSQL文が生成されるケースもあります。動的にSQL文を生成するケースでは文字列を連結してSQL文を生成することが多いです。このような場合でも、SQL構文は文字列の連結で生成しても、ユーザーの入力値にはバインド変数を利用することも可能です。下のサンプルアプリケーションは名前、住所、電話番号で部分一致検索が可能なアプリケーションです。

ここで名前に「田」が住所に「市」という文字が含まれるという条件で検索してみます。SQL文が動的に生成され、NAME列とADDRESS列への検索条件が含まれていますが、バインドが利用されているのが分かります。

画面に条件が入力された場合に、動的にSQL文を生成し、バインド変数を利用した条件句を追加するコードは、例えば以下のような形で実現できます。

String sqltext = "select name, address, phone from dbsecdemo.addrbook where ";
if (!inName.isEmpty()) sqltext = sqltext + "name like ? and   ";
if (!inAddr.isEmpty()) sqltext = sqltext + "address like ? and   ";
if (!inPhon.isEmpty()) sqltext = sqltext + "phone like ? and   ";
sqltext = sqltext.substring(0, sqltext.length()-7);
if (!(inName.isEmpty() & inAddr.isEmpty() & inPhon.isEmpty())) sqltext = sqltext + " escape '\\'";
out.println(sqltext + "
");
PreparedStatement pstmt = conn.prepareStatement(sqltext);

int i = 1;
if (!inName.isEmpty()) {
    pstmt.setString(i, "%" + inName.replaceAll("\\\\","\\\\\\\\").replaceAll("%","\\\\%").replaceAll("_","\\\\_") + "%");
    i = i + 1;
}
if (!inAddr.isEmpty()) {
    pstmt.setString(i, "%" + inAddr.replaceAll("\\\\","\\\\\\\\").replaceAll("%","\\\\%").replaceAll("_","\\\\_") + "%");
    i = i + 1;
}
if (!inPhon.isEmpty()) pstmt.setString(i, "%" + inPhon.replaceAll("\\\\","\\\\\\\\").replaceAll("%","\\\\%").replaceAll("_","\\\\_") + "%");

ResultSet rset = pstmt.executeQuery();

動的SQLの利用とバインド変数の利用は両立可能ですので、動的SQLをどうしても利用しなくてはならない場合でも、文字列結合による検索条件の値の設定はおこなわないようにすることを推奨します。

 

さて、文字列結合を利用している場合でもSQLインジェクションの対策としてSQLインジェクションに利用される文字をエスケープするなどサニタイズする方法があります。
Oracle Databaseでは、DBMS_ASSERTパッケージを用意しており、例えばENQUOTE_LITERALファンクションでは、引数を半角シングルクォート「’」で囲い、同時に途中にシングルクォートがないことを確認して文字列が途中で切れていないことを保証します。このパッケージはSQLインジェクション対策に利用することができますが、あくまでPL/SQLでプログラムを書いているときのみです。JAVAでプログラムを書いている場合にはこのパッケージを利用してもSQLインジェクションを確実に防げないケースもありますので注意が必要です。

エスケープはOracle Databaseが用意するパッケージやメソッドを利用しなくても、カスタム開発で実装することも可能です。実際にここまで数回のSQLインジェクションの記事では、LIKE句による部分一致検索をおこなうときに特殊記号として利用する半角パーセント「%」、半角アンダーバー「_」やESCAPE句で特殊記号として指定する半角バックスラッシュ「\」をエスケープするためにreplaceAll関数を利用して文字を置き換えています。同じ要領でSQLインジェクションで利用される特殊記号をエスケープすればSQLインジェクション攻撃を防ぐことができますが、どの文字をエスケープすればよいかというのは場合によって異なります。半角シングルクォートをエスケープすればよいのはあくまで検索条件の文字列型として変数が指定されているときであり、たとえば検索条件が数字型の場合にはシングルクォートでは囲われていませんので、半角シングルクォートをエスケープするのではなく、変数に代入しようとしている値が数字かどうかのチェックが必要になります。
文字をエスケープしたり、データ型を確認したりとSQLインジェクションを起こさないようにサニタイズすることは不可能ではありませんが、確実にSQLインジェクションが起こらないようにするためには、さまざまな条件でそれぞれの対応をとる必要があり、深い知識が必要になります。また、特定の環境やバージョンのみで発生する新しいSQLインジェクションの手法が開発される可能性もあります。

万が一、考慮漏れがあった場合にはSQLインジェクションの被害を受ける可能性がありますので、可能な限りバインド変数の利用を推奨します。

 

「もくじ」にもどる