なぜPHPアプリにセキュリティホールが多いのか?

第14回 減らないSQLインジェクション脆弱性

この記事を読むのに必要な時間:およそ 3 分

チェックポイント:すべてのパラメータを文字列として取り扱っているか?

前のチェックポイントですべてのパラメータがエスケープ処理されているか確認しましたが,すべてのパラメータが文字列として処理されていないと意味がありません。

例えば,uid,gidが整数であるテーブルのエスケープ処理をする場合に

$sql = 'SELECT * FROM user WHERE uid = '.pg_escape_string($_POST['uid']). ' AND gid = '. pg_escape_string($_POST['gid']). ';';

としても意味がありません。$_POST['uid'] に

1; DELETE FROM user; --

などが入っていればSQLインジェクションが可能です。すべての入力を文字列として取り扱えば,このような問題は発生しません。

$sql = "SELECT * FROM user WHERE uid = '".pg_escape_string($_POST['uid']). "' AND gid = '". pg_escape_string($_POST['gid']). "';";

ところで,uid,gidが整数型であるなら整数型にキャストすればよいのでは? と考えられた方もいると思います。しかし,キャストする方法は2つの理由でベストプラクティスとは呼べません。

キャストした場合,不正な攻撃目的の入力が行われてもクエリエラーが発生しない可能性があります。攻撃目的の入力は検出できるほうが好ましく,文字列として扱えばクエリエラーで簡単に攻撃用の文字列が検出できます。またキャストを行うと,32ビットアーキテクチャのコンピュータでは符号付き32ビット整数となり,データベースが一般的にIDに利用する符号付き64ビット整数に比べ著しく狭い範囲の整数でしか正常に動作しません。

チェックポイント:データベースが利用する文字エンコーディングを変更する場合,クエリでなくデータベースAPI関数を利用しているか?

データベース文字エンコーディングをSJISに変更した場合に最も影響が大きいですが,ほかの文字エンコーディングでもセキュリティ上の問題の一因となることもあります。最も分かりやすい,MySQLを例にします。データベース文字エンコーディングはバイナリを利用しているとします。

mysql_query("SET NAMES 'sjis';");
$result = mysql_query("SELECT * FROM user WHERE first_name = '".mysql_real_escape_string($_POST['first_name'])."' AND '".mysql_real_escape_string($_POST['last_name']) "';");

$_POST['first_name']に「表」などの\を含む文字が指定されると

first_name = '表\' 

とエスケープ処理され,サーバ側ではSJISとして処理されます。この結果,2つ目のパラメータ($_POST['last_name'])でSQLインジェクションが可能になります。

チェックポイント:クエリを生成する場合に「すべて」のパラメータを文字列として扱いエスケープ処理し,クエリを生成しているか?

実際時々見かける間違いですが,「プリペアードクエリを利用すればSQLインジェクションできない」とするSQLインジェクション対策の原理を理解せずにプリペアードクエリを利用しているプログラマが,プリペアードクエリの生成にパラメータを使ってしまっているケースがあります。

$prepared = "SELECT ". $_GET['field_name'] . " FROM " . $_GET['table_name']. " WHERE user = $1 AND group = $2";

このようなプリペアードクエリの作成ではまったくSQLインジェクション対策になりません。

チェックポイント:テーブル名,フィールド名の指定はホワイトリスト方式か?

テーブル名やフィールド名をパラメータで設定している場合,利用可能な文字を限定する方式も可能ですが,できればホワイトリスト方式でチェックします。

実際の確認手順

1. ソースコード中にSQLインジェクションが可能となる可能性があるコードが無いか?

PHPファイルすべてからSET NAMES(MySQL),SET CLIENT ENCODING(PostgreSQL)などの文字列を検索します。

LinuxやMac OSなどでは以下のようなコマンドで検索できます。

find . -name "*.php" | xargs grep -in "SET NAMES"

2. エスケープ関数が正しく使われているか?

PHPファイルすべてからmysql_real_escape_string, “mysql_escape_string, “pg_escape_string, “pg_escape_bytea, “addslashes関数の利用箇所を探し,不適切な使用方法がないか確認します。mysql_real_escape_string,pg_escape_stringが利用され,データベース接続リソースも指定されていることを確認します。

LinuxやMacOSなどでは以下のようなコマンドで検索できます。

find . -name "*.php" | xargs grep -in "mysql_real_escape_string"

3. クエリ実行箇所を確認し,すべてのパラメータが文字列として扱われエスケープされているか?

PHPファイルすべてからmysql_query, “mysql_unbuffered_query, “mysql_db_query, “pg_query, “pg_send_query関数の利用箇所を探し,クエリ文生成の際にすべてのパラメータが文字列として扱われ適切にエスケープされていることを確認します。テーブル名やフィールド名をパラメータで設定している場合,ホワイトリスト方式でチェックされていることを確認します。

4. プリペアードクエリが利用されている箇所を確認し,プリペアード文にパラメータが含まれないか?

PHPファイルすべてからpg_prepare, “pg_query_params, “pg_send_prepare, “pg_send_query_params関数が利用されている箇所を探し,プリペアード文の生成にパラメータが利用されていないか確認します。テーブル名やフィールド名をパラメータで設定している場合,ホワイトリスト方式でチェックされていることを確認します。

著者プロフィール

大垣靖男(おおがきやすお)

University of Denver卒。同校にてコンピュータサイエンスとビジネスを学ぶ。株式会社シーエーシーを経て,エレクトロニック・サービス・イニシアチブ有限会社を設立。
オープンソース製品は比較的古くから利用し,Linuxは0.9xのころから利用している。オープンソースシステム開発への参加はエレクトロニック・サービス・イニシアチブ設立後から。PHPプロジェクトでは,PostgreSQLモジュールのメンテナンスを担当している。

URLhttp://blog.ohgaki.net/

著書

コメント

  • Re:

    もう1点

    > キャストした場合,不正な攻撃目的の入力が行われてもクエリエラーが発生しない可能性があります。攻撃目的の入力は検出できるほうが好ましく,文字列として扱えばクエリエラーで簡単に攻撃用の文字列が検出できます。

    入力として整数が期待されるのであれば整数(として扱える文字列)であるかのチェックを行い、問題があればクエリを実行せずにエラーとして処理するべきです。

    型キャストするのはベストプラクティスではないとのことですが、整数が期待されるパラメータに整数以外の文字列が入力された事をクエリの失敗で検出するというのはそれとは比較にならないほど致命的な誤りです。

    クエリーの失敗はあくまで結果であり、失敗することを何かの目的として期待すべきではありません。

    Commented : #2  鈴木 (2008/09/18, 14:12)

  • Re:

    PostgreSQLではインターフェースライブラリのlibpqを通してSET client_encoding TOを呼ぶのとlibpqのPQsetClientEncodingを呼ぶのに変わりはありません。

    SET client_encoding TOを使わないのはなく、SET client_encoding TOを含む何らかの方法で適切に文字コードを設定する事が重要です。

    本記事はSET client_encoding TOに問題があるかのような誤った理解に繋がるように思えます。

    Commented : #1  鈴木 (2008/09/18, 10:18)

コメントの記入