想定読者: WordPress等PHPサイトを運用中で、2026年10月のPHP2Shell騒動で「自分のサーバー大丈夫か?」と不安になっている運用者
所要時間: 記事を読む ~15分 / 実装対応 ~半日
事件の前提:PHP2Shellとは何か(2026年10月21日公表)
| 項目 | 内容 |
|---|---|
| 名称 | PHP2Shell(CVE-2026-109618 / CVE-2026-109619) |
| 内容 | OOM時のメモリ破損/リクエスト間の解放済みメモリ参照(use-after-free)による認証不要RCE |
| 深刻度 | CVSS v4.0 9.2(Critical)(watchTowr評価は10.0) |
| 影響範囲 | PHP 5.4〜修正版以前の全系統。Webサイトの約7割がPHP実行環境(WordPress/Drupal/Joomla含む) |
| 修正版 | PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12(10月21日公開)。8.1以前は公式パッチなし=メジャーアップグレード必須 |
| 発見者 | Calif+OpenAI「Patch the Planet」計画、Wiz。AIが脆弱性発見に関与した事件として世界中で報道 |
| 日本 | 高市総理がXで緊急要請、首相HPも一時停止。ニコニコ・pixiv・GBF等が緊急メンテナンス。IPAが10/22付注意喚起(alert20261022) |
ポイント: WordPress本体やプラグインを更新してもPHP本体の脆弱性は直らない。サーバー側の実行PHPバージョンを更新するのが唯一の安全策だ(開発元も完全な緩和策はないと明言)。
はじめに:Xを流していたら、高市首相の記事が目に入った
2026年10月21日、X(旧Twitter)を流していたら高市首相関連の記事が目に入った。内容はPHPの脆弱性に関するものだった—— プログラミング言語PHPに認証不要で遠隔コード実行(RCE)を許す重大な脆弱性「PHP2Shell」(CVE-2026-109618 / CVE-2026-109619、CVSS v4.0 深刻度9.2)が公表され、総理自らが「大至急のお願いです」と修正パッチ適用かサービス一時停止を呼びかけた日だ。首相自身もホームページを一時的に停止する異例の事態だった。
「またそんな話か」と思いつつ読み進めていたら、話にならなかった——まさか自分のBLOGも同じ該当範疇だとは思わなかった。WordPress=その上で動くPHP本体が脆弱性そのものに含まれていた。自分が何年も「とりあえず動く状態」で置き去りにしていた環境が、記事の中にそのまま出てきた。
慌ててPHPのバージョンを確認し、アップデートと併せて対策を施した。半日程度で済んだが、「知ってからやる」と「知らずにやらない」では雲泥の差だったと身をもって思い知ったわけだ。実際、ニコニコ動画・pixiv・グランブルーファンタジー等の大手サービスが緊急メンテナンスで一時停止し、Cloudflareは修正版公開前後に数百万件規模の悪用試行を観測している。「ウチくらいは大丈夫」は通用しない規模の事件だった。
この体験をきっかけに、自分のブログ・自分のサーバーに同じ穴がないかを実務レベルで棚卸しした内容を、本記事にまとめた。読者は自分と同じ目に遭わないために、最低でも今夜10分だけ時間を割ってほしい。
対策10原則(重要度順)
1. PHPとライブラリのバージョンを常に最新化する(今回なら8.2.35 / 8.3.36 / 8.4.27 / 8.5.12以上)
- PHP 8.1以前は PHP2Shell の公式パッチが存在しない。EOL系列を使っているなら「パッチ待ち」ではなくメジャーアップグレードが唯一の解
- WordPress運用者は管理画面「ツール → サイトヘルス → サーバー」で実行中PHPバージョンを直接確認。レンタルサーバーの自動適用に頼り切らない(ロリポップ系は自操作が必要なケースあり)
composer auditで依存パッケージの既知脆弱性を CI で毎日検査する
composer audit --format=plain
2. SQLインジェクション → PDO準備済みステートメント以外禁止
文字列連結でのSQL組み立ては全排除。バインド変数は「型」まで指定する。
// ❌ 危険
$sql = "SELECT * FROM users WHERE name = '$name'";
// ✅ 安全
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name');
$stmt->execute(['name' => $name]);
PDO::ATTR_EMULATE_PREPARES => falseを必ず設定(擬似準備済みだとインジェクション余地が残る)- MySQL の場合は
SET NAMES ... NO_BACKSLASH_ESCAPES問題も無効化されるよう DSN で charset 指定
3. XSS → エスケープは「文脈」で選ぶ
- HTML本文:
htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') - 属性値・JS・URL文脈では
rawurlencode()や JSON エスケープを使い分ける - テンプレートの自動エスケープ(Blade/Twig/Smarty auto_escape)をデフォルトにする
// ✅ 出力は常にこのヘルパー経由にする
function e(string $v): string {
return htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
4. CSRF → トークン + SameSite クッキー
- 全POSTフォームにワンタイムCSRFトークン(
random_bytes(32)→ session保存 → 検証はhash_equals()) - セッションクッキーは
SameSite=Lax(ログイン後操作はStrict)、Secure、HttpOnly
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
http_response_code(403); exit;
}
5. パスワード → password_hash() / password_verify() のみ
- MD5/SHA1 は論外。
password_hash($pw, PASSWORD_ARGON2ID)(使えない環境では bcrypt) password_needs_rehash()でコスト上昇にも自動追従- 最低長は12字、ログイン回数は速率制限(後述)
$hash = password_hash($password, PASSWORD_ARGON2ID);
if (password_verify($password, $hash)) { /* login */ }
6. セッション設定を php.ini / ini_set で固める
session_start([
'cookie_lifetime' => 0,
'cookie_secure' => true,
'cookie_httponly' => true,
'cookie_samesite' => 'Lax',
'sid_length' => 48, // 48〜128推奨(デフォルト32)
'sid_bits_of_entropy' => 128, // 乱数のエントロピー量。ログイン時は regenerate で切替
]);
- ID固定化攻撃対策: ログイン成功時に必ず
session_regenerate_id(true)
7. アップロードファイルの受け入れ基準をホワイトリストで
- MIME(
$_FILES['type']は信用しない)はfinfoで判定、拡張子は許可リストのみ - ファイル名はサーバー側で生成し格納ディレクトリは Webルートの外
- PHP実行禁止: アップロード領域は
php_admin_flag engine offもしくは.htaccess/nginx のlocationで php 処理を遮断
8. エラー設定: 本番で display_errors = Off
log_errors = On+ ログファイルへ。画面にスタックトレースとDB接続文字列を出さないerror_reporting = E_ALL(ログ側は全記録)
; php.ini (本番)
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
expose_php = Off
9. リモートコード実行の元になる「危険関数」を叩く
eval()/assert()の引数に文字列(=ユーザー入力)は絶対禁止include $userInput→ LFI/RFI。パスは白名单 +basename()で解決するexec/shell_exec/passthru/system/proc_open/popen/unserializeは使わない設計に。やむを得ないなら引数をescapeshellarg()
10. ヘッダとCSPで防御の最終線を張る
Content-Security-Policy: default-src 'self'; script-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=31536000; includeSubDomains
Permissions-Policy: camera=(), microphone=()
今回の私のBLOG対策は「ローカルAI」で実施した
今回の対策、実は手作業ではなく自宅のローカルAIエージェントに実行させた。皮肉なことに、PHP2ShellはOpenAIの「Patch the Planet」計画を含むAIによる脆弱性発見事件だった——AIが見つけた穴を、AIに塞がせた構図になる。
実行させた手順
- 影響判定:
php -vとWordPressサイトヘルスを取得させ、稼働PHPが影響範囲(修正版未満/EOL系列)かを即判定 - フルバックアップ: 作業前にファイル+DBのエクスポートを自動実行(ロールバック前提の必須工程)
- バージョン更新: レンタルサーバー側のパッチ適用・系列切替の手順を引かせ、適用後に
php -vで実稼働バージョンの再検証まで徹底 - 動作検証+侵害兆候チェック: ログイン・記事表示・フォーム送信・REST APIの疎通確認と、不審なファイル作成・想定外の管理者アカウント・PHPワーカー異常終了のログ点検
- 恒久対策: 上記10原則(エラー表示オフ、CSRF、エスケープ統一)も併せて適用
ローカルAIにやらせたメリット
- サーバー設定・認証情報を外部クラウドに出さない。機微な環境情報をローカル完結で処理できるのが最大の利点
- 事件当日の夜、情報収集→判定→実行→検証まで一気通貫で数時間。人間だけでやれば調査記事を追うだけでもたつく工程
- 常駐させれば「IPA注意喚起→自分の環境該当性判定」の定期巡回にもなる
注意点(人間のレビューは必須)
- AIが出すシェルコマンド・設定変更はそのまま流さず必ず中身を検証してから実行。EOL系列の判断ミスや、自動適用済みサーバーで二重作業するリスクは人間側で潰す
- 破壊的操作をさせるならバックアップ取得を工程として強制する(AIに任せても「バックアップ取れていない」状態が起きる)
実装チェックリスト(コピペ用)
| # | 項目 | 確認方法 |
|---|---|---|
| 1 | PHP 8.3以上 / composer audit通過 | php -v, composer audit |
| 2 | SQLは全てPDO準備済み | grep -R "mysql_query\|->query(\"" app/ |
| 3 | 全出力がエスケープヘルパー経由 | code review + CSP report-only |
| 4 | POST全フォームにCSRFトークン | 手動 or e2eテスト |
| 5 | password_hash使用、平文/MD5ゼロ | grep -R "md5\|sha1" app/ |
| 6 | セッションクッキー Secure/HttpOnly/SameSite | ブラウザDevTools |
| 7 | アップロードはWebルート外+実行禁止 | nginx conf確認 |
| 8 | display_errors=Off | php -i \| grep display_errors |
| 9 | eval/unserialize/exec引数なし | セマンティックgrep |
| 10 | CSP/HSTSヘッダ存在 | SecurityHeaders.com A判定 |
自動化: CIに載せるならこの3点
# .github/workflows/security.yml 例
- name: Static analysis (Psalm/PHPStan taint mode)
run: vendor/bin/phpstan analyse --level=max
- name: Dependency audit
run: composer audit
- name: Secret scan
uses: gitleaks/gitleaks-action@v2
- Semgrep の PHP用ルール(
php.lang.security.*)をCIに常設すると、上記9割の危険パターンはPR段階で止まる - OWASP ZAP をステージ環境へ週次スキャン
よくある「効く気がする」誤対策
| 誤解 | 実態 |
|---|---|
addslashes() でSQL安全 |
バイトコード系列では無防備。PDOバインド一択 |
| strip_tags() でXSS防止 | イベントハンドラ・属性文脈を素通り。文脈別エスケープへ |
| WAFがあればOK | WAFは補助輪。根本修正の代替ではない |
| .php拡張子禁止でアップロード安全 | バイト列・.htaccess上書き・パース順序攻撃が残る。格納場所分離が本命 |
まとめ
- バージョン更新が最大の対策。PHP2Shellで実証された通り、EOL系列(8.1以前)は「待ってもパッチが来ない」
- 入出力の原則は「入力検証(ホワイトリスト)→ 文脈別エスケープ → 安全API」の3層
- 人間が守るルールではなく CIで機械的に止める仕組みを作る
- そして、AIが見つけ、AIが塞げる時代。ローカルAIを自サーバー守衛に使う選択肢はもう実用レベルにある
次の一手(今夜10分): php -v で実行バージョンを確認し、修正版(8.2.35 / 8.3.36 / 8.4.27 / 8.5.12)未満なら即更新。レンタルサーバー利用中は事業者の適用完了告知と実際の稼働バージョンの両方を見る。それでも残る不安なら、ローカルAIに棚卸しさせるのが手っ取り早い。
出典(2026年10月22日時点で確認済み)
- IPA「PHPの脆弱性対策について (CVE-2026-109618、CVE-2026-109619:PHP2shell)」2026年10月22日公開 — https://www.ipa.go.jp/security/security-alert/2026/alert20261022.html
- PHP公式アドバイザリ「Critical remote code execution vulnerabilities, update now」(2026-10-21) — https://www.php.net/index.php#2026-10-21-5
- GitHub Security Advisory: GHSA-w8hw-w9fx-j79w(OOM corruption)/ GHSA-6wmf-4rv5-p3rp(stale global state)
- Wikipedia「PHP2Shell」(発見経緯・各国対応・高市総理の呼びかけ) — https://ja.wikipedia.org/wiki/PHP2Shell
- 高市総理のXでの緊急要請報道: 日本経済新聞・朝日新聞・毎日新聞・ITmedia(2026年10月21〜22日)
- Cloudflare WAF自動展開・悪用試行観測: Cloudflare公式ブログ / Palo Alto Networks Unit 42 Threat Brief
