← 記事一覧へ

「今夜に対応して頂きたい大至急のお願い」――首相がPHPの修正を呼びかけた理由と、php2shellの影響

10月21日21時55分、高市早苗首相がウェブサイトの管理者に、その夜のうちにPHPを修正するか、サイトを止めるよう求めました。PHP2Shellとは何か、日本のどこまでに影響するか、今何をすべきかを整理します。

事案の発生時期: 2026年10月20〜22日(日本時間):事前告知、情報流出、修正版公開、初動出典確認日: 2026/10/22更新日: 2026/10/22

30秒でわかる要点

要点
10月21日21時55分、高市早苗首相はXで、ウェブサイトの管理者にPHPの使用を確認し、その夜のうちに修正を適用するよう求め、サイトの一時停止も有効策だとしました。PHPは重大な脆弱性2件(CVE-2026-109618とCVE-2026-109619、通称PHP2Shell)を8.5.12、8.4.27、8.3.36、8.2.35で修正しました。
対応するチーム
IT運用、SIEM、EDR、委託先リスク、法務・プライバシー
最初のアクション
PHPが動いているサーバー、レンタルサーバー上のサイト、PHPを組み込んだ製品をすべて洗い出してください。修正版かディストリビューションの修正済みパッケージに更新し、PHPのワーカーをすべて再起動します。今夜できなければ暫定設定を入れるかサイトを止め、10月20日までさかのぼって痕跡を調べてください。
不確実性・注意点
悪用の広がりは見方が分かれます。Cloudflareは攻撃通信を観測しましたが、NCSC-NLとHuntressは10月21日時点で悪用を確認しておらず、CISAも登録していません。PHP 8.1以前には公式の修正版が出ません。
ヘッダー画像。「今夜に対応して頂きたい大至急のお願い」。PHP2Shellについての首相の投稿は2026年10月21日21時55分、修正版公開の約1時間前。日本時間の4つの時刻:4時52分、PHPが重大な脆弱性2件を事前告知。20時ごろ、Cloudflareが初期の攻撃を報告。21時44分と21時55分、国家サイバー統括室と首相が、サイトの一時停止も有効策と発信。22時40分ごろ、PHP 8.5.12、8.4.27、8.3.36、8.2.35を公開。
実務への示唆

夜10時に政府から「深夜0時までにウェブサイトを修正するか、止めてほしい」と求められたら、どのサーバーがPHPで動いているか、誰が修正できるか、誰がサイト停止を決められるかを、すぐに答えられるでしょうか。

10月21日21時55分、予定された修正版の公開の約1時間前に、首相が全国のウェブサイト管理者に向けて、夜のうちに修正を適用するよう呼びかけました。 高市早苗首相のXへの投稿は、ウェブサイトのおよそ7割で使われているプログラミング言語PHPを名指しし、修正パッチの公開時刻を示し、すぐに適用できない場合はサイトを止めることもリスクを下げる有効策だとしました。銀行はインターネットバンキングを止め、ゲーム会社は緊急メンテナンスに入り、レンタルサーバー各社も夜通しの緊急メンテナンスを行いました。この記事では、首相がなぜ修正パッチに言及したのか、2件の脆弱性は何か、日本のどこまでに影響するか、今何をすべきかを整理します。

高市早苗首相のXへの投稿を、Xの自動英訳で表示したスクリーンショット。英文の内容:We have an urgent request for everyone to take action tonight. Regarding PHP, which is used on many websites, the PHP security team has issued a warning about two critical vulnerabilities. Please check if your publicly available websites use PHP, and prepare to apply the fix patch that will be released tonight at 11 PM on October 21 (Japan time) as soon as possible. If it is difficult to apply the fix patch promptly, temporarily suspending websites and similar measures can be considered an effective way to reduce risk. Related information is also being posted on the X account of the National Center of Incident Readiness and Strategy for Cybersecurity, so please refer to that as well. 末尾に@cyber_forecastへのリンク。

図1:首相の投稿(@takaichi_sanae、2026年10月21日21時55分)。Xの自動英訳による表示で、読者から提供されたスクリーンショット。プロフィール写真とXのロゴは除いています。英訳では国家サイバー統括室の旧英語名が使われています。

投稿の原文は次のとおりです。

皆様に、今夜に対応して頂きたい大至急のお願いです。

多くのウェブサイトで使用されているPHPに関し、PHPのセキュリティチームが、重大な2件の脆弱性に関する注意喚起を行っています。

公開しているウェブサイトでPHPを使用しているか確認し、今夜、10月21日午後11時(日本時間)に公開される修正パッチについて、可及的速やかに適用できるよう準備をお願いします。

修正パッチを速やかに適用することが困難な場合は、例えばウェブサイト等を一時的に停止させることも、リスクを低減させるための有効策の一つと考えられます。

投稿は最後に、国家サイバー統括室の注意・警戒情報のアカウント(@cyber_forecast)も参照するよう案内しています。高市早苗氏のX · 日本経済新聞 · Impress Watch

首相の投稿はなぜ重いのか

脆弱性の注意喚起は毎週のように出ています。ただし多くは専門機関から、セキュリティ担当者向けの言葉で出されます。今回の投稿は4つの点で違いました。

  1. 技術者ではなく、サイトの持ち主に向けた言葉だった。 「公開しているウェブサイトでPHPを使用しているか確認し」という問いは、制作会社に作ってもらったサイトをレンタルサーバーで動かしている商店、診療所、学校、自治体の担当者に向けたものです。こうした人たちはJPCERT/CCやIPAの注意喚起を読みませんが、首相の投稿は目にします。その夜、多くの人が制作会社やレンタルサーバー会社に問い合わせました。
  2. サイトを止める判断を後押しした。 国家サイバー統括室は11分前の21時44分に、同じ内容を投稿していました。修正パッチを速やかに適用することが困難な場合は、ウェブサイト等を一時的に停止させることもリスクを低減させるための有効策の一つ、というものです。国家サイバー統括室のX 売上のあるサイトを止めるのは経営判断です。首相が公に述べたことで、銀行や小売、自治体は、停止の理由を利用者や監査に説明しやすくなりました。EU機関のインシデント対応チームCERT-EUも、前日の夜に同様の助言を出しています。The Next Web(英語)
  3. 日単位ではなく、時間単位の話だった。 詳細は公開前に流出し、Cloudflareはすでに攻撃の試みを報告していました(後述の経緯)。「今夜」という言葉には、数日以内に適用するという通常のペースでは遅いという判断が表れています。
  4. 政策の流れの上にある。 高市氏は自民党のサイバーセキュリティ対策本部長として、2019年に能動的サイバー防御を可能にする法整備と、サイバーセキュリティ庁の設置を提言しました。2025年7月1日に国家サイバー統括室が発足した際には、新たな司令塔ができたことに「感無量」と投稿しています。高市早苗氏のX(2025年7月1日) 能動的サイバー防御の法律は2026年10月1日に施行され、10月8〜9日には大臣と与党のサイバー担当トップが相次ぐ漏えいを「大変危急の状況」「サイバー空間の非常事態」と表現しました。当サイトの記事のとおりです。10月21日にこの体制が向き合ったのは、それとは性質の違う課題でした。他者が決めた、数時間後に迫る技術的な期限です。

ほかの政治家も呼びかけを繰り返しました。国民民主党の玉木雄一郎氏は22時20分に同じ趣旨の警告を投稿しています。玉木雄一郎氏のX

投稿が意味しないこと。 これは要請であり、命令ではありません。統括室に、民間企業へ修正やサイト停止を強制する権限はありません。また、投稿が最も届いたウェブサイトの利用者には、修正すべきものがありません。脆弱性はサーバー側にあり、利用者はサイトの持ち主の対応を待つしかありません。

