2013年12月16日月曜日

PHP+PDO+MySQLの組み合わせではSQLインジェクション攻撃で複文呼び出しが可能


基礎からのPHPという書籍を読んでおりましたら、SQLインジェクションの攻撃例として、以下のSQL文ができあがる例が紹介されていました。PHP+PDO+MySQLという組み合わせです。
SELECT * FROM tb2 WHERE ban=1;delete from tb2
2つのSQL文がセミコロンで区切って1つにまとめられていますが、これを「複文(multiple statement)」と言います。私は、SQLインジェクション攻撃の文脈で複文が使える組み合わせを調べたことがあり、PHPとMySQLという組み合わせでは、複文は使えないと思っていましたので、この攻撃は成立しないのではないかと思いました。
しかし、決めつけも良くないと思い手元の環境で動かしてみたところ、あっさり動くではありませんか。

PDOを用いてMySQLを呼び出す場合は複文が実行できると気づきましたが、なぜPDOの場合だけ複文が実行できるのかが気になりました。以前の調査の時はPHPのmysql関数により調査しましたが、mysqli(MySQL 改良版拡張モジュール)でも、複文は実行できません。mysqli::multi_queryを使っている場合は複文実行できますが、これはこのメソッドが複文実行を目的としたものなので、当然ですね。

ここで、検証用の環境を紹介します。まず下記のテーブルを準備します。表名や列名が変なのは、「基礎からのPHP」の踏襲ですのでご容赦ください。
CREATE TABLE tb2(ban int, nam varchar(30));
INSERT INTO tb2 VALUES(1, 'usagi');
次に検証用スクリプトですが、これも「基礎からのPHP」のサンプルを少し修正して使用しています。$bは外部から変更可能な変数とします。
<?php
$b = "1";
$db = new PDO("mysql:host=192.168.xx.xx;dbname=db;charset=utf8", "xxxx", "xxxx");
$ps = $db->query("SELECT * FROM tb2 WHERE ban=$b");
while ($row = $ps->fetch()) {
  echo $row[0] . " : " . $row[1] . "\n";
}
これを動かすと、下記の表示となります。
$ php pdo.php
1 : usagi
次に $b に攻撃用文字列を指定しましょう。先のPHPスクリプトの2行目を以下のように変更します。
$b = "1 or true;update tb2 set ban=ban+1";
SQLインジェクション攻撃の結果、呼び出されるSQL文は下記となります。
SELECT * FROM tb2 WHERE ban=1 or true;
update tb2 set ban=ban+1
第1のSQL文(SELECT)のWHERE句が、ban=1 or trueに変わっているので全件表示となります。第2のSQL文は、列banをインクリメントします。その結果、PHPスクリプト呼び出しの度に、数字が1ずつ増加します。
$ php pdo.php
1 : usagi
$ php pdo.php
2 : usagi
$ php pdo.php
3 : usagi
複文が動いていることは明らかですが、なぜ動くのかが疑問です。そこで、PHPとMySQLの通信をWiresharkでキャプチャしてみました。まずはクエリーのリクエストです。

このリクエストはmysql関数が呼び出すものと何ら変わりません。そこで、DB接続時の設定が違うのではないかと思い、そちらを調べてみました(Chasetがlatin1になっているのはPHP5.2.1でのキャプチャであるためです。PHP5.3.5までは接続文字列で指定した文字エンコーディングは無視されていました)。

