auditing-php-applications について
PHP のコード監査向けセキュリティスキルです。一般的な脆弱性監査手法を PHP 固有の危険箇所に適用し、バージョン依存の挙動や入力から危険な処理までの経路を検証します。
主な機能
- 01入力から危険な処理への経路を追跡
- 02PHP のデシリアライズとオブジェクト注入を確認
- 03ファイルインクルード、ラッパー、アップロードを調査
- 04SQL インジェクションと比較の回避を特定
- 05PHP バージョンに基づいて影響を検証
活用シーン
- 01PHP アプリやプラグインのソースを監査
- 02WordPress、Magento、Joomla、Laravel のコードを確認
- 03RCE、LFI、SQLi、オブジェクト注入の疑いを調査
- 04ユーザー入力の unserialize や == の悪用可能性を評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill auditing-php-applicationsインストール後、Claude Code で/auditing-php-applicationsを入力して読み込まれたか確認します。
詳細
PHP アプリケーションの監査
これは、auditing-code-for-vulnerabilities の上に構築された PHP 固有のレイヤーです。
そのスキルの手法、つまりコンテキスト → 攻撃対象領域 → バグクラスの探索 → バリアント分析 → 4 つの質問による検証ゲートを取り入れ、以下の PHP シンクカタログに適用してください。このスキルが存在するのは、言語に依存しない手法だけではカバーできない PHP 固有の落とし穴があるためです。ファイル読み込みをリモートコード実行に変える機能や、比較ルールによって == が認証バイパスを引き起こすケースがあります。
このコレクションに言語別の監査スキルがこれ以外にないのは、意図的なものです。 PHP が例外として扱われるのは、実際に重大なバグが発生しているレガシーウェブや WordPress/Magento の領域で圧倒的なシェアを占めていること、そしてその動的機能が、レビュー担当者に特別な注意を促す必要がある形で問題を引き起こすためです。
使用する場面
- PHP のコードベースやプラグインに、悪用可能な脆弱性がないかレビューするとき
- WordPress、Magento、Joomla、Laravel アプリケーションのソースを監査するとき
- PHP における RCE、LFI、SQLi、オブジェクトインジェクションの疑いがある経路を追跡するとき
- ユーザー入力に対する
unserialize、include、==が悪用可能かどうかを判断するとき
使用しない場面
- 一般的な監査手法、または PHP 以外の言語を対象とする場合 —
auditing-code-for-vulnerabilitiesを使用してください。このスキルはその手法を前提としています - 脆弱性ではなく、仕込まれたウェブシェルやバックドアを見つける場合 —
hunting-web-backdoorsを使用してください - シンクを見つけた後、デシリアライズのエクスプロイトチェーンを構築する場合 —
exploiting-deserializationを使用してください - 稼働中のアプリをブラックボックステストする場合 —
testing-web-applicationsを使用してください - 変更差分のみをレビューする場合 —
reviewing-code-changesを使用してください
バグクラス別のシンク
ユーザー入力(スーパーグローバル、ヘッダー、アップロードされたファイル名、かつてユーザー入力だった DB 値)から、以下の各シンクに至る経路を追跡します。ソースから到達して初めて、シンクはバグになります。
リモートコード実行
- 直接的な eval シンク:
eval、assert(文字列引数、8.0 より前)、/eを指定したpreg_replace(7.0 より前)、create_function(8.0 より前)、eオプションを指定したmb_ereg_replace/mb_eregi_replace。 - コマンド実行:
system、exec、passthru、shell_exec、バッククォート、proc_open、popen、pcntl_exec。escapeshellargとescapeshellcmdの誤用に注意してください。escapeshellcmdは引数インジェクションを防止しません。 - 動的ディスパッチ: 入力に由来する
$funcを使った$func()、call_user_func/call_user_func_array、ユーザーの影響を受けるコールバックを使ったarray_map/usort/preg_replace_callback。
オブジェクトインジェクション(PHP を特徴づけるバグ)
- ユーザーが制御できるデータに対する
unserialize()は、すべてオブジェクトインジェクションです。悪用には POP チェーンが使われます。これは、オブジェクトが生成されたときに危険な処理を行う__wakeup、__destruct、__toString、__callマジックメソッドを持つ既存クラスを利用します。脆弱なファイル自体には危険なコードが一切含まれていない場合もあります。ガジェットはフレームワークや依存関係に存在することがあります。 phar://は偽装された unserialize ですが、バージョンによって異なります。 PHP 8.0 より前では、phar://パスに対するファイル操作(file_exists、fopen、include、getimagesizeなど)を行うと、Phar のメタデータが自動的にデシリアライズされます。そのため、ファイル関数がアップロードされた.phar(または Phar メタデータを含む任意のファイル)を参照するパストラバーサルや LFI は、unserializeの呼び出しが見当たらなくてもオブジェクトインジェクションになります。PHP 8.0 では、この自動デシリアライズが削除されました。現在デシリアライズが行われるのは、明示的なPhar::getMetadata()のみです。したがって、このバグは PHP 7.x が広く使われている環境では重大ですが、コードがgetMetadata()自体を呼び出さない限り、8.x では問題になりません。該当するかどうかを判断する前に、実行時のバージョンを確認してください。- 修正では
json_decodeを優先してください。明らかなガジェットが見当たらなくても、信頼できないデータに対するunserializeはすべて指摘してください。ガジェットは次の依存関係更新で追加される可能性があります。
LFI / RFI とストリームラッパー
- ユーザー入力を使った
include、require、include_once、require_once。 - あらゆるものを変えるラッパー:
php://filter/...(ソースコードの読み取り、およびconvert.iconv/圧縮フィルターを経由する既知の RCE チェーン)、php://input(RFI のようにリクエストボディをインクルード)、data://(インラインペイロード)、phar://(オブジェクトインジェクション)、expect://、zip://。allow_url_includeが有効だと、LFI がリモート RFI に変わります。 - ファイル読み取りシンク(
file_get_contents、readfile、fopen、highlight_file)でトラバーサルが可能な場合、ソースコードや秘密情報が漏えいします。
SQL インジェクション
- 文字列連結した入力を使うレガシーな
mysql_query/mysqli_query。 $wpdb->prepare()を使わない$wpdb->query("... $var ...")—->prepareが使われていても、プレースホルダーの外側で補間していれば、依然としてインジェクションが可能です。- バインドパラメーターの代わりに文字列構築を使う PDO/mysqli。バインドできないテーブル名/カラム名にプレースホルダーを使うのは、よくある実際の脆弱性です。
型のジャグリングとマジックハッシュによる認証バイパス
==と!=は緩やかな比較です。 安全な形式はstrcmp/hash_equals/===です。典型的なバイパスは、if (md5($input) == $stored)において、2 つの入力のハッシュがどちらも0e[0-9]+という「マジックハッシュ」になり、等しいと判定されるケースです。数値文字列同士は数値として比較されるためです(0e123 == 0e456→0.0 == 0.0)。- バージョンが重要です。ここには落とし穴があります。 PHP 8.0 では数値と文字列の比較ルールが変わりました。
0 == "foo"は PHP 7 ではtrueでしたが、PHP 8 ではfalseです。そのため、PHP 7 で機能したin_array(0, $userStrings)や0 == $tokenのバイパスは、PHP 8 では失敗します。しかし、0eのマジックハッシュによるバイパスは PHP 8 でも有効です。 両辺が数値文字列であり、引き続き数値として比較されるためです。型のジャグリングに関するバグの有無を判断する前に、対象の PHP バージョンを確認してください。 - PHP 7 では、配列を渡すと
strcmp($_GET['x'], $secret)はNULL(緩やかな比較では== 0)を返し、認証バイパスにつながりました。PHP 8 ではTypeErrorが発生します。同じコードでも、バージョンによって判定が異なります。
スーパーグローバルの信頼と可変変数
extract($_GET/$_POST/$_REQUEST)を使うと、リクエストから任意のローカル変数を設定できます — 認証フラグや設定など、まだ初期化されていないあらゆるものを上書きできます。$$var/${$key}という可変変数、結果配列を指定しないparse_str(スコープに書き込む)、import_request_variables(5.4 で削除) — いずれもregister_globalsを思わせる変数インジェクションです。
SSRF とファイルアップロード
- ユーザーが指定した URL に対する
file_get_contents/curl/fopenは SSRF です — 悪用方法の検討はexploiting-ssrfを経由してください。 - アップロード:保存先がウェブルートの外であること、拡張子が攻撃者によって選ばれないこと(2 重拡張子、
.phtml、.php5、古い PHP のヌルバイト)、およびサーバーがその種類のファイルを実行しないことを確認してください。MIME が検証済みでも、攻撃者が拡張子を制御でき、ウェブからアクセスできるディレクトリに保存される場合は、RCE につながります。
エコシステムのコンテキスト
- WordPress: nonce はCSRF対策であり、認可ではありません — 有効な nonce があっても、ユーザーに操作権限があるとは限りません。不足している
current_user_can()のチェックを探してください。補間には$wpdb->prepareが必須です。unfiltered_html、edit_*権限、未認証の AJAX/REST(wp_ajax_nopriv_*、permission_callback => '__return_true')が、よくある脆弱性です。 - Magento:
unserializeの使用歴が多く、レイアウト XML インジェクションがあり、既知の CVE が到達可能かどうかは管理パスとパッチレベルによって変わります。 - Laravel: マスアサインメント(
$fillable/$guarded)、Blade の{!! !!}(エスケープなし)と{{ }}(エスケープあり)の違い、そして漏えいしたAPP_KEYによって可能になる Cookie/decryptのオブジェクトインジェクションに注意してください。
報告前に検証する
auditing-code-for-vulnerabilities の基準を適用してください。ソースからシンクまで追跡し、その経路に到達できること(認証、ルーティング、そして特に型のジャグリングや削除済み関数のバグに関わるPHP バージョン)を確認し、影響を断定するのではなく実証してください。到達可能なガジェットがない unserialize や、PHP 8 での /e 正規表現は、重大な脆弱性ではなくセキュリティ強化に関する注意事項です — どちらに当たるかを明記してください。
却下すべき合理化
- 「
unserializeがないので、オブジェクトインジェクションはありません。」phar://は通常のファイル関数を通じてunserializeに到達します。ユーザーがパスに影響を与えられるすべてのファイル操作を確認してください。 - 「
==を使っていますが、ハッシュのチェックなら問題ありません。」 ハッシュに対する==はマジックハッシュによるバイパスを招きます。安全なのはhash_equals/===だけで、このバイパスは PHP 8 でも通用します。 - 「比較のバグが機能することは確認しました。」 どの PHP バージョンでしょうか?
0 == "x"とstrcmp(array)の判定結果は、PHP 8.0 で変わりました。バージョンを明記してください。 - 「
prepare()が呼び出されているので、クエリは安全です。」 プレースホルダー内の値に限って安全です。補間されたテーブル名やカラム名、プレースホルダーの周囲に挿入されたテキストには、依然としてインジェクションの危険があります。 - 「ノンスチェックに通るので、認可されています。」 WordPress のノンスは CSRF トークンです。認可は別途行われるため、
current_user_can()を探してください。 - 「アップロード時に MIME タイプを検証しています。」 MIME は偽装可能であり、攻撃者が拡張子を制御でき、ディレクトリで PHP が実行されるなら関係ありません。
- 「これはフレームワークのクラスなので、ガジェットはここにはありません。」 POP チェーンのガジェットは、設計上、依存関係に存在します。脆弱なファイルに危険なコードがないのは通常のことであり、潔白の証明にはなりません。
外部ソースを読む
公開されている勧告、仕様、ベンダーのレポートは、Markdown として取得します。
curl -sL "https://defuddle.md/<url>" # scheme in the path is optional
これによりページの余分な要素が取り除かれます。文章中心のページならトークン数をおよそ 78%削減でき、要約ではなく全文が返されるため、grep で検索でき、該当する内容がないという結果も信頼できます。
用途は次の 3 つではありません。JSON や API のレスポンスは、可読性抽出によって構造化データが損なわれるため、加工せずに取得してください。認証が必要なページや JavaScript で描画されるページは、匿名で取得されるため、直接取得してください。また、攻撃者のインフラ(フィッシングリンク、C2、マルウェアのホスティング先)、クライアント所有のホスト、エンゲージメント URL は、絶対にこの方法で経由させないでください。リクエストがマシンから第三者へ送信され、稼働中の攻撃者のインフラの場合は、オペレーターにも気付かれてしまいます。
サイトによっては抽出ツールをブロックし、ページの代わりにエラーを返します。たとえば freedesktop.org からは {"error":"Failed to fetch: 418 I'm a teapot"} が返されます。これは取得が拒否されたことを示しているのであり、その情報が存在しないとソースが述べているわけではありません。そこから結論を出す前に、URL を直接再取得してください。
参考資料
auditing-code-for-vulnerabilities— このスキルの基盤となる一般的な手法とゲートhunting-web-backdoors— 脆弱性ではなく、仕込まれた悪意あるコードを探しますexploiting-deserialization—unserialize/phar://のシンクを見つけた後に POP チェーンを構築しますexploiting-ssrf— ユーザーが制御する URL のフェッチを悪用する方法testing-web-applications— 実行中のアプリケーションのブラックボックステスト