何が起きたか:時系列

2026年10月20〜22日の時系列(日本時間)を2段で示した図。上段はPHPとセキュリティ業界:10月21日4時52分、PHPがCVSS 9.2の重大な脆弱性2件を事前告知し、公開は日本時間23時と予告。10月20日(UTC)、Hacker Newsに脆弱性を告げる投稿が出て、その後削除。12時02分、CloudflareのWAFルールが既定で顧客を保護。20時ごろ、CloudflareのCTOが初期の攻撃が出始めていると発言。22時40分ごろ、PHPが8.5.12、8.4.27、8.3.36、8.2.35を公開。下段は日本:日中、IPAが準備を呼びかけ。16時15分までに予防的なサービス停止の発表が始まり、静岡銀行や七十七銀行などがサービスを停止。21時44分、国家サイバー統括室がサイトの一時停止も有効策と投稿。21時55分、首相が今夜の対応を要請。23時以降、Cygames、pixiv、レンタルサーバー各社が緊急メンテナンス。

図2:事前告知から修正版公開までの約20時間(2026年10月21日、日本時間)。Xの投稿時刻は投稿IDから算出。本記事のために作成した図。

  • 10月21日4時52分(UTCでは10月20日19時52分)。 PHPのセキュリティチームが、CVSS v4.0で9.2の重大な脆弱性2件を事前告知しました。公開は10月21日14時(UTC、日本時間23時)とし、詳細が公になった場合や悪用を確認した場合は前倒しすることがあるとしました。The PHP Foundation(X、英語) · php.announce(英語) · oss-security(英語)
  • 10月20日(UTC)。 Hacker Newsに、PHPのリモートコード実行の脆弱性2件の公表が近いとする投稿が出て、後に削除されました。Sansecは、PHP2Shellという名前の攻撃ツールの画面が流出したと報告しており、これが通称の由来です。Sansec(英語) · Huntress(英語)
  • 10月21日日中。 IPAが、PHPを使っているものを棚卸しし、公開後すぐに適用できるよう準備するよう呼びかけました。サポートが終了した古いPHPが使われ続けている例があること、攻撃はまだ確認していないことにも触れています。IPA
  • 10月21日12時02分(UTC 3時02分)。 Cloudflareによると、この時刻から無料・有料の顧客をマネージドWAFルールで既定で保護しています。顧客への攻撃通信と、ルールをすり抜けようとするペイロードの変更も観測したとし、CTOは初期の攻撃が出始めていると述べました。Cloudflare(英語)
  • 10月21日午後から夜。 予防的なサービス停止が始まりました。静岡銀行はインターネットバンキング、ホームページ、グループ会社のサイト、アプリを停止し、七十七銀行と第四北越銀行も一部サービスを止めました。静岡銀行と七十七銀行は、情報漏えいは確認されていないとしています。日本経済新聞:静岡銀行 · 日本経済新聞:七十七銀行 · 新潟日報:第四北越銀行
  • 10月21日21時44分と21時55分。 国家サイバー統括室、続いて首相が、サイトの一時停止も有効策だと発信しました。
  • 10月21日22時40分ごろ。 国内の報道によると、PHPは予定より少し早く8.5.12、8.4.27、8.3.36、8.2.35を公開しました。22時57分には、アドバイザリへのリンクを付けた投稿が出回っていました。ITmedia NEWS
  • 10月21〜22日の夜間。 Cygames、pixiv、ニコニコ、DMM.com、BASEなどが緊急メンテナンスやサービス停止を行い、さくらインターネット、エックスサーバー、GMOペパボのレンタルサーバー各社も緊急メンテナンスを実施しました。被害は報告されていません。Impress Watch · GAME Watch · 日本経済新聞:さくらインターネット

2件の脆弱性の中身

CVE-2026-109618 CVE-2026-109619
PHPのアドバイザリ GHSA-w8hw-w9fx-j79w「RCE via OOM corruption」 GHSA-6wmf-4rv5-p3rp「RCE via stale global state」
何が起きるか リクエストの開始時にメモリが不足すると、リクエスト用のメモリが初期化途中のまま残り、後片付けの処理がそれを正しいものとして扱う あるリクエストの状態が、メモリを解放した後も残り、同じワーカーで処理される次のリクエストがそれを使ってしまう
深刻度 CVSS v4.0 9.2(緊急) CVSS v4.0 9.2(緊急)
修正版 8.5.12、8.4.27、8.3.36、8.2.35 8.5.12、8.4.27、8.3.36、8.2.35

どちらも、認証なしにHTTP経由で遠隔からコードを実行されるおそれがあります。影響の大きさを決めるのは次の4点です。

  • ウェブのリクエストを処理するPHPはすべて対象です。 PHPは、ウェブのリクエストを処理するすべてのインストールに危険があるとしています。NCSC-NLは、PHP-FPM、Apacheのmod_php、php-cgi、FrankenPHP、LiteSpeedを挙げています。最も危険なのはmod_phpで、アプリケーションのコードが動く前に攻撃が成立することが実証されています。ログイン画面や、アプリ側の防御、更新済みのWordPressプラグインでは防げません。欠陥はPHP本体にあるからです。
  • 完全な緩和策はありません。 PHPの告知は、更新だけが安全な選択だとしています。後述の暫定設定は確認済みの攻撃経路をふさぐもので、すべての派生を防ぐものではありません。
  • PHP 8.1以前には公式の修正版が出ません。 7.4、8.0、8.1向けにはソースコードのみのバックポートがあると報じられており、配布するかどうかは各Linuxディストリビューションが判断します。PHP 5も影響を受けるという指摘(CERT-EUやSansec)はありますが、PHPのアドバイザリには書かれていません。
  • CVE-2026-109618は設定にも左右されます。 CrowdStrikeの分析では、脆弱になる条件はおおむね、memory_limitがpost_max_sizeの4倍より小さい場合です。PHPの初期設定はこれに当てはまりませんが、アップロード上限64MB、メモリ上限128MBといった本番環境でよくある組み合わせは当てはまります。CrowdStrike(英語)

悪用されているのか。 報告は分かれています。Cloudflareは攻撃通信とペイロードの変更を観測し、watchTowrはハニーポットで悪用を確認したとしています。NCSC-NLは10月21日時点で悪用の兆候はないとしつつ、公開された実証コードが複数あり、大規模な悪用が近いと見ています。Huntressも顧客環境では確認していません。CISAの「悪用が確認された脆弱性」カタログは、私たちが確認したバージョン2026.10.21(10月21日14時04分UTC公開)では、どちらのCVEも登録していませんでした。NCSC-NL(英語) · watchTowr(英語)

日本のどこまでに影響するか

当サイトの分析です。 規模を示す数字は3つあります。

  1. ウェブサイトのおよそ7割。 W3Techsによると、サーバー側の言語が分かるウェブサイトの70.5%がPHPを使っています(2026年8月)。そのうち29.4%はPHP 7、8.0%はPHP 5で、いずれも公式の修正版が出ません。W3Techs(英語)
  2. 日本国内で約14万2,000のウェブサービスが、ヘッダーでPHPを名乗っています。 私たちはスキャンをせずに、インターネット計測のデータセット(直近数週間の収集分)から、X-Powered-ByヘッダーでPHPを名乗っている国内のウェブサービスを数えました。バージョンを照合できた13万6,489件のうち、**10万633件(73.7%)は公式の修正版がないブランチ(8.1以前)**でした。修正版を名乗っていたのは26件だけです。バージョンを隠しているサイトは含まれないため、これは全数ではなく下限です。また、バージョンを名乗るサーバーは設定に手が回っていないものが多いため、古いブランチの割合は、サーバー全体よりここで高めに出ていると考えられます。
  3. 前回、主な標的は日本だった。 Windows上のPHP-CGIの脆弱性CVE-2024-4577は、日本語環境で影響が大きくなるもので、2025年1月から主に日本の組織に対して悪用されました。対象はテクノロジー、通信、エンターテインメント、教育、ECの各分野で、攻撃者はCobalt Strikeを入れ、認証情報を盗み、ログを消しました。Cisco Talos(英語) CISAはこの脆弱性を、ランサムウェアに使われたものとして登録しています。

