11. カスタマイズした画面にSQLインジェクション(検証例)
11
$state = $_POST['state'];
$city = $_POST['city'];
$town = $_POST['town'];
$db = mysql_connect(DB_SERVER, DB_USER, DB_PASSWORD);
mysql_select_db(DB_NAME, $db);
$r = mysql_query("SELECT * FROM mtb_zip WHERE state = '$state' AND city LIKE '$city%'
AND town LIKE '$town%' LIMIT 100");
続きはデモで
12. 日本で一番売れているPHP教科書のサンプル
12
// ここまでで、認証済みであるこの検査が済んでいる
$id = $_REQUEST['id'];
// 投稿を検査する
$sql = sprintf('SELECT * FROM posts WHERE id=%d',
mysql_real_escape_string($id));
$record = mysql_query($sql) or die(mysql_error());
$table = mysql_fetch_assoc($record);
if ($table[‘member_id’] == $_SESSION[‘id’]) { // 投稿者の確認
// 投稿した本人であれば、削除
mysql_query('DELETE FROM posts WHERE id=' . mysql_real_escape_string($id))
or die(mysql_error());
}
ここにSQLインジェクション
しかし、DELETE FROM
文なので表示はない
たにぐちまこと、「よく分かるPHPの教科書」 P272より引用
13. エスケープしているのになぜSQLインジェクション?
$id = ’88-1’; で説明。これはエスケープ後も 88-1
$sql = sprintf('SELECT * FROM posts WHERE id=%d',
mysql_real_escape_string($id));
↓ 生成されるSQL文は、88-1が%dの整数化で88になるので
SELECT * FROM posts WHERE id=88
mysql_query(‘DELETE FROM posts WHERE id=’ .
mysql_real_escape_string($id));
↓ 生成されるSQL文は、エスケープ後も 88-1 のままなので
DELETE FROM posts WHERE id=88-1
※チェックはid=88で、削除されるのはid=88-1 すなわち、87が削除される
13
14. エラーメッセージから情報窃取
• MySQLはエラーメッセージの中にリテラルの情報を含めな
いものが多いが、例外としてextractvalue関数がある
mysql> select extractvalue('<a><b>xss</b></a>', '/a/b');
+-------------------------------------------+
| extractvalue('<a><b>xss</b></a>', '/a/b') | ← 正常系
+-------------------------------------------+
| xss |
+-------------------------------------------+
mysql> SELECT extractvalue('<a><b>xss</b></a>', '/$this is a pen');
ERROR 1105 (HY000): XPATH syntax error: '$this is a pen'
• 副問い合わせにより、extravalueにわざとエラーのあるクエ
リを用いて情報窃取
mysql> SELECT extractvalue('',concat('/$',(SELECT cardnumber FROM
eccube_db.dtb_customer_card LIMIT 1 OFFSET 0) ));
General error: 1105 XPATH syntax error: '$1234567890123456'
14
15. SQL文のエラーが起こるか否かで情報を盗む
• SQLインジェクションにより実行されるSQL文の例
DELETE FROM posts WHERE id=18-(SELECT id FROM
members WHERE id LIKE char(49) ESCAPE IF(SUBSTR((SELECT
email FROM members LIMIT 1,1),1,1)>='M', 'a', 'ab')))
• WHERE句の中 18-(SELECT … WHERE …)
• 中のWHERE句は
LIKE 述語にESCAPE句がある
• ESCAPE句はIF関数により、membersの1行目の1文字目が
’M’以上の場合’a’、それ以外の場合’ab’
• SQL文の文法上、ESCAPE句は1文字以外だとエラー
• この結果を繰り返すことによって、対象文字列を絞り込む
→ブラインドSQLインジェクション
15
16. SQLインジェクション対策はプレースホルダで
• プレースホルダとは
SELECT * FROM books WHERE id=?
• 静的プレースホルダと動的プレースホルダ
– 静的: サーバー側で値をバインドする(エスケープは必要
ない)
– 動的: 呼び出し側で値をエスケープしてバインドする
• 接続時に文字エンコーディングを指定する
$db = new PDO('mysql:host=myhost;dbname=mydb;charset=utf8',
DBUSER, DBPASS);
• 列の型を意識する
16
17. サンプルコード(PHP5.5.21 以降)
// エミュレーションモードOFF = 静的プレースホルダ
$opt = array(PDO::ATTR_EMULATE_PREPARES => false,
// 「複文」を禁止する (PHP 5.5.21 / PHP 5.65 以降で有効)
PDO::MYSQL_ATTR_MULTI_STATEMENTS => false);
// 文字エンコーディング指定は PHP 5.3.7 以降
$db = new PDO('mysql:host=myhost;dbname=mydb;charset=utf8',
DBUSER, DBPASS, $opt);
// プレースホルダを使ってSQLを準備
$prepare = $db->prepare(
'SELECT * FROM example WHERE id = :id and language = :lang');
// 型を指定してbind (キャストはPDOのバグ対策)
$prepare->bindValue(':id', (int) $int, PDO::PARAM_INT);
$prepare->bindValue(':lang', $str, PDO::PARAM_STR);
$prepare->execute();
17