PHP Note
category: セキュリティ クロスサイトスクリプティング(XSS) register_globals = On の恐怖

汚染された GET・POST・COOKIE

created: 2003-11-25 | modified: 2006-08-15

HTTP の仕組みはいたって簡単で、クライアントのリクエストを受けてサーバがレスポンス返す、サーバはレスポンスを返し終えた時点でひと仕事完了です。よくサーバ~クライアント間で何度かやり取りしながら一連のサービスを提供したりしていますが、ひとつひとつ独立した「リクエストとレスポンス」を続けて行うことで一連のサービスを構成しているに過ぎません。

ここで覚えておかなくてはならないのは、一旦出力したもの(HTMLなど)をサーバ側で管理することは一切出来ないと言うことです。自ら PHP で出力した HTML も例外ではなく、クライアントから想定外の値が返ってくる来る可能性があります。と言うより、常に何が来るかわからないことを前提にプログラミングすることが必要です。

PHP ではクライアントから送られてくる値を $_GET, $_POST, $_COOKIE で得ることが出来ますが、これらが想定外の値だった場合に起こる危険を考えてみます。


フォームの改ざん

複数のスクリプト間で値を共有する為、フォームの <input type="hidden"> で持ちまわすことがあります。例えば買い物カゴのシステムにおいて以下の商品選択フォームがあるとします。

<form action="./kago.php" method="post">
<input type="hidden" name="item" value="MG0324">
<input type="hidden" name="price" value="1200">
商品名:ペア・マグカップ(MG0324)<br>
販売価格:1,200円<br>
カラー:<select name="color">
<option value="オレンジ" selected>オレンジ
<option value="ブルー">ブルー
<option value="レッド">レッド
</select><br>
注文数:<select name="units">
<option value="1">1
<option value="2">2
<option value="3">3
<option value="4">4
<option value="5">5
</select><br>
<input type="submit" value="買い物カゴに入れる">
</form>

商品コードを示す item=MG0324 と 価格を示す price=1200 がフォームに埋め込まれてますが、このフォームはクライアントのコンピュータに一旦保存されますので、ユーザが <input type="hidden" name="price" value="10"> と書き換えるのはとても簡単なことです。書き換えたフォームを送信すれば 10円のマグカップの注文として処理することになります。 このようなフォームの改ざんは、<input type="hidden"> に限ったことではなく、セレクトボックスでも、チェックボックスでも、ラジオボタンでもありえることです。


改ざんされて困るデータはクライアントに渡さない

この買い物カゴの場合なら、「商品コード・数量・カラー」だけを受け取り、kago.php で処理の際データベースなどにある「価格」を参照するといった処理にする必要があります。

「フォームの改ざん」と銘打ってますが、ユーザが書いたフォームからリクエストを送信することも可能ですし、任意の値を送信できる HTTPクライアントはいくらでも存在します。

クライアントから来る値は間違っても信用してはいけません。「なにが来るかわからない」を前提に「どんな値が来ても問題ないロジックを組む」必要があります。



SQLインジェクション

クライアントから受け取ったデータを直接 SQL文に埋め込んでいると、想定していない SQLが実行され、データの削除・改ざん・情報が盗まれるなどの可能性があります。

下記の例では、クライアントから受けとった書籍ID $_POST['id'] をそのままSQL文の一部にしています。

<?php

// ユーザーが選択した書籍IDに一致する
// 書籍データを表示する処理。(PostgreSQL の例)

// 接続処理(省略)

// SQL文作成・問い合わせ
$sql = "select * from book where id='". $_POST['id']. "';";
$result = pg_query($conn, $sql);

// 以下、レコードの獲得・処理(省略)

?>

設計者は $_POST['id'] が英数字である事を想定しているのですが、この値が英数字である保証はどこにもありません。例えセレクトボックスを使いユーザに選択してもらう値だったとしても、$_POST['id'] がそのフォームから送信されたとは限らないからです。

この $_POST['id'] に、"'; delete from book where id like '%" という値が入力された場合 SQL文はこうなります。

select * from book where bnumber=''; delete from book where id like '%';

これが実行されれば、テーブル book の 全レコードが削除されます。

クライアントから来たデータを直接SQL文に埋め込んだ場合に、必ずしも任意のSQLが実行されるわけではありません。magic_quotes_gpc = On の環境なら特殊な文字がエスケープされますし、ユーザにコマンドの実行権がなければこれも実行されません。また mysql_query関数は複数の文を許可していないのでこれも実行されません。いくつかの条件が整った場合のみ起こります。

hyg様はじめ数名の方よりご指摘頂きました。誠にありがとうございました。


想定内の値か必ず確認する

クライアントから来る値を直接 SQL文に埋め込む事は、「好きなようにデータベースをお使い下さい」と言ってるようなものです。この例ならば、あらかじめ正規表現関数などを使い $_POST['id'] が英数字のみで構成されているかどうか確認が必要です。

// 0 - 9, a - z, A - Z  以外の文字が
// 含まれていれば強制終了
if (preg_match('/[^0-9a-z]/i', $_POST['id'])) {
    echo '書籍IDが不正です。';
    exit();
}

データの妥当性を検査する処理を「データバリデーション」とか単に「バリデーション」と呼びます。 クライアントが送信してくる $_GET, $_POST, $_COOKIE 全てに対してバリデーションは必須の処理です。バリデーションしなかったために、エラーが出る・処理が中断する・予期せぬコードが実行されるなど、あらかじめ想定して無いだけにその影響は計り知れません。SQLインジェクションはこのひとつにすぎません。

処理のはじめにバリデーションしていれば、これ以降の処理は不安をかかえながらプログラミングする必要はなくなります。



category: セキュリティ クロスサイトスクリプティング(XSS) register_globals = On の恐怖
PHP Note
ページの一番上へ
Googleグックマークに登録 Yahooグックマークに登録 livedoorクリップに登録 @niftyクリップに登録 はてなブックマークに登録 deliciousに登録 Buzzurlに登録 FC2ブックマークに登録
最近更新したNote
よく読まれている記事
Yahoo Search

最近更新された掲示板トピックス
PHPマニュアル
今日のブックマーク
PHPマニュアル関数検索
関数名を入力し検索ボタンをクリック↑