X-Powered-ByヘッダーでPHPのバージョンを示している国内のウェブサービス数を、ブランチ別に示した棒グラフ。バージョンを示すサービスは136,489件。公式の修正版があるブランチ:PHP 8.5が3,341件、PHP 8.4が3,561件、PHP 8.3が20,594件、PHP 8.2が8,360件。公式の修正版がないブランチ:PHP 8.1が5,412件、PHP 8.0が6,570件、PHP 7.4が26,909件、PHP 7.3が8,818件、PHP 7.2が8,152件、PHP 7.1が3,056件、PHP 7.0が2,724件、PHP 5.6が12,829件、PHP 5.5が4,278件、PHP 5.4が8,582件、PHP 5.3が10,997件、PHP 5.2が2,306件。合計100,633件(73.7%)に公式の修正版がない。

図3:PHPのバージョンを示している国内のウェブサービス(ブランチ別)。8.2〜8.5には公式の修正版があり、8.1以前にはありません。2026年10月22日(日本時間)に、インターネット計測のデータセットからスキャンせずに集計。1サービスは1つのIPアドレスとポートの組。本記事のために作成した図。

ブランチ 名乗っているサービス数 公式の修正版
PHP 8.5 3,341 あり:8.5.12
PHP 8.4 3,561 あり:8.4.27
PHP 8.3 20,594 あり:8.3.36
PHP 8.2 8,360 あり:8.2.35
PHP 8.1 5,412 なし
PHP 8.0 6,570 なし
PHP 7.4 26,909 なし
PHP 7.3 8,818 なし
PHP 7.2 8,152 なし
PHP 7.1 3,056 なし
PHP 7.0 2,724 なし
PHP 5.6 12,829 なし
PHP 5.5 4,278 なし
PHP 5.4 8,582 なし
PHP 5.3 10,997 なし
PHP 5.2 2,306 なし
合計 136,489 公式の修正版なし:100,633(73.7%)

数字の読み方に注意してください。 ヘッダーはサーバーが名乗っているバージョンで、実際に動いているものとは限りません。Ubuntu、Debian、Red Hatは古いブランチをバージョン番号を変えずに修正するため、「7.4.3」を名乗るサーバーが修正済みのこともあれば、ヘッダーを隠したサーバーが未修正のこともあります。データセットは数週間かけて収集されたもので、修正版公開後ではなく公開前の状態を示しています。

PHPが隠れている場所。 自社のウェブサーバー以外も確認してください。

  • レンタルサーバーやマネージドホスティング。 事業者はPHP本体を更新しますが、管理画面で古いPHPのバージョンを選んだままのサイトは、サポート終了版で動いている可能性があります。事業者のお知らせを確認してください。
  • 購入した製品。 EC(EC-CUBE)、CMS(WordPress)、グループウェア、ウェブメール、監視コンソール、NAS、ネットワーク機器は、独自にPHPを組み込んでいることがあります。NCSC-NLはPHPに依存する製品の一覧を公開しています。
  • コンテナ。 古いphp:イメージを基にしたコンテナは、作り直して配備し直すまで脆弱なままです。

攻撃者の視点

レッドチームや組織的な攻撃者がPHP2Shellをどう使うかを、MITRE ATT&CKに対応させて示します。攻撃者の特定ではなく、想定のモデルです。

段階 具体的な動き ATT&CK
標的を探す 検索エンジンやスキャンデータからPHPを名乗るホストを集め、任意の.phpパスに送りつける。アプリケーションの知識は要らない T1595.002 Vulnerability Scanning、T1596 Search Open Technical Databases
侵入する 細工したHTTPリクエストを1〜数回送る。多くはマルチパートのフォーム送信で、アプリのコードやログインより前に成立する T1190 Exploit Public-Facing Application
コマンドを実行する ウェブサーバーのアカウントでシェルコマンドを実行。Windows上のPHPならPowerShell T1059.004 Unix Shell、T1059.001 PowerShell
居座る ウェブシェルを置く、.user.iniやphp.iniにauto_prepend_fileを加える、cronやSSH鍵を追加する T1505.003 Web Shell、T1053.003 Cron、T1098.004 SSH Authorized Keys
隠れる WAFルールをすり抜けるようペイロードを変える(Cloudflareが観測)。サーバーは200を返す。2025年の日本への攻撃のようにログを消す T1027 Obfuscated Files or Information、T1070.002 Clear Linux or Mac System Logs
利益を得る wp-config.phpや.envからデータベースのパスワードやクラウドの鍵を読み取る。マイニングツールを入れる。データを持ち出す。内部へ広がる T1552.001 Credentials In Files、T1496 Resource Hijacking、T1005 Data from Local System

狙いを定めた攻撃者は、やり方が違います。 大量の悪用は騒がしく、標的型の攻撃者はそれを隠れみのにします。目立つウェブシェルではなく、修正後も残る静かな仕掛け(prependファイルやcron)を置き、設定ファイルからクラウドの鍵を取って去ります。修正で扉は閉まりますが、すでに中にいる者は出ていきません。 後述の調査を、詳細が流出した10月20日から始めるのはそのためです。

防御側の視点

1. 今夜:修正するか、切り離す

  1. PHPをすべて見つける。 自社のウェブサーバー、レンタルサーバー上のサイト、コンテナ、PHPを組み込んだ製品。レンタルサーバー会社や製品の提供元には書面で確認します(後述のテンプレート)。
  2. 更新して再起動する。 8.5.12、8.4.27、8.3.36、8.2.35か、ディストリビューションの修正済みパッケージを入れます。PHP-FPMのプールとApacheはすべて再起動してください。動き続けているワーカーは古いコードのままです。コンテナはイメージを作り直して配備し直します。
  3. 今夜更新できない場合は、PHPが示した暫定設定を、ini_set()やディレクトリごとの.user.iniではなくサーバー全体の設定で入れ、再起動します。
    • session.upload_progress.enabled = 0で、セッションのアップロード進捗を使う経路をふさぐ
    • PHP 8.5ではfatal_error_backtraces = 0で、致命的エラーのバックトレースを使う経路をふさぐ
    • アップロードを受け付けないアプリでは、リバースプロキシやWAFでmultipart/form-dataのリクエストを遮断するか厳しく制限する
    • WAFの提供元が緊急ルールを出していれば有効にする(Cloudflareは公開済み)
  4. どれもできなければサイトを止める。 政府が助言したとおりです。インターネットに公開したmod_phpのサーバー、サポート終了版、個人情報を扱うものから優先します。
  5. サポート終了版には公式の修正版がありません。8.2以降へ上げるか、修正済みパッケージを出すディストリビューションや提供元を使うか、対応できるまでサーバーを切り離してください。

サーバーで動いているものを確認します。コマンドラインのPHPは、PHP-FPMやApacheとは別のphp.iniを読むことがあるので、ウェブを処理しているものを確認してください。

# What PHP does this server run, and are the interim settings in force?
php -v
php -i | grep -E '^(session.upload_progress.enabled|memory_limit|post_max_size|fatal_error_backtraces) '
# The PHP that serves the web can differ from the command line: check PHP-FPM and Apache too.
for f in /usr/sbin/php-fpm*; do [ -x "$f" ] && "$f" -v; done
apachectl -M 2>/dev/null | grep -i php
# Distribution packages: compare with your distribution's advisory, not with php.net.
dpkg -l 'php*' 2>/dev/null | grep '^ii' || rpm -qa 'php*'

