FlawOpen

flawopen.com/ファイルインクルード/PHP include()

ユーザー入力を使う PHP include() は安全ですか?

CWE-98: PHP Remote File Inclusionリファレンスページ
要点

いいえ。include() は選択したファイルを PHP として解析しますが、影響は到達できるファイルと内容によって異なります。wrapper を通じたソース開示や意図しないローカルファイルの読み込みが起こり得ます。攻撃者が PHP タグ付き内容を制御し、アプリがそのファイルを読み込む場合に限り、そのコードが実行されます。リモート URL の読み込みも PHP 設定に依存します。ユーザーが選ぶキーを固定許可リストに対応付け、リクエストからパスを組み立てないでください。

脆弱
<?php
// User-controlled path: unintended local file
include $_GET['page'] . '.php';

// Example selector: ../../private/sample
// Impact depends on the resolved file and
// whether it contains PHP code to interpret.

// Remote URL inclusion requires the relevant
// URL wrapper and allow_url_include setting.

// Wrappers can change which resource is read;
// php://filter is a read filter, not a path
// containment check.
修正済み
<?php
// Allowlist: input selects a KEY,
// never a path or wrapper.
$pages = [
    'home'    => __DIR__ . '/pages/home.php',
    'about'   => __DIR__ . '/pages/about.php',
    'contact' => __DIR__ . '/pages/contact.php',
];

$key = $_GET['page'] ?? 'home';

if (!is_string($key) || !array_key_exists($key, $pages)) {
    http_response_code(404);
    exit;
}

include $pages[$key];

ローカル読み込みが自動的なコード実行を意味しない理由

ローカル読み込みだけでコードが自動実行されるわけではありません。PHP は読み込んだファイルを解析し、有効な PHP タグ内の命令を呼び出し元のコンテキストで実行します。それ以外のバイト列は出力されることがあります。実行には、攻撃者が制御する PHP を含む到達可能なファイルなどが必要です。アップロードやログ書き込みの欠陥が、制御した PHP 内容を後で読み込まれるファイルに置く場合、攻撃チェーンになり得ます。

php://filter はストリームを開く際にフィルターを適用します。脆弱な読み込み処理では、適切なフィルターで実行せずにソース文字列を露出させる場合があります。読み取り可否は wrapper、権限、設定に依存します。

パスのサニタイズだけでは不十分な理由

../ の削除、固定拡張子の追加、ディレクトリの接頭辞追加だけでは、最終リソースが許可済みとは確認できません。エンコード、絶対パス、OS の規則、wrapper により解釈が変わります。リソース名をユーザーに制御させず、アプリが定義した固定キーだけを許可してください。

パス解決が必要なら realpath() で正規化し、Path Traversal ガイドのようにディレクトリ境界内か検証します。さらに対象を許可済みファイルに限定し、シンボリックリンクや検査と使用の競合を考慮してください。固定宛先の許可リストがより安全です。

よくある質問

allow_url_include=Off なら安全ですか?

この設定は include/require の URL wrapper を無効にします。PHP では既定でオフで、PHP 7.4 から非推奨です。リモート読み込みのリスクは下げますが、ローカルパストラバーサルや危険なローカル include は防げません。オフのままにし、ユーザーが対象を制御できないようにします。

ヌルバイトの手法はまだ使えますか?

PHP 5.3.4 でパス処理のヌルバイト終端は修正されました。古い手法が使えないことを防御と見なさないでください。信頼できない include 先は依然危険です。

require、include_once、readfile はどうですか?

require と _once も対象を読み込み解析します。失敗時や重複時の挙動は異なりますが、信頼できない対象を安全にはしません。readfile() と file_get_contents() は PHP として解析せずデータを返すため、危険なパスは traversal や情報漏えいにつながります。

参考資料

関連項目 パストラバーサル(CWE-22)の仕組みと各言語での防ぎ方 — flawopen.com Python os.path.join() のパストラバーサル対策 Zip Slip 脆弱性とは? Node.js path.join() では Path Traversal を防げない