PostgreSQL 使用時の文字列のエスケープ回避問題
2006.03.06 追記
この問題については、iakio日誌(2006-02-15)において、PHP スクリプトで回避する方法の例と、PostgreSQL に Patch を当てて回避する方法を示しておられます。問題の影響を受ける場合は参考にすると良いと思います。
1月22日に書いた、addslashes() による SQL 文字列のエスケープ回避問題の続きです。PostgreSQL でさらに検証してみました。
結論としては、前回と同様ですが、PostgreSQL に関しては、SJIS は使用しない方が安全という事になります。
クライアントの文字コードとして、SJIS を使用している場合、addslashes() によるエスケープは既に指摘されている通りですが、PostgreSQL 用の文字列エスケープ関数である、pg_escape_string() を使用している場合でも問題があります。
また、その他の PostgreSQL の一部の関数でも影響を受けることを確認しました。ただし、PHP 5.1.0 以降で導入された pg_prepare() と pg_execute() を使用した場合は、問題ないようです。
使用した PHP と PostgreSQL のバージョンは PHP 5.1.2 と PostgreSQL 8.1.3 ですが、他のバージョンでも同様の問題があると考えられます。
検証
以下は検証です。
まず、test というデータベースに以下のテーブルを作成します(文字コードは EUC_JP になっています)。
test=> SELECT * FROM t1; val ----- OK (1 row) test=> SELECT * FROM t2; val ----- NG (1 row)
PHP 5.1.2 で以下のようなスクリプトを作成して検証しました。
<?php $conn = pg_connect( "host=localhost dbname=test user=test" ); if ( ! $conn ) { exit( 'Could not connect' ); } // クライアントの文字コードを SJIS に変更 pg_set_client_encoding( 'SJIS' ); echo "Client Encoding:" . pg_client_encoding() . "\n"; // SQL インジェクション検証用文字列 $input = "\x95'; SELECT * FROM t2; --"; // 文字列のエスケープに pg_escape_string() を使用 $query = "SELECT * FROM t1 WHERE val = '" . pg_escape_string( $input ) . "';"; echo "SQL: " . $query . "\n"; $result = pg_query( $query ); if ( ! $result ) { exit( "Query failed\n" ); } // 結果表示 echo "Result: \n"; var_dump( pg_fetch_all( $result ) ); ?>
結果は以下の通りになりました。本来は bool(false) が結果として表示されるはずです。
Client Encoding:SJIS SQL: SELECT * FROM t1 WHERE val = ''; SELECT * FROM t2; --'; Result: array(1) { [0]=> array(1) { ["val"]=> string(2) "NG" } }
原因は、0x9527 という文字列「\x95'」 は SJIS の範囲内ではないのですが、これが SJIS として扱われているという点にあるようです。
つまり、\x95' を pg_escape_string() でエスケープ処理を行うと、「'」は「''」に変換され、\x95'' になり、上のコードでは、エスケープ処理後、以下のような SQL が構築されています。
SELECT * FROM t WHERE val = '\x95''; SELECT * FROM id; --';
PostgreSQL 側が「\x95'」を SJIS の一文字として扱ってしまうため、「'」を回避して、任意の SQL をその後ろに記述することが可能になっています。
内部で pg_convert() を使用する関数の場合(例: pg_select() )
問題は、pg_escape_string() だけではなく、pg_convert() および、pg_convert() を内部で使用している関数も影響を受けることを確認しました。
例えば、以下のように、pg_select() を使用したコードを実行します。
<?php $conn = pg_connect( "host=localhost dbname=test user=test" ); if ( ! $conn ) { exit( 'Could not connect' ); } pg_set_client_encoding( 'SJIS' ); $input = "\x95'; SELECT * FROM t2; --"; print_r( pg_select( $conn, 't1', array( "val" => $input ) ) ); ?>
結果は以下の通りです。
Array ( [0] => Array ( [val] => NG ) )
PEAR::DB の Prepared Statement を使用した場合
また、PEAR::DB の Prepared Statement を使用した場合でも問題がありました。
<?php require( 'DB.php' ); $dsn = 'pgsql://postgres@localhost/test'; $db =& DB::connect( $dsn ); $db->query( "SET client_encoding TO 'SJIS'" ); $dh = $db->prepare( "SELECT * FROM t1 WHERE val = ?" ); $res = $db->execute( $dh, "\x95'; SELECT * FROM t2; --" ); if ( PEAR::isError( $res ) ) { exit( $res->getMessage() . "\n" ); } while ( $res->fetchInto( $row ) ) { echo $row[0] . "\n"; } ?>
PEAR::DB ではクライアントの文字コードを指定する関数がありませんでしたので、「SET client_encoding TO 'SJIS'」 というクエリを発行しています。
結果
NG
pg_prepare() と pg_execute() を使用した場合
PHP 5.1.0 から導入された PostgreSQL の Prepared Statement を使用する関数である、pg_prepare() と pg_execute() を使用した場合は、このような問題がないことを確認しました。
<?php $conn = pg_connect( "host=localhost dbname=test user=test" ); if ( ! $conn ) { exit( 'Could not connect' ); } pg_set_client_encoding( "SJIS" ); $result = pg_prepare( $conn, 'query', 'SELECT * FROM t1 WHERE val = $1' ); $result = pg_execute( $conn, 'query', array( "\x95'; SELECT * FROM t2; --" ) ); var_dump( pg_fetch_all( $result ) ); ?>
結果
bool(false)