84
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

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

確認環境

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();
}

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
84
Help us understand the problem. What are the problem?