[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種類の環境を用意しました。

WindowsLinux
OSWindows Server 2003Ubuntu 10.04
PHPPHP5.3.6(zip)PHP5.3.6(ソースからmake)
MySQLMySQL 5.1.37MySQL 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の環境で検証した結果を以下に示します。

WindowsLinux
データベース接続時の文字エンコーディング指定
文字エンコーディングの変換
エスケープ時の文字エンコーディング考慮×

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

Google