[PR]小規模ECサイトに最適なWAF、SiteGuard Lite


2011年03月29日

_PDO/MySQL(Windows版)の文字エンコーディング指定の不具合原因

前回のブログ「PHP5.3.6からPDOの文字エンコーディング指定が可能となったがWindows版では不具合(脆弱性)あり」にて、PHP5.3.6(本エントリ執筆時点での最新版)からPDOにてサポートされた文字エンコーディング指定がWindows版では正しく動作しないことを報告しました。Linuxでは正常に動作するので不思議だなという話になっていたのですが、千葉征弘さん(@nihen)が調べてくださった結果がtwitter上で発表されました(toggerによるまとめ「PHP5.3.6からPDOの文字エンコーディング指定が可能となったがWindows版では不具合にまつわる件」)。この件につき、当ブログでも報告します。

問題点の復習

PHP5.3.6以降のPDOでは、データベースに接続する際の文字エンコーディングを、接続文字列内にcharset=utf8等の形で指定することができます。PDO+MySQLの組み合わせの場合、Windows版のPHPからShift_JISでMySQLに接続する場合に、文字列のエスケープが正常に動かず、SQLインジェクション脆弱性の原因になっていました。

問題の原因

PHPのMySQLネイティブ・ドライバ(Mysqlnd)にバグがありました。ext/mysqlnd/mysqlnd_charset.cというファイル中のShift_JISの判定部分にバグがあります。

330 #define valid_sjis_head(c) ( (0x81 <= (c) && (c) <= 0x9F) && \
331     (0xE0 <= (c) && (c) <= 0xFC))
332 #define valid_sjis_tail(c) ( (0x40 <= (c) && (c) <= 0x7E) && \
333     (0x80 <= (c) && (c) <= 0x7C))

[http://svn.php.net/viewvc/php/php-src/trunk/ext/mysqlnd/mysqlnd_charset.c?view=markup]

これだけだとどこが悪いのか分かりにくいかもしれませんが、cp932の場合の同様の処理と比べるとよく分かります。

213 	#define valid_cp932head(c) ( (0x81 <= (c) && (c) <= 0x9F) || (0xE0 <= (c) && c <= 0xFC))
214 	#define valid_cp932tail(c) ( (0x40 <= (c) && (c) <= 0x7E) || (0x80 <= (c) && c <= 0xFC)) 

[http://svn.php.net/viewvc/php/php-src/trunk/ext/mysqlnd/mysqlnd_charset.c?view=markup]

全体のORをとらないといけないところをANDをとっているので、マクロvalid_sjis_headは常に偽を返します。このため、すべてのバイトが1バイト文字と認識されることになり、文字エンコードとしてLatin1(ISO8859-1)を指定した場合と同じ挙動となっています。これは、先の評価の結果と一致します。文字エンコーディングとしてcp932を指定した場合は正しく動作します。

また、333行目の末尾付近の0x7Cは、正しくは0xFCでしょう。ちょっと雑な作りというか、このあたり「やっつけ仕事」という印象を受けます。テストはしていないでしょうね。

なお、WindowsとLinuxで挙動が異なる理由は、WindowsではMySQLとの接続にMySQLネイティブ・ドライバ(Mysqlnd)が使用され、Linux(Unix)ではデフォルトでMySQLクライアント・ライブラリが使用されるからです(参照→PHP公式マニュアル)。上記のバグはMySQLネイティブ・ドライバ固有の問題なので、MySQLクライアント・ライブラリを使用する場合(Unix/Linuxのデフォルト設定)には影響しません。

どうすればよいか

では、どう書けばよいということですが、そもそもデータベースとの接続にShift_JISを使うこと自体が好ましくないので、結局前回の指摘と変わりません。再掲すると以下のようになります。

  • 静的プレースホルダを用いる
  • データベース接続の文字エンコーディングにはUTF-8を用いる

具体的には、以下のようにすると良いでしょう。

$dbh = new PDO('mysql:host=localhost;dbname=test;charset=utf8', USERNAME, PASSWORD,
               array(PDO::ATTR_EMULATE_PREPARES => false));
// set names は必要ない
// 後はプレースホルダによりSQLを呼び出す

ただし、既存のアプリケーションで、かつWindows環境でShift_JISを使っている場合には、文字エンコーディングとしてcharset=cp932を指定するとよいでしょう。その場合でも、array(PDO::ATTR_EMULATE_PREPARES => false)の指定により静的プレースホルダを用いることを強く推奨します。



[PR]小規模ECサイトに最適なWAF、SiteGuard Lite

Google