[PR]小規模ECサイトに最適なWAF、SiteGuard Lite
2011年03月22日
_PHP5.3.6からPDOの文字エンコーディング指定が可能となったがWindows版では不具合(脆弱性)あり
PHP5.3.6からPDO/MySQLにてデータベースの接続文字列にて文字エンコーディングが指定可能となりました。この機能を検証した結果、Linux上では正常な動作を確認しました。Windows版でも一見動作するようですが、動的プレースホルダ内部のエスケープ処理に不備があり、Shift_JISでデータベースと接続している際にSQLインジェクション脆弱性の可能性があります。
きっかけ
@haruyamaさんのツイートがきっかけで、PHP5.3.6からPDOの文字エンコーディング指定が有効となったことを知りました。
PHP 5.3.6以降では PDO/MySQLのDSNでのcharset指定が有効になったようです. (MySQL 4.1.11以降, MySQL 5.0.7以降, もしくはMySQLNDを利用している場合)
[http://twitter.com/#!/haruyama/status/48534861631324160より引用]
これは重要な改良です。以前、「ぼくがPDOを採用しなかったわけ(Shift_JISによるSQLインジェクション) 」で報告したように、従来PDOはデータベース接続文字列中の文字エンコーディング指定を無視しており、my.cnfファイル名を指定する方法でしか文字エンコーディングを正しく指定する方法がありませんでした。この方法を使っていない場合や、Windows環境などmy.cnfのファイル名を接続時に指定できない環境では、SQLインジェクション脆弱性の原因になる場合がありました。文字エンコーディングの指定が容易になると、安心してPDOを使うことができるはずです。
このため、本当に安心してPDOを使えるようになったかどうかを検証してみました。
検証内容
検証環境としては、Windows Server 2003とLinux(Ubuntu 10.04)の2種類の環境を用意しました。
| Windows | Linux | |
| OS | Windows Server 2003 | Ubuntu 10.04 |
| PHP | PHP5.3.6(zip) | PHP5.3.6(ソースからmake) |
| MySQL | MySQL 5.1.37 | MySQL 5.1.41 |
テーブルの文字エンコーディングはUTF-8、データベースとの接続に用いる文字エンコーディングはShift_JISを用いました。MySQLのバージョンが少し古めですが検証には問題ないと思われます。
検証では以下の三点を確認しました。
- データベース接続時に文字エンコーディング指定がなされているか*1
- 文字エンコーディングは正しく変換されるか
- 動的プレースホルダにて文字列リテラルのエスケープは文字エンコーディングを考慮しているか
検証に用いたスクリプトの概要を以下に示します。
<?
// ソースの文字エンコーディングはShift_JIS
try {
$dbh = new PDO('mysql:host=localhost;dbname=test;charset=sjis', USERNAME, PASSWORD);
$sth = $dbh->prepare("select * from test5c WHERE a=:a");
$sth->setFetchMode(PDO::FETCH_NUM);
$a = "ソ\' OR 1 = 1#--"; // 「ソ」の後ろの「\」は「ソ」の2バイト目の0x5cに対するエスケープ
$sth->execute(array(':a' => $a));
while ($data = $sth->fetch()) {
var_dump($data);
}
} catch (PDOException $e) {
die($e->getMessage());
}
結果
WindowsとLinuxの環境で検証した結果を以下に示します。
| Windows | Linux | |
| データベース接続時の文字エンコーディング指定 | ○ | ○ |
| 文字エンコーディングの変換 | ○ | ○ |
| エスケープ時の文字エンコーディング考慮 | × | ○ |
WindowsとLinuxの両方を試した理由は、「Linuxでは期待通り動作するがWindowsには対応していない」状況を想定していたのですが、結果はご覧の通り、Windowsでは中途半端に対応している(けど結局使えない)という予想外の結果となりました。Windows上の動作が合点がいかなかったので、念のためWindows Server 2003のクリーンな環境にMySQL(5.5.10)とPHP(5.3.6)をインストールした環境でも試しましたが、結果は同じでした。古いバージョンのPHPが悪さをしていたなどの問題でもなさそうです。
まとめ
PHP5.3.6以降で対応しているPDOの文字エンコーディング指定を検証しました。Linux環境では期待通り安全に動作し、Windowsではエスケープの文字エンコーディングが考慮されていないという結果になりました。このため、Windows環境では依然として、SQLインジェクション脆弱性の可能性が残ります。
このため、PDOを安全に使うには、静的プレースホルダと文字エンコーディングとしてUTF-8の指定が有効と考えられます。すなわち、ぼくがPDOを採用しなかったわけ(Shift_JISによるSQLインジェクション) で報告したように、以下を推奨します。
- 静的プレースホルダを用いる
- データベース接続の文字エンコーディングにはUTF-8を用いる
具体的には、以下のようにすると良いでしょう。
$dbh = new PDO('mysql:host=localhost;dbname=test;charset=utf8', USERNAME, PASSWORD,
array(PDO::ATTR_EMULATE_PREPARES => false));
// set names は必要ない
// 後はプレースホルダによりSQLを呼び出す
PHP5.3.6のPDO改良の検証は始まったばかりです。とくに、WindowsとLinuxで動作が異なる原因は今回未着手です。このエントリがきっかけとなって、検証に参加される方が増えることを期待します。
追記(2011/03/29)
千葉征弘さん(@nihen)の調査により、この問題の原因がPHPのMySQLネイティブ・ドライバ(Mysqlnd)のバグだと分かりました。詳しくはこちらを参照ください。
*1 Wiresharkによるネットワークキャプチャにより確認しました
[PR]小規模ECサイトに最適なWAF、SiteGuard Lite