2. 今週:10月20日までさかのぼって調べる

  • サーバーに残る痕跡は、エラーの1行だけかもしれません。 CrowdStrikeによると、攻撃が成功しても、ウェブサーバーのエラーログに「in Unknown on line 0」で終わるメモリ不足の致命的エラーが1行残るだけで、応答はHTTP 200、アプリのログには何も残らないことがあります。これを探します(後述のクエリ)。
  • ワーカーのクラッシュ。 同じ時間帯に、PHP-FPMの子プロセスがシグナル11で終了したり、Apacheの子プロセスがセグメンテーション違反を起こしたりしていないか。
  • ウェブサーバーがプログラムを起動していないか。 php-fpm、apache2、httpdがシェル、curl、wget、python、perlを起動していないか。
  • 新しいPHPファイルと設定の変更。 ウェブサーバーのアカウントがドキュメントルートに書いた新しい.phpファイル。.user.iniやphp.iniのauto_prepend_file、auto_append_file。新しいcronやSSH鍵。
  • 何か見つかったらサーバーを切り離し、作り直す前にログとメモリを保全し(順序はDFIR講座 第2回)、設定ファイルにある秘密情報をすべて変更します。

3. 対応すべきチーム

チーム 確認する範囲 次の一手 必要な証跡
IT運用 ウェブサーバー、コンテナ、レンタルサーバー上のサイト 修正か暫定設定。ワーカーの再起動。直せないものは停止 PHPのバージョン付きの資産一覧、修正と再起動の時刻
委託先リスク レンタルサーバー会社、制作会社、PHPを組み込んだ製品 後述の質問を送り、回答を管理する 書面の回答と日付
SIEM / SOC ウェブサーバーのエラーログ、WAFのログ 10月20日以降のエラーログをクエリで調べ、同じパターンに警報を設定 クエリの結果、警報ルール
EDR LinuxとWindowsのウェブサーバー ウェブサーバーのプロセスによるシェル起動とPHPファイルの書き込みを調べる プロセスとファイルのイベント
法務・プライバシー 個人情報を扱うサイト 予防的な停止に利用者向けのお知らせが要るか判断。不正アクセスが見つかった場合の個人情報保護委員会への報告に備える 判断の記録、お知らせの文面

ハンティング:ウェブサーバー上のPHP2Shell

確認時点で、確定した侵害の痕跡(IOC)を公表したベンダーはありませんでした。HuntressとEquixlyも同じ見解です。以下のクエリは、攻撃とその後に現れる振る舞いを探すものです。いずれも説明用の例で、実データでは実行していません。テーブル名とフィールド名は、2026年10月22日(日本時間)に各社の文書で確認しました。開始日は、詳細が流出した10月20日にしています。

1. ウェブサーバーがシェルやダウンロードツールを起動する。 Microsoft Defender XDRの高度な追及、DeviceProcessEvents(Defender for Endpointを入れたLinuxサーバー)。

// Illustrative; not run against live data. Microsoft Defender XDR advanced hunting, DeviceProcessEvents (Linux web servers with Defender for Endpoint).
// PHP2Shell (CVE-2026-109618, CVE-2026-109619): after exploitation, the PHP worker or web server starts a shell or a download tool.
// Expect noise from applications that call sendmail, convert, git or similar; list them in KnownGood after review.
let Since = datetime(2026-10-20);
let WebServers = dynamic(["REPLACE_WITH_WEB_SERVER_NAME"]);
let KnownGood = dynamic(["REPLACE_WITH_EXPECTED_COMMAND_LINE"]);
DeviceProcessEvents
| where Timestamp > Since
| where DeviceName has_any (WebServers)
| where InitiatingProcessFileName matches regex @"^(php(-fpm|-cgi)?[0-9.]*|apache2|httpd|lsphp|frankenphp)$"
| where FileName in~ ("sh", "bash", "dash", "busybox", "curl", "wget", "python", "python3", "perl", "nc", "ncat", "socat", "base64", "whoami", "id", "uname")
| where not(ProcessCommandLine has_any (KnownGood))
| summarize Runs = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp), Commands = make_set(ProcessCommandLine, 20)
    by DeviceName, InitiatingProcessFileName, FileName, AccountName
| order by FirstSeen asc
| take 500

2. 攻撃が残すエラーの1行。 Microsoft Sentinel、Syslogテーブル。Apache、Nginx、PHP-FPMのエラーログをsyslog経由で転送しているウェブサーバーが対象です。エラーログをカスタムテーブルに送っている場合は、テーブル名と列名を変えてください。

// Illustrative; not run against live data. Microsoft Sentinel, Syslog table, for web servers that forward Apache, Nginx or PHP-FPM error logs through syslog.
// PHP2Shell (CVE-2026-109618): a successful attempt may leave only a memory-exhaustion fatal error ending "in Unknown on line 0",
// raised while the request starts, before any script runs. Worker crashes (signal 11) around the same time strengthen the signal.
let Since = datetime(2026-10-20);
let WebServers = dynamic(["REPLACE_WITH_WEB_SERVER_NAME"]);
Syslog
| where TimeGenerated > Since
| where Computer has_any (WebServers)
| where (SyslogMessage has "Allowed memory size" and SyslogMessage contains "in Unknown on line 0")
    or SyslogMessage has "exited on signal 11"
    or SyslogMessage has "Segmentation fault"
| extend Signal = iff(SyslogMessage has "Allowed memory size", "startup memory fatal", "worker crash")
| summarize Events = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Sample = take_any(SyslogMessage)
    by Computer, Signal, bin(TimeGenerated, 10m)
| order by FirstSeen asc
| take 500

3. 同じエラーをSplunkで探す。 ウェブサーバーのエラーログが対象です。

index="REPLACE_WITH_WEB_INDEX" sourcetype="REPLACE_WITH_ERROR_LOG_SOURCETYPE" earliest="10/20/2026:00:00:00"
    (("Allowed memory size" "Unknown on line 0") OR "exited on signal 11" OR "Segmentation fault")
| eval signal=if(like(_raw, "%Allowed memory size%"), "startup memory fatal", "worker crash")
| bin _time span=10m
| stats count AS events earliest(_time) AS first_seen latest(_time) AS last_seen values(source) AS sources BY _time host signal
| sort 0 first_seen

クエリのダウンロード:プロセスのKQL · エラーログのKQL · エラーログのSPL

ほかの製品。 CrowdStrike Falcon(LogScale / Next-Gen SIEM)、LinuxのProcessRollup2。CrowdStrikeはエラーログ側の兆候向けに、相関ルールのテンプレート「Generic - Web - PHP Memory Exhaustion During Request Startup」も提供しています。

// Illustrative; not run against live data. CrowdStrike Falcon, ProcessRollup2 events from Linux web servers.
// The web server or PHP worker starting a shell or a download tool. Map aid to your host inventory before triage.
#event_simpleName=ProcessRollup2 event_platform=Lin
| in(field="aid", values=["REPLACE_WITH_WEB_SERVER_AID_1", "REPLACE_WITH_WEB_SERVER_AID_2"])
| ParentBaseFileName=/^(php(-fpm|-cgi)?[0-9.]*|apache2|httpd|lsphp|frankenphp)$/i
| FileName=/^(sh|bash|dash|busybox|curl|wget|python3?|perl|nc|ncat|socat|base64|whoami|id|uname)$/i
| groupBy([aid, ParentBaseFileName, FileName, CommandLine], function=[count(as=Runs), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)])
| sort(FirstSeen, order=asc, limit=500)

Sumo Logic、ウェブサーバーのエラーログ:

_sourceCategory="REPLACE_WITH_ERROR_LOG_SOURCE_CATEGORY"
| where (_raw matches "*Allowed memory size*" and _raw matches "*Unknown on line 0*") or _raw matches "*exited on signal 11*" or _raw matches "*Segmentation fault*"
| if(_raw matches "*Allowed memory size*", "startup memory fatal", "worker crash") as signal
| timeslice 10m
| count as events by _timeslice, _sourceHost, signal
| sort by _timeslice asc

結果が出なくても、攻撃を受けていないとは言えません。エラーログを転送していないサーバーは多く、メモリ不足のエラーはノイズとして除外されがちで、シェルを得た攻撃者はローカルのログを消せます。リクエスト側の検知には、CorelightがZeekとSuricataのルールを公開しています。DeviceProcessEvents(英語) · Syslogテーブル(英語) · Corelight(英語)

検知ルール:Microsoft SentinelとCrowdStrike

上のハンティングは一度だけさかのぼる調査です。以下のルールは定期的に実行されて警報を出すので、次の攻撃をその場で捉えられます。最初のリクエストから、修正後も残る居座りまで、攻撃の流れを追う構成にしています。

いずれも説明用の例で、実データでは実行していません。KQLはすべて、Microsoftのクエリエンジンのライブラリ(Kusto.Language)を使い、Microsoftの文書にあるテーブル定義に照らして解析しました。存在しない列や構文の誤りはこれで見つかります。Sentinelの各ファイルは、Microsoftが公開している分析ルールのテンプレート要件とも照合しました。CQLはCrowdStrikeが公開しているイベント名とクエリ関数に照らして確認しましたが、実行はしていません。

捉えるもの ルール 必要なデータ ATT&CK 重大度
サーバーに残る攻撃の唯一の痕跡:スクリプト実行前のメモリ不足エラー Sentinel 01・CrowdStrike 05 ウェブサーバーのエラーログ T1190 中
攻撃の試行・調整中に起きるPHPワーカーのクラッシュの集中 Sentinel 02 ウェブサーバーのエラーログ T1190、T1499.004 中
ウェブサーバーによるシェル、ダウンロードツール、偵察コマンドの起動 Sentinel 03・CrowdStrike 01 EDRのプロセスイベント T1059.004、T1505.003 高
ウェブサーバーによるウェブシェル、.user.ini、.htaccess、php.iniの書き込み Sentinel 04・CrowdStrike 02 EDRのファイルイベント T1505.003 中
アップロード、キャッシュ、一時フォルダーにあるスクリプトのPHPによる実行 CrowdStrike 03 LinuxセンサーのPHP可視化 T1505.003 高
ウェブサーバーから、これまで通信したことのない公開アドレスへの接続 Sentinel 05・CrowdStrike 04 EDRのネットワークイベント T1105、T1071.001 中
ウェブサーバー側からのcron、systemd、SSH鍵、アカウントの変更 Sentinel 06・CrowdStrike 06 EDRのプロセスとファイルのイベント T1053.003、T1098.004、T1543.002 高
同じサーバーで、試行から30分以内にシェルが起動 Sentinel 07 エラーログとEDR T1190、T1059.004 高
修正版より古い、または修正版のないPHPが動いているサーバー Defenderのクエリ Defender脆弱性の管理

導入のしかた。

  • Microsoft Sentinel。 各ファイルは、MicrosoftのSentinelリポジトリと同じYAML形式の分析ルールです。リポジトリ接続で取り込むか、スケジュールルールを作成して、クエリ、実行間隔、さかのぼる期間、エンティティのマッピング、戦術をファイルから写してください。先にREPLACE_WITH_の値をすべて置き換えます。ルール03〜07には、Device*テーブルを転送するMicrosoft Defender XDRコネクタが必要です。ルール01、02、07には、ウェブサーバーのエラーログをAzure Monitor AgentでSyslogに収集しておく必要があります。
  • CrowdStrike Falcon。 各CQLをNext-Gen SIEMの相関ルールとして保存する(まずは15分ごとに直近20分を対象にするのが目安)か、Event Searchで実行します。ルール03には、PhpExecuteScriptを記録するLinuxセンサーのPHP可視化が必要です。ルール05には、ウェブサーバーのエラーログをサードパーティデータとして取り込む必要があります。CrowdStrikeは、エラーログ側の兆候向けにテンプレート「Generic - Web - PHP Memory Exhaustion During Request Startup」も提供しています。
  • まずは静かに動かす。 各ルールを1週間、テスト用のキューに警報を送る形で動かし、何がなぜ検知されたかを記録してください。アプリケーションの通常の動きをKnownGood、WebAccounts、パスの除外に加えてから、本番の警報にします。

Microsoft Sentinelの分析ルール

01. リクエスト開始時のPHPメモリ不足エラー。 CrowdStrikeが、攻撃が成功しても残るのはこれだけかもしれないとする1行です。スクリプトが動く前に出る「in Unknown on line 0」のメモリ不足エラーです。通常のスクリプトのメモリエラーにはファイル名と行番号が出るため、誤検知はまれです。

id: e0d3171b-48a9-5604-b1e4-653cc3e65f55
name: PHP2Shell - PHP memory fatal at request startup
description: |
  'A web server logged a PHP memory-exhaustion fatal error raised "in Unknown on line 0", before any script ran: the trace CrowdStrike describes for attempts on CVE-2026-109618. Needs web server error logs in Syslog.'
severity: Medium
requiredDataConnectors:
  - connectorId: SyslogAma
    dataTypes:
      - Syslog
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  // PHP2Shell (CVE-2026-109618): a memory fatal raised while the request starts, before any application code runs.
  // Ordinary script memory errors name a file and a line; these name "Unknown" and line 0.
  Syslog
  | where SyslogMessage has "Allowed memory size" and SyslogMessage contains "in Unknown on line 0"
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Events = count(), Sample = take_any(SyslogMessage) by Computer, ProcessName
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

02. PHPワーカーのクラッシュの集中。 メモリ破壊型の攻撃は、試行中にワーカーを落とすことがよくあります。1台のサーバーで10分間に5回を最初のしきい値とし、小規模なサイトでは下げてください。

id: 2c646486-7a9c-5ba7-a7e1-61950d5272f6
name: PHP2Shell - Burst of PHP worker crashes
description: |
  'Five or more PHP-FPM or Apache worker crashes (signal 11, segmentation fault) on one server within ten minutes. Memory-corruption exploits such as CVE-2026-109619 often crash workers while they are being tuned.'
severity: Medium
requiredDataConnectors:
  - connectorId: SyslogAma
    dataTypes:
      - Syslog
queryFrequency: 15m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Impact
relevantTechniques:
  - T1190
  - T1499.004
query: |
  // PHP2Shell (CVE-2026-109619): bursts of crashed PHP workers while an exploit is tried and tuned.
  // PHP-FPM logs "child ... exited on signal 11 (SIGSEGV)"; Apache logs "child pid ... exit signal Segmentation fault (11)" (AH00052).
  let Threshold = 5;
  Syslog
  | where SyslogMessage has "exited on signal 11" or SyslogMessage has "Segmentation fault"
  | where ProcessName has_any ("php", "apache", "httpd") or SyslogMessage has_any ("php-fpm", "AH00052")
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Crashes = count(), Sample = take_any(SyslogMessage) by Computer, bin(TimeGenerated, 10m)
  | where Crashes >= Threshold
  | project StartTime, EndTime, Computer, Crashes, Sample
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

03. ウェブサーバーによるシェルやツールの起動。 攻撃が成功したことを示す最も確かな兆候です。PHPワーカーやウェブサーバーがsh、curl、python、whoamiを起動します。sendmailや画像処理ツールを呼ぶアプリケーションもあるので、一度確認してKnownGoodに加えてください。

id: 046199a4-b983-57ac-8e4d-3c78e139a8bf
name: PHP2Shell - Web server starts a shell or tool
description: |
  'A PHP worker or web server process started a shell, a download tool or a reconnaissance command. After PHP2Shell exploitation the attacker runs commands as the web server account. Tune KnownGood for tools your applications call.'
severity: High
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - Persistence
relevantTechniques:
  - T1059.004
  - T1059.001
  - T1505.003
query: |
  // PHP2Shell post-exploitation: commands run by the PHP worker or web server (Linux and PHP on Windows).
  let WebServer = @"(?i)^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$";
  let Tools = dynamic(["sh", "bash", "dash", "zsh", "busybox", "curl", "wget", "python", "python3", "perl", "ruby", "nc", "ncat", "netcat", "socat", "base64", "whoami", "id", "uname", "cmd.exe", "powershell.exe", "pwsh.exe", "certutil.exe", "whoami.exe"]);
  // Add command lines your applications legitimately run (a mail or image tool, for example) after review.
  let KnownGood = dynamic(["REPLACE_WITH_EXPECTED_COMMAND_LINE"]);
  DeviceProcessEvents
  | where InitiatingProcessFileName matches regex WebServer
  | where FileName in~ (Tools)
  | where not(ProcessCommandLine has_any (KnownGood))
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Runs = count(), Commands = make_set(ProcessCommandLine, 20) by DeviceName, AccountName, InitiatingProcessFileName, FileName
  | extend CommandLine = tostring(Commands[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: CommandLine
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

04. ウェブサーバーによるPHPファイルや設定ファイルの書き込み。 ウェブシェルやauto_prepend_fileによる居座りはこうして仕込まれます。ブラウザーから実行するCMSの更新もPHPファイルを書き込みますが、通常は数百件が一度に出ます。アップロード用フォルダーに数件の新しいファイルがある場合こそ追うべきです。

id: 451d8a12-b30b-578f-aa74-3255c881ef46
name: PHP2Shell - Web server writes PHP or ini files
description: |
  'A PHP worker or web server, or a process it started, created or changed a PHP script, .user.ini, .htaccess or php.ini. This is how web shells and auto_prepend_file persistence are planted. CMS updates write many files at once; review those.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceFileEvents
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1505.003
query: |
  // PHP2Shell persistence: web shells and auto_prepend_file settings written by the web server or its children.
  // A CMS update run from the browser also writes PHP files: a large Files count in one burst is usually an update.
  let WebServer = @"(?i)^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$";
  let ScriptExtensions = dynamic([".php", ".phtml", ".phar", ".php5", ".php7", ".pht", ".inc"]);
  DeviceFileEvents
  | where ActionType in ("FileCreated", "FileModified", "FileRenamed")
  | where InitiatingProcessFileName matches regex WebServer or InitiatingProcessParentFileName matches regex WebServer
  | extend Extension = tolower(extract(@"(\.[A-Za-z0-9]+)$", 1, FileName))
  | where Extension in (ScriptExtensions) or FileName in~ (".user.ini", ".htaccess", "php.ini")
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Files = dcount(FileName), Names = make_set(FileName, 20), Folders = make_set(FolderPath, 10) by DeviceName, InitiatingProcessAccountName, InitiatingProcessFileName
  | extend FileName = tostring(Names[0]), FolderPath = tostring(Folders[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingProcessAccountName
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FileName
      - identifier: Directory
        columnName: FolderPath
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

05. ウェブサーバーから新しい接続先への通信。 直近1時間を過去14日間と比べるので、サーバーごとの通常の通信先を学習します。追加のダウンロード、リバースシェル、マイニングは、新しい公開アドレスとして現れます。

id: 15a8f236-3b55-55e9-afd0-be581a551778
name: PHP2Shell - Web server reaches a new destination
description: |
  'The web server, a PHP worker or a process they started connected to a public address it had not contacted in the previous 14 days: second-stage downloads, reverse shells and miners. Applications calling new APIs will also match.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceNetworkEvents
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1105
  - T1071.001
query: |
  // PHP2Shell follow-on traffic: outbound connections from the web server tree to public addresses not seen in the last 14 days.
  let Recent = 1h;
  let WebServer = @"(?i)^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$";
  let Outbound = dynamic(["ConnectionSuccess", "ConnectionAttempt", "ConnectionRequest"]);
  let Known = DeviceNetworkEvents
  | where TimeGenerated < ago(Recent)
  | where ActionType in (Outbound) and RemoteIPType == "Public"
  | where InitiatingProcessFileName matches regex WebServer or InitiatingProcessParentFileName matches regex WebServer
  | distinct DeviceId, RemoteIP;
  DeviceNetworkEvents
  | where TimeGenerated >= ago(Recent)
  | where ActionType in (Outbound) and RemoteIPType == "Public"
  | where InitiatingProcessFileName matches regex WebServer or InitiatingProcessParentFileName matches regex WebServer
  | join kind=leftanti Known on DeviceId, RemoteIP
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Connections = count(), Ports = make_set(RemotePort, 10), Urls = make_set(RemoteUrl, 10) by DeviceName, InitiatingProcessAccountName, InitiatingProcessFileName, RemoteIP
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingProcessAccountName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: RemoteIP
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

06. ウェブサーバーのアカウントによる居座り。 修正後も追い出されないために攻撃者が取る手順です。ウェブサーバーのアカウントや、それが起動したプロセスによるcron、systemdユニット、SSH鍵、アカウントの追加を捉えます。

id: 6bbdb4a8-2890-5061-ac24-5bca80d57c40
name: PHP2Shell - Persistence by the web server account
description: |
  'The web server account, or a process started by the web server, edited cron, systemd units or SSH keys, or ran crontab, useradd or ssh-keygen. Attackers use these to survive patching. Add your site accounts to WebAccounts.'
severity: High
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
      - DeviceFileEvents
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1053.003
  - T1098.004
  - T1543.002
query: |
  // PHP2Shell persistence that survives the patch: cron, systemd and SSH keys changed from the web server's side.
  let WebServer = @"(?i)^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$";
  let WebAccounts = dynamic(["www-data", "apache", "nginx", "httpd", "REPLACE_WITH_SITE_ACCOUNT"]);
  let Processes = DeviceProcessEvents
  | where AccountName in~ (WebAccounts) or InitiatingProcessFileName matches regex WebServer or InitiatingProcessParentFileName matches regex WebServer
  | where FileName in~ ("crontab", "at", "ssh-keygen", "useradd", "usermod", "systemctl")
  | project TimeGenerated, DeviceName, AccountName, Activity = strcat("ran ", FileName), Detail = ProcessCommandLine;
  let Files = DeviceFileEvents
  | where ActionType in ("FileCreated", "FileModified", "FileRenamed")
  | where InitiatingProcessAccountName in~ (WebAccounts) or InitiatingProcessFileName matches regex WebServer or InitiatingProcessParentFileName matches regex WebServer
  | where FolderPath has_any ("/var/spool/cron", "/etc/cron", "/etc/systemd/system", "/.ssh/") or FileName =~ "authorized_keys"
  | project TimeGenerated, DeviceName, AccountName = InitiatingProcessAccountName, Activity = strcat("wrote ", FileName), Detail = FolderPath;
  union Processes, Files
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Activities = make_set(Activity, 10), Details = make_set(Detail, 10) by DeviceName, AccountName
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

07. 起動時のメモリ不足エラーの後にシェル。 ルール01と03を、同じホストで30分以内という条件で組み合わせます。成功した攻撃を示します。めったに出ませんが、出たら直ちにインシデント対応へ回してください。

id: 041b9e86-07b8-584e-9e39-731fe3660f9e
name: PHP2Shell - Startup memory fatal then shell
description: |
  'On the same server, a PHP startup memory fatal (rule 01) was followed within 30 minutes by the web server starting a shell or tool (rule 03): an exploit attempt that worked. Matches hosts on their short name.'
severity: High
requiredDataConnectors:
  - connectorId: SyslogAma
    dataTypes:
      - Syslog
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 30m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Execution
relevantTechniques:
  - T1190
  - T1059.004
query: |
  // PHP2Shell, attempt then execution: the error-log signal and a web server shell on the same host within 30 minutes.
  // Syslog and Defender name hosts differently, so both sides are matched on the short host name.
  let Window = 30m;
  let WebServer = @"(?i)^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$";
  let Tools = dynamic(["sh", "bash", "dash", "zsh", "busybox", "curl", "wget", "python", "python3", "perl", "ruby", "nc", "ncat", "netcat", "socat", "base64", "whoami", "id", "uname", "cmd.exe", "powershell.exe", "pwsh.exe", "certutil.exe", "whoami.exe"]);
  let Signals = Syslog
  | where SyslogMessage has "Allowed memory size" and SyslogMessage contains "in Unknown on line 0"
  | extend Host = tolower(tostring(split(Computer, ".")[0]))
  | summarize SignalTime = min(TimeGenerated), Signals = count() by Host;
  DeviceProcessEvents
  | where InitiatingProcessFileName matches regex WebServer
  | where FileName in~ (Tools)
  | extend Host = tolower(tostring(split(DeviceName, ".")[0]))
  | join kind=inner Signals on Host
  | where TimeGenerated between (SignalTime .. (SignalTime + Window))
  | summarize StartTime = min(SignalTime), EndTime = max(TimeGenerated), Signals = max(Signals), Commands = make_set(ProcessCommandLine, 10) by DeviceName, AccountName
  | extend CommandLine = tostring(Commands[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: CommandLine
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

Defender XDR:修正が必要なサーバー。 これはDefender XDRの高度な追及で実行してください。Microsoftは脆弱性のテーブルをSentinelに取り込んでいません。Defenderが見つけたPHPのバージョンを、ブランチごとの修正版と比べます。Microsoftが2件のCVEを対応付けた後は、DeviceTvmSoftwareVulnerabilitiesをCVE番号で絞り込めば、同じ答えを直接得られます。

// Microsoft Defender XDR advanced hunting, not Sentinel: Defender Vulnerability Management tables are not ingested into Sentinel.
// PHP found by Defender Vulnerability Management, by branch, against the fixed releases of 21 October 2026.
// On Linux the inventory covers PHP installed with the package manager, not PHP built from source or inside containers.
// Distributions often fix PHP without changing the version number: check "below the fixed release" rows against your distribution's advisory.
let Fixed = datatable(Branch: string, FixedVersion: string) ["8.5", "8.5.12", "8.4", "8.4.27", "8.3", "8.3.36", "8.2", "8.2.35"];
DeviceTvmSoftwareInventory
| where SoftwareName has "php"
| extend Numeric = extract(@"^(\d+\.\d+\.\d+)", 1, SoftwareVersion)
| extend Branch = extract(@"^(\d+\.\d+)", 1, Numeric)
| join kind=leftouter Fixed on Branch
| extend Status = case(isempty(Numeric), "Version not recognised", isempty(FixedVersion), "No official fix: PHP 8.1 or older", parse_version(Numeric) < parse_version(FixedVersion), "Below the fixed release", "At or above the fixed release")
| summarize Devices = dcount(DeviceId), Examples = make_set(DeviceName, 10) by SoftwareVendor, SoftwareName, SoftwareVersion, Status
| order by Status asc, Devices desc

CrowdStrike Falcon Next-Gen SIEMのルール

01. ウェブサーバーによるシェルやツールの起動。 Sentinelのルール03と同じ考え方を、ProcessRollup2で実装したものです。

// Illustrative; not run against live data. CrowdStrike Falcon Next-Gen SIEM (CQL): save as a correlation rule or run in Event Search.
// PHP2Shell post-exploitation: a shell, download tool or reconnaissance command started by a PHP worker or web server.
// Schedule every 15 minutes over the last 20. Events carry the sensor ID (aid): map it to your host inventory.
#event_simpleName=ProcessRollup2
| ParentBaseFileName=/^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$/i
| FileName=/^(sh|bash|dash|zsh|busybox|curl|wget|python3?|perl|ruby|nc|ncat|netcat|socat|base64|whoami|id|uname|cmd\.exe|powershell\.exe|pwsh\.exe|certutil\.exe|whoami\.exe)$/i
| CommandLine!=/REPLACE_WITH_EXPECTED_COMMAND_LINE/
| groupBy([aid, UserName, ParentBaseFileName, FileName], function=[count(as=Runs), collect([CommandLine]), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)])

02. ウェブサーバーによるPHPスクリプトの書き込み。 NewScriptWrittenは、ファイルと書き込んだプロセスを記録します。CrowdStrike自身が挙げるウェブシェルの例は、apache2がアップロード用フォルダーに書いたcache.phpです。

// Illustrative; not run against live data. CrowdStrike Falcon Next-Gen SIEM (CQL): save as a correlation rule or run in Event Search.
// PHP2Shell persistence: a PHP script written to disk by the web server (NewScriptWritten, Linux sensor).
// A CMS update run from the browser writes many files at once; a handful of new files in an uploads folder is the case to chase.
#event_simpleName=NewScriptWritten
| ContextBaseFileName=/^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$/i
| FileName=/\.(php[0-9]?|phtml|phar|pht|inc)$/i
| groupBy([aid, ContextBaseFileName, FilePath], function=[count(as=Files), collect([FileName]), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)])

03. アップロードやキャッシュのフォルダーにあるスクリプトの実行。 PhpExecuteScriptは、実行されたPHPファイルをすべて記録します。正規のアプリケーションがアップロード、キャッシュ、一時フォルダーからコードを実行することはまれですが、ウェブシェルの多くはそうします。

// Illustrative; not run against live data. CrowdStrike Falcon Next-Gen SIEM (CQL): save as a correlation rule or run in Event Search.
// PHP2Shell web shell in use: PHP executing a script from a folder meant for uploads, caches or temporary files.
// Needs the Linux sensor's PHP visibility (PhpExecuteScript). Add your application's legitimate cache paths to the exclusion.
#event_simpleName=PhpExecuteScript
| TargetFileName=/\/(uploads?|tmp|temp|cache|images?|files|media)\//i
| TargetFileName!=/REPLACE_WITH_LEGITIMATE_CACHE_PATH/
| groupBy([aid, TargetFileName], function=[count(as=Runs), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)])

04. ウェブサーバーのプロセスからの外向き通信。 ネットワーク接続を、それを行ったプロセスと結び付けます。検索期間より前に起動した常駐のPHPワーカーは対象外になるため、含めるには期間を24時間に広げてください。

// Illustrative; not run against live data. CrowdStrike Falcon Next-Gen SIEM (CQL): save as a correlation rule or run in Event Search.
// PHP2Shell follow-on traffic: public connections from processes started by the web server, and from workers started in the search window.
// Long-running PHP workers started before the window are missed; widen the window to 24 hours to include them.
#event_simpleName=NetworkConnectIP4
| cidr(field=RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16", "100.64.0.0/10"], negate=true)
| join({#event_simpleName=ProcessRollup2 | FileName=/^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$/i or ParentBaseFileName=/^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$/i}, field=[aid, ContextProcessId], key=[aid, TargetProcessId], include=[FileName, ParentBaseFileName, CommandLine])
| groupBy([aid, ParentBaseFileName, FileName, RemoteAddressIP4, RemotePort], function=[count(as=Connections), collect([CommandLine]), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)])

05. リクエスト開始時のPHPメモリ不足エラー。 Next-Gen SIEMに送ったウェブサーバーのログで、エラーログ側の兆候を探します。

// Illustrative; not run against live data. CrowdStrike Falcon Next-Gen SIEM (CQL): save as a correlation rule or run in Event Search.
// PHP2Shell (CVE-2026-109618): the memory fatal raised before any script runs, in web server error logs ingested as third-party data.
// host.name follows the CrowdStrike Parsing Standard; change it if your parser names the host differently.
"Allowed memory size" "in Unknown on line 0"
| groupBy([host.name], function=[count(as=Events), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)])

06. ウェブサーバーのアカウントによる居座り。 Sentinelのルール06と同じ考え方を、プロセスイベントで実装したものです。

// Illustrative; not run against live data. CrowdStrike Falcon Next-Gen SIEM (CQL): save as a correlation rule or run in Event Search.
// PHP2Shell persistence that survives the patch: cron, SSH keys, accounts and services changed by the web server account or its children.
#event_simpleName=ProcessRollup2
| UserName=/^(www-data|apache|nginx|httpd|REPLACE_WITH_SITE_ACCOUNT)$/i or ParentBaseFileName=/^(php(-fpm|-cgi)?[0-9.]*(\.exe)?|apache2|httpd(\.exe)?|lsphp|frankenphp)$/i
| FileName=/^(crontab|at|ssh-keygen|useradd|usermod|systemctl)$/i
| groupBy([aid, UserName, ParentBaseFileName, FileName], function=[count(as=Runs), collect([CommandLine]), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)])

ルールのダウンロード:Sentinel 01 · 02 · 03 · 04 · 05 · 06 · 07 · Defenderの棚卸しKQL · CrowdStrike 01 · 02 · 03 · 04 · 05 · 06

ルールが検知したら。 ルール03、06、07は侵害の可能性が高いものとして扱ってください。サーバーを切り離し、作り直す前にメモリとログを保全し、ルール04と06の結果から仕込まれたものを確認し、設定ファイルにある秘密情報をすべて変更します。修正済みのサーバーでルール01や02が出た場合は、誰かが攻撃を試みています。未修正のサーバーなら、今すぐ修正するか止めてください。警報が出なくても、攻撃者がいないとは言えません。これらのルールが見えるのは、エラーログとEDRが記録するものだけです。

コピーして使えるテンプレート

[]の部分を置き換えてください。社外に出すものは、法務またはプライバシー担当の確認を受けてください。

レンタルサーバー会社、制作会社、製品の提供元への質問

件名:PHPの脆弱性CVE-2026-109618、CVE-2026-109619(PHP2Shell)への対応状況について([サイト名/製品名])

PHPは2026年10月21日、遠隔からコードを実行される重大な脆弱性2件の修正版(8.5.12、8.4.27、8.3.36、8.2.35)を公開しました。国家サイバー統括室と首相も、サイト管理者に直ちに適用するよう求めています。[日時]までに、次の点をご回答ください。

1. [サイト名/製品名]で使っているPHPのバージョンと実行方式(PHP-FPM、mod_php、CGI、LiteSpeed、その他)。
2. 修正版、またはディストリビューションの修正済みパッケージは適用済みか。適用日時と、PHPのワーカーをすべて再起動したか。
3. 未適用の場合、どの暫定策を入れているか(session.upload_progress.enabled = 0、WAFルール、アップロード制限など)と、修正の予定日。
4. PHP 8.1以前で動いている部分はあるか。ある場合の対応方針。
5. 10月20日以降のログで、悪用の兆候(「in Unknown on line 0」のメモリエラー、ワーカーのクラッシュ、新しいPHPファイル)を確認したか。その結果。
6. 何か見つかった場合の連絡窓口と、こちらへ連絡するまでの時間。

予防的にサイトを止めるときの利用者向けのお知らせ

[サービス名]一時停止のお知らせ(セキュリティ対策のため)

多くのウェブサイトで使われているプログラミング言語「PHP」に、重大な脆弱性が見つかりました。当社のサービスもPHPを使っています。国家サイバー統括室の呼びかけを受け、修正を適用するまでの間、[日付][時刻]から[サービス/ページ]を一時停止しています。

現時点で、不正アクセスやお客様の情報の漏えいは確認されていません。お客様に必要な操作はありません。修正と確認が終わり次第、[日時]ごろの再開を予定しています。状況はこのお知らせで更新します。

お問い合わせ:[連絡先]

経営層向け5行報告

1. 何が起きたか:2026年10月21日、PHPが、ログインなしにウェブサーバー上でコードを実行される重大な脆弱性2件(CVE-2026-109618、CVE-2026-109619)を修正しました。首相と国家サイバー統括室は、その夜のうちに修正するか、修正できないサイトは止めるよう求めました。
2. 当社への影響:インターネットに公開しているシステムのうち[件数]がPHPを使用。[時刻]までに[件数]を修正済み。公式の修正版がないPHP 8.1以前が[件数]。
3. 暫定策:[設定/WAFルール/停止したサイトと時刻]。
4. 確認状況:[件数]のシステムで10月20日以降のログを確認、結果は[なし/調査中]。提供元への確認は[件数]件、回答は[件数]件。
5. 判断が必要な事項:[例:[サイト]を[日付]まで停止したままにする、サポート終了版のPHPを更新する予算]。次回の報告は[日時]。

まだ分かっていないこと

  • どこまで悪用されているか。 Cloudflareは攻撃通信を観測しましたが、NCSC-NLとHuntressは10月21日時点で悪用を確認していません。確定した痕跡情報は公表されていません。
  • どの古いバージョンが影響を受けるか。 PHPのアドバイザリはサポート中のブランチだけを対象にしています。PHP 5も影響を受けるという指摘は、PHPではなくCERT-EUや研究者によるものです。
  • 国内で実際に攻撃できるサーバーがどれだけあるか。 当サイトの集計は、修正版公開前にPHPを名乗っていたサーバーの数です。ヘッダーを隠したサーバー、バックポートされたパッケージ、メモリの設定によって、実際の数は変わります。
  • サイトの停止で何かを防げたか。 確認時点で、被害を公表した組織はありませんでした。

出典と更新履歴

当サイトの関連記事:「サイバー空間の非常事態」 · 国内の漏えい公表60日間のまとめ · DFIR講座 第2回:証拠を壊さずに集める

2026年10月22日9時50分: 検知ルールを追加しました。Microsoft Sentinelの分析ルール7件、修正が必要なサーバーを探すDefender XDRのクエリ、CrowdStrike FalconのCQLルール6件と、それぞれのダウンロードです。KQLはMicrosoftのKusto.Languageライブラリを使い、文書にあるテーブル定義に照らして解析しました。いずれのルールも実データでは実行していません。

2026年10月22日: 初版を公開しました。出典は10月22日1時50分から2時12分(日本時間)に確認しました。php.net、GitHub上のPHPのアドバイザリ、IPA、国家サイバー統括室、PHP Foundationのページには、調査環境から直接アクセスできませんでした。これらの文言は検索エンジンの抜粋によるもので、少なくとも2つの報道と照合しています。Xの投稿時刻は投稿IDから算出し、Impress Watchと共同通信が伝えた時刻と一致することを確かめました。CISAのカタログ(バージョン2026.10.21)は私たちが直接確認しました。国内の集計は、既存のデータセットからスキャンせずに行った当サイト独自のものです。確認時点で、この脆弱性に関するJPCERT/CCの注意喚起は見つかりませんでした。図1のスクリーンショットは読者から提供されたもので、首相の公的な発信を報道し引用するために掲載しています。プロフィール写真とXのロゴは除きました。

記事一覧へ →Read in English ↗