Supports multiple statementsのビットがセットされています。これが原因のようですね。mysqlおよびmysqliの場合は、このビットは0にセットされています。
次に、該当のソースを見てみます。ext/pdo_mysql/mysql_driver.c のmysql_driver.cです。
448:         int connect_opts = 0
449: #ifdef CLIENT_MULTI_RESULTS
450:                 |CLIENT_MULTI_RESULTS
451: #endif
452: #ifdef CLIENT_MULTI_STATEMENTS
453:                 |CLIENT_MULTI_STATEMENTS
454: #endif
CLIENT_MULTI_STATEMENTSはMySQLのAPIで複文の呼び出しを許可するフラグです。MySQLのマニュアルから該当箇所を引用します。
クライアントが複数行クエリ(‘;’ をステートメントの区切りとする)を送信する可能性があることをサーバに通知する。このフラグが設定されていない場合、複数行クエリは無効。MySQL 4.1 の新機能。
MySQL :: MySQL 4.1 リファレンスマニュアル :: 11.1.3.43 mysql_real_connect() より引用
これを読むと、条件コンパイルの意味が分かりますね。CLIENT_MULTI_STATEMENTSはMySQL 4.1以降で有効ですが、PDOはMySQLの3.xもサポートしていたため、CLIENT_MULTI_STATEMENTSをサポートしていないMySQLを考慮する必要があるということでしょう。
試みに、上記引用部分をコメントアウトしてPHPをビルドしたところ、MySQLで複文実行はできなくなりました。これらから、PDOは複文の実行を(少なくともソースコード上は)明示的に許可していることが分かります。


PDO+MySQLで複文実行できる条件

PHPの様々なバージョンでの試験やソースコードの状況から、PDO+MySQLで複文実行できる条件は下記になると思われます。
  • MySQL4.1以上(上記から)
  • PHP5.2.1以上(試験から)
  • PDO::setAttribute(PDO::ATTR_EMULATE_PREPARES, false); を呼んでいない
PHP5.2.0以下を使っている場合、、あるいは PDO::setAttribute(PDO::ATTR_EMULATE_PREPARES, false); を呼んでいる場合は、PDO::queryによるSQL呼び出しであっても下記のようにいったんprepareされます。

そして、下記のエラーになります。
Error Code: 1064
SQL Code: 42000
Error eessage: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'update tb2 set ban=ban+1' at line 1
queryでは複文は許可されても、Prepare statementでは許可されないようです。


複文を実行できる影響

 MySQLの場合、SQLインジェクションの影響は情報漏えいとファイルの読み書きが主なものですが、複文を実行できる場合は、これに加えて、以下が可能となります。いずれも、権限がある場合です。

  • データベースの更新(いわゆる改ざん)
  • データベースの行削除やテーブルの削除
  • 新規テーブルの作成
  • 新規データベースの作成
  • 新規ユーザの作成
  • データベースの削除
  • その他SQLにて実行可能なこと

SQLインジェクション攻撃の影響が詳しく載っている金床本にもMySQLにて複文実行可能な可能性については言及されていません(MS SQLやPostgreSQLは言及されている)。これはMySQLに対する一般的な認識だと思いますが、このため、MySQLを使っている場合にSQLインジェクションでデータ変更されないとして、これらリスクを受容している場合は特に注意が必要です。


対策

この件について特別な対策は必要ありません。複文が実行されない環境であってもSQLインジェクション脆弱性は受容できない脆弱性として取り扱うべきです。淡々とSQLインジェクション対策すればよいと考えます。以下を推奨します。

  • 原則として文字列連結でSQL文を組み立てない
  • パラメータはプレースホルダにより指定する
  • 特別な事情がなければ静的プレースホルダを使う。元々その方が安全だが、PDO::setAttribute(PDO::ATTR_EMULATE_PREPARES, false);により複文の実行も予防できる
  • 詳しくはIPAの「安全なSQLの呼び出し方」を読む

まとめ

PDO+MySQLの組み合わせで、アプリケーションにSQLインジェクション脆弱性があると、通常の攻撃に加えて複文による攻撃が可能となり、データの変更や削除が可能となることを紹介しました。
ただし、大騒ぎするような問題ではないと考えます。大騒ぎしなければならないのは、あなたの管理するWebサイトやWebアプリケーションにSQLインジェクション脆弱性がある場合です。SQLインジェクションは元々「あってはならない」脆弱性であって、複文実行が可能になったからと言って、それが変わるわけではないからです。

0 件のコメント:

コメントを投稿

フォロワー