Oracleでのエスケープ

今日の日記では、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-8SJISでも同様の現象が発生します。

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であるとまでは言えないと思いますが)。

動作検証した環境

以下の環境で検証しました。

  • Oracle 10g Express Edition 10.2.0.1.0(OCI 10.2.0.3-1)
  • MySQL 5.0.26
  • Postgres 8.1.9