今日の日記では、OracleでのSQLインジェクション対策について書きます。
以下のようなコード(PHP)があるとします。
<?php ... $foo_escape = str_replace("'", "''", $foo); $sql = "SELECT * FROM table1 WHERE foo='$foo_escape'"; $stmt = OCIParse($dbh, $sql); ...
「'」を「''」にエスケープしてSQL文に埋め込み、Oracleで実行(Parse)しています。
割とよくあるコードかもしれませんが、これだとSQLインジェクション攻撃に脆弱な場合があります。
不正な文字列
DBサーバとのコネクションにEUC-JP(JA16EUCTILDE)を使用しているとします。
ここで、$fooに「[0xA1]' OR 1=1--
」のような、EUC-JPとして不正なバイトを含む文字列を与えられるとします([0xA1]
はHEX表記でA1となるバイトを表しています)。
この場合、エスケープ処理を経て以下のSQL文が実行されます。
SELECT * FROM table1 WHERE foo='[0xA1]'' OR 1=1--'
Oracleは網掛け部分を1文字とみなします。その結果、エスケープが回避されてしまいます。
不正なバイトによって後続の文字を壊すXSS攻撃の手法がありますが(参考)、そのSQLインジェクション版と考えると判りやすいかもしれません。
脆弱な場合とは
この現象は、PHPのアプリケーションに限った話ではありません。
Perl(+DBI)やC言語のCGIなどでも、アプリケーション内部で文字エンコーディングを正規化せず、かつ単純な文字列置換(バイト列置換)によってエスケープ処理を行なっている場合には、同様の現象が発生します。
また、コネクションの文字コードがEUC-JPの場合だけで発生するわけではありません。UTF-8やSJISでも同様の現象が発生します。
PostgresやMySQLのSJIS問題は割と知られていますが、それ以外の文字コード・DBMSでの問題は余り知られていないかもしれません。稀ですが、実稼働しているアプリケーションにこの手の脆弱性が見つかることもありますので、注意が必要だと思います。
対策
対策は、自作のエスケープ+文字列結合ではなく、DBMSが正規に提供するAPIを利用してSQL文を生成することです。
OracleはバインドAPIを提供しています(エスケープAPIは提供していません)。バインドを使うのが最も確実な解決方法といえると思います。
DBMSによる違い
MySQLとPostgresについて調べてみました。
MySQLでは、不正なバイトで後続の文字が壊される現象は発生しないようです。そのため、EUC-JPやUTF-8などでaddslashes関数などによる勝手エスケープをしていても、この手の現象は発生しないと思います。
しかし逆に、アプリケーションで中途半端にマルチバイト文字を意識した独自のエスケープ処理を行なってしまうと(例えばmb_ereg_replace関数を使うなどすると)、SQLインジェクションに脆弱になります。
Postgresは、SQL文実行の際に文字エンコーディグのチェックを行ない、不正なバイトが含まれるSQL文に対してはSQLエラーを発生させるようです。
一般にDBサーバがSQL文を実行する際には、DBサーバはDBクライアントにエラーを返すことができます。不正なバイトを含むSQL文を受取ったDBサーバはいかに振る舞うのが安全かという観点で考えると、Postgresのようにエラーを返すのが望ましいと思います(それがMUSTであるとまでは言えないと思いますが)。