LoginSignup
81
90

More than 5 years have passed since last update.

PDOでATTR_EMULATE_PREPARESを適切に設定してないとSQLインジェクションの原因になるかも(MySQL編)

Last updated at Posted at 2013-07-22

確認環境

MySQL 5.5.27
PHP 5.4.7

現象

PDO::setAttribute( ATTR_EMULATE_PREPARES, false )

をせず、prepareしても静的プレースホルダは使用されない。

確認

<?php

$dsn = 'mysql:dbname=test;host=localhost';
$user = 'root';
$password = '';

try{
    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    $sql = 'select blog_id, blog_name from blogs where blog_name like ?';
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array('%my%'));

}catch (PDOException $e){
    print('Error:'.$e->getMessage());
    die();
}

結果

 MySQLのクエリーログで確認

setAttribute(PDO::ATTR_EMULATE_PREPARES, true)またはデフォルトの場合:

130722 14:40:59 2 Connect root@localhost on test
2 Query select blog_id, blog_name from blogs where blog_name like '%my%'
2 Quit

setAttribute(PDO::ATTR_EMULATE_PREPARES, false)した場合:

130722 14:41:26 3 Connect root@localhost on test
3 Prepare select blog_id, blog_name from blogs where blog_name like ?
3 Execute select blog_id, blog_name from blogs where blog_name like '%my%'
3 Close stmt

3 Quit

まとめ

 SQLインジェクションの対策としてプリペアドステートメントの使用が推奨されますが、PDOにおいてデフォルトではPDO::ATTR_EMULATE_PREPARESがtrueに設定されており、この状態ではprepareでプレースホルダを指定してもエミュレートされ、実際にMySQLにわたる際にはプリペアドステートメントではなくなってしまいます(普通にSQL発行するのと同じ)。この状態では、 自動エスケープはされるものの 、SQLインジェクションの危険があると思います。
(2013.07.22 19:14 訂正: 「追記」を参照してください。)

 PDOで静的プレースホルダを使用する場合(少なくともMySQLでは)、PDO::ATTR_EMULATE_PREPARESを「false」にするべきでしょう。
(え、常識?)

(2013.07.22 18:57 追記)
 ATTR_EMULATE_PREPARESがデフォルトの状態(true)でエミュレーションが効いている場合、ドライバ側で自動エスケープしてくれるみたいです。

 だから安心かというとそうではなくて、文字コード変換を利用した下記攻撃でSQLインジェクションできてしまいました。

 ちなみにATTR_EMULATE_PREPARESをfalseにすると下記プログラムでもTRUNCATEは実行されません(SQLインジェクション対策できている)。

$dsn = 'mysql:dbname=test;host=localhost';
$user = 'root';
$password = '';

try{
    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//  $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    $pdo->query('SET NAMES sjis');

    $attack = "\x97' OR 1=1; truncate blogs; #";

    $sql = 'select blog_id, blog_name from blogs where blog_id = ?';
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array($attack));

    while($result = $stmt->fetch(PDO::FETCH_ASSOC)){
        print_r($result);
    }

}catch (PDOException $e){
    print('Error:'.$e->getMessage());
    die();
}

81
90
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
81
90