2つのSQL文がセミコロンで区切って1つにまとめられていますが、これを「複文(multiple statement)」と言います。私は、SQLインジェクション攻撃の文脈で複文が使える組み合わせを調べたことがあり、PHPとMySQLという組み合わせでは、複文は使えないと思っていましたので、この攻撃は成立しないのではないかと思いました。SELECT * FROM tb2 WHERE ban=1;delete from tb2
しかし、決めつけも良くないと思い手元の環境で動かしてみたところ、あっさり動くではありませんか。
PDOを用いてMySQLを呼び出す場合は複文が実行できると気づきましたが、なぜPDOの場合だけ複文が実行できるのかが気になりました。以前の調査の時はPHPのmysql関数により調査しましたが、mysqli(MySQL 改良版拡張モジュール)でも、複文は実行できません。mysqli::multi_queryを使っている場合は複文実行できますが、これはこのメソッドが複文実行を目的としたものなので、当然ですね。
ここで、検証用の環境を紹介します。まず下記のテーブルを準備します。表名や列名が変なのは、「基礎からのPHP」の踏襲ですのでご容赦ください。
次に検証用スクリプトですが、これも「基礎からのPHP」のサンプルを少し修正して使用しています。$bは外部から変更可能な変数とします。CREATE TABLE tb2(ban int, nam varchar(30)); INSERT INTO tb2 VALUES(1, 'usagi');
これを動かすと、下記の表示となります。<?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"; }
次に $b に攻撃用文字列を指定しましょう。先のPHPスクリプトの2行目を以下のように変更します。$ php pdo.php 1 : usagi
SQLインジェクション攻撃の結果、呼び出されるSQL文は下記となります。$b = "1 or true;update tb2 set ban=ban+1";
第1のSQL文(SELECT)のWHERE句が、ban=1 or trueに変わっているので全件表示となります。第2のSQL文は、列banをインクリメントします。その結果、PHPスクリプト呼び出しの度に、数字が1ずつ増加します。SELECT * FROM tb2 WHERE ban=1 or true; update tb2 set ban=ban+1
複文が動いていることは明らかですが、なぜ動くのかが疑問です。そこで、PHPとMySQLの通信をWiresharkでキャプチャしてみました。まずはクエリーのリクエストです。$ php pdo.php 1 : usagi $ php pdo.php 2 : usagi $ php pdo.php 3 : usagi
このリクエストは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です。
CLIENT_MULTI_STATEMENTSはMySQLのAPIで複文の呼び出しを許可するフラグです。MySQLのマニュアルから該当箇所を引用します。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
クライアントが複数行クエリ(‘;’ をステートメントの区切りとする)を送信する可能性があることをサーバに通知する。このフラグが設定されていない場合、複数行クエリは無効。MySQL 4.1 の新機能。これを読むと、条件コンパイルの意味が分かりますね。CLIENT_MULTI_STATEMENTSはMySQL 4.1以降で有効ですが、PDOはMySQLの3.xもサポートしていたため、CLIENT_MULTI_STATEMENTSをサポートしていないMySQLを考慮する必要があるということでしょう。
MySQL :: MySQL 4.1 リファレンスマニュアル :: 11.1.3.43 mysql_real_connect() より引用
試みに、上記引用部分をコメントアウトしてPHPをビルドしたところ、MySQLで複文実行はできなくなりました。これらから、PDOは複文の実行を(少なくともソースコード上は)明示的に許可していることが分かります。
PDO+MySQLで複文実行できる条件
PHPの様々なバージョンでの試験やソースコードの状況から、PDO+MySQLで複文実行できる条件は下記になると思われます。- MySQL4.1以上(上記から)
- PHP5.2.1以上(試験から)
- PDO::setAttribute(PDO::ATTR_EMULATE_PREPARES, false); を呼んでいない
そして、下記のエラーになります。
Error Code: 1064queryでは複文は許可されても、Prepare statementでは許可されないようです。
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
複文を実行できる影響
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 件のコメント:
コメントを投稿