Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

確認環境

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 do not you register as a user and use Qiita more conveniently?
  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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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