記事一覧に戻る
セキュリティ2026年7月18日21分で読めます

パッチは入口しか塞がない — WordPress Core 認証不要RCEから考えるコンテナ横展開対策

2026年7月17日に公開された WordPress Core の認証不要RCE(wp2shell)は、認証不要・プラグイン不要・Core本体という3条件が揃った稀な脆弱性です。ただし本稿の主題はパッチではありません。RCEが通った瞬間、攻撃者はコンテナの中にいます。ホストへ脱出しなくても、環境変数の読み取り、内部ネットワークへの横展開、クラウドメタデータからのIAM権限奪取、KubernetesのServiceAccount悪用は成立します。docker-compose.yml と Kubernetes マニフェストの確認箇所、bind mount の :ro、IMDSv2到達性とIAM最小権限の二段階、RBACの過剰権限まで、侵入後の被害範囲を設計で決めるための確認手順を整理します。

WordPressRCEDockerKubernetesコンテナセキュリティIMDSv2IAMRBACbind mountassume breach

何が起きたか

2026年7月17日、WordPress 7.0.2 がリリースされました。Critical 1件、High 1件を修正するセキュリティリリースで、深刻度を理由に WordPress.org 側で強制自動更新が有効化されています。同時に 6.9.5、6.8.6 も公開されました。

Critical の正体は、Searchlight Cyber の Adam Kues 氏が報告した wp2shell — WordPress Core 本体の認証不要RCE(pre-authentication RCE)です。

この脆弱性の異常さは、3つの条件が同時に揃っている点にあります。

条件意味
認証不要(pre-auth / unauthenticated)ログイン不要。URLを知っていればネット上の誰でも撃てる
RCEサーバー上で任意コード実行。実質サーバーの乗っ取り
Core本体・プラグイン不要該当バージョンなら全サイトが例外なく対象。切り分け不要

経路は REST API のバッチエンドポイント /wp-json/batch/v1(?rest_route=/batch/v1 からも到達可)。技術詳細と PoC は深刻度を考慮して非公開ですが、修正パッチの差分を解析すれば攻撃コードは作れます。攻撃コードが公開される前が、最も安全に更新できる時間です。

対処: 6.9系は 6.9.5 へ、7.0系は 7.0.2 へ。6.8系にも 6.8.6 が出ています。利用中のブランチの最新セキュリティリリースへ更新してください。強制自動更新が効いているとはいえ、自動更新を無効化している企業サイトは手動対応が必須です。

ここからが本題 — パッチは入口しか塞がない

更新の話はここまでです。本稿で書きたいのは、その先です。

RCEが通った瞬間、攻撃者はあなたのコンテナの中でシェルを持っています。パッチは入口を塞ぐ対策であって、すでに入られた後の横展開を止める対策ではありません。層が違うので、片方だけでは止まりません。

Core本体の脆弱性は自分では防げません。しかし侵入後に何が奪われるかは、自分の設計で決まります。ここが本稿の主張です。

攻撃者が辿るルートは一本道ではありません。ホストへ脱出しなくても横展開は成立します。

RCE
 └─ コンテナ内でコード実行
     ├─ 環境変数・設定ファイル・秘密情報を読む
     ├─ 内部ネットワークへ横展開する
     ├─ クラウドやKubernetesの権限を奪う
     └─ 危険なマウントや特権設定からホストへ到達する

確認すべきは3つです。

  1. コンテナからホストへ出られるか
  2. コンテナから内部サービスやクラウド基盤へ届くか
  3. 横展開に使える認証情報や権限が与えられていないか

Docker: docker-compose.yml で確認する

まず設定ファイルを grep します。

grep -nE 'docker\.sock|privileged|cap_add|network_mode|pid:|user:|volumes:' docker-compose.yml

見るべき箇所:

危険な記述なぜ危ないかあるべき姿
/var/run/docker.sock のマウントコンテナ内からホストのDockerを操作可能。即ホスト奪取公開コンテナには絶対に付けない
privileged: trueほぼホストと同等。脱出は容易使わない
cap_add: [SYS_ADMIN, SYS_PTRACE]特権相当のcapabilitycap_drop: [ALL] して必要分だけ
network_mode: hostネットワーク分離が消え、内部サービスに到達通常のbridge
pid: hostホストプロセスが見える。認証情報の窃取使わない
user: の記述なしデフォルトroot実行user: "1000:1000"
environment: に秘密の直書きシェル1本で env すれば全部読めるシークレット管理へ分離
- /:/host、- /etc:/etcホストFSの過剰マウント必要なパスだけ、:ro を付ける

安全側の型:

services:
  app:
    user: "1000:1000"
    read_only: true
    tmpfs:
      - /tmp
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true

bind mount と :ro — 現場で一番ありがちな穴

ここは実運用で必ず引っかかる部分なので、独立して書きます。

「毎回ビルドしたくないから、ソースを bind mount して git pull → restart で回す」。よくある構成で、それ自体は合理的です。bind mount が悪いのではありません。問題は RW であることと、マウント範囲が広いことです。

コードを読み込むだけなら書き込み権限は要りません。

volumes:
  - ./src:/app/src:ro

:ro を足すだけで、RCEが通ってもホスト側のコードを書き換えられなくなります。デプロイ手順は1ミリも変わらず、攻撃者の永続化ルートだけが消えます。

より危険なのは - .:/app のようにディレクトリを丸ごとマウントしている場合です。同じ階層の .env と .git が一緒にコンテナへ入ります。

  • .env — DB認証情報、APIパスワード、Webhook URL がファイルとして読める
  • .git — コミット履歴、削除済みファイル、リモートURL、そして .git/hooks/。構成によっては認証情報の痕跡が残っている場合もあります

.git/hooks/post-merge に仕込まれると、運用者が次にホスト上で git pull した瞬間に任意コードが走ります。RWマウントがコンテナからホストへ影響を残す、現実的な経路の一つです。

対策はマウント範囲を絞ること。bind mount は .dockerignore を見ないので、compose 側で指定する必要があります。

volumes:
  - ./src:/app/src:ro
  - ./config:/app/config:ro

アプリがマウント先に書き込む場合(ログ、__pycache__、build成果物)だけ、別ボリュームに逃がします。

「書いてある設定」ではなく「動いている状態」を見る

compose ファイルが現状と一致している保証はありません。稼働中コンテナの実態を確認します。

docker inspect $(docker ps -q) --format '{{.Name}} Priv={{.HostConfig.Privileged}} User={{.Config.User}} PID={{.HostConfig.PidMode}} Net={{.HostConfig.NetworkMode}}'

マウントの実態(RW=true と .git/.env に注目):

docker inspect $(docker ps -q) --format '{{.Name}}{{range .Mounts}} {{.Source}}->{{.Destination}} RW={{.RW}}{{end}}'

capability の実効値:

docker inspect $(docker ps -q) --format '{{.Name}} Add={{.HostConfig.CapAdd}} Drop={{.HostConfig.CapDrop}}'

メタデータ — 「届くか」と「権限が強いか」の二段階

ここは二段階で考えます。

コンテナからメタデータへ到達できる
        +
インスタンスロールの権限が強い
        ↓
AWSアカウント内へ横展開

確認事項は3つです。

確認項目意味
IMDSv1が無効か単純GETで認証情報が取れる状態でないか
コンテナからIMDSv2へ到達できるかトークン取得経由でも届いてしまわないか
取得できるIAMロールが最小権限か奪われた場合に何ができるか

まず単純GET(IMDSv1の生存確認):

docker exec <container> curl -s -m 2 http://169.254.169.254/latest/meta-data/iam/security-credentials/

ここで応答がなくても安心はできません。 IMDSv2経由で到達できるかを確認します。

docker exec <container> sh -c '
TOKEN=$(curl -sS -m 2 -X PUT \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \
  http://169.254.169.254/latest/api/token) &&
curl -sS -m 2 \
  -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/
'

ロール名が返った場合、そこで終わりにせず、そのロールで何ができるかまで確認します。

aws iam simulate-principal-policy \
  --policy-source-arn <ROLE_ARN> \
  --action-names \
    s3:ListAllMyBuckets \
    s3:GetObject \
    secretsmanager:GetSecretValue \
    ssm:GetParameter \
    ec2:DescribeInstances

EC2側の設定確認:

aws ec2 describe-instances \
  --instance-ids i-xxxxxxxx \
  --query 'Reservations[].Instances[].MetadataOptions'

まず HttpTokens: required として IMDSv1 を無効化します。

HttpPutResponseHopLimit: 1 にすると、一般的なブリッジ型コンテナから IMDSv2 へ到達しにくくできます。ただし、コンテナ内の正規ワークロードが IMDS を利用する構成では、AWS は hop limit 2 を案内しています。したがって、1 を一律の正解にはできません。

重要なのは、用途に応じて次を組み合わせることです。

  • コンテナから IMDS を利用しないなら、hop limit やネットワーク制御で到達させない
  • 利用するなら IMDSv2 を必須とし、取得できる IAM ロールを最小権限にする
  • 可能なら、インスタンス全体の強いロールをコンテナへ渡さず、ワークロード単位の権限を使う

実務上、メタデータへの到達制限と、奪われても大事故にならない IAM 権限の両方が必要です。 到達制限だけでは構成変更で崩れる可能性があり、最小権限だけでは不要な認証情報を攻撃者に渡すことになります。

Kubernetes: 同じ思想をマニフェストで

grep -rnE 'privileged|hostPath|hostNetwork|hostPID|automountServiceAccountToken|runAsUser|allowPrivilegeEscalation' ./manifests/

安全側の型:

spec:
  automountServiceAccountToken: false
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]

稼働中クラスタの実態:

kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" priv="}{.spec.containers[*].securityContext.privileged}{" hostNet="}{.spec.hostNetwork}{" sa="}{.spec.automountServiceAccountToken}{"\n"}{end}'

コンテナ乗っ取り後の防御は、4層で整理すると明確です。

防御層止めるもの
Security Context特権昇格、root実行、危険なcapability
Volume制限hostPath、ホストFS、秘密情報へのアクセス
RBACSecrets、Pod作成、exec、クラスタ操作
NetworkPolicyDB、Redis、メタデータ、他namespaceへの横展開

そして RBACの過剰権限。これがKubernetes版の docker.sock です。

kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa>

危険なのは secrets の get/list だけではありません。

  • pods/create
  • pods/exec
  • deployments/patch
  • daemonsets/create
  • serviceaccounts/token
  • roles / rolebindings の作成・変更

secretsを読めなくても、Podの作成、既存Podへのexec、RoleBindingの変更が許可されていれば、別の経路で権限を奪われます。 攻撃者はSecretsを直接読めなくても、強いServiceAccountを指定したPodを作れるなら権限を奪えます。RBACは「Secretsが読めるか」だけでなく、**「より強いPodや権限を作れるか」**まで確認します。

NetworkPolicy はデフォルト deny から始め、DB や Redis、他 namespace への到達先を明示的に絞ります。クラウドメタデータへの通信は、利用する CNI がリンクローカルアドレスを制御できるか確認し、必要に応じて CNI 固有のポリシー、ノード側のファイアウォール、クラウド側の Pod 単位 ID を組み合わせます。

Docker と Kubernetes の対応関係

観点DockerKubernetes
ホスト奪取ルートdocker.sock マウント過剰なRBAC(secrets、pods/create、pods/exec)
特権privileged、cap_addprivileged、allowPrivilegeEscalation
ホストFSbind mount の RW・広範囲hostPath
分離の破壊network_mode: host、pid: hosthostNetwork、hostPID
認証情報env直書き、.env マウントSAトークン自動マウント
クラウド横展開メタデータ到達 + 強いIAMロールノードメタデータ到達 + 強いIAMロール。CNI、ノード側FW、Pod単位IDで制御

結論

WordPress Coreの脆弱性そのものを、利用者が事前に修正することはできません。

しかし、RCEが成立した後の被害範囲は設計で変えられます。

コンテナ内でコードを実行されても、ホストへ出られない。内部のDBや管理系サービスへ届かない。クラウドメタデータから強いIAM権限を奪えない。KubernetesのServiceAccountからクラスタ全体へ進めない。その状態を先に作っておく必要があります。

:ro を一つ足す。docker.sock を外す。rootで動かさない。不要なcapabilityを落とす。メタデータへの通信を制限する。IAMとRBACを最小権限にする。ServiceAccountトークンの自動マウントを切る。NetworkPolicyで公開Podから内部サービスへの到達先を絞る。

どれも、RCEが公表されてから初めて考える対策ではありません。

パッチは侵入口を塞ぐ対策です。コンテナ、IAM、RBAC、ネットワークの分離は、侵入された後に被害を封じ込める対策です。

脆弱性をゼロにすることはできなくても、一つの公開コンテナの侵害を、ホストやクラウドアカウント、クラスタ全体の侵害にしないことはできます。

侵入を防ぐだけではなく、侵入されても広げさせない。今回確認すべきなのは、そこです。


本稿は特定製品の設定を推奨するものではなく、一般的なコンテナ構成の確認手順として整理したものです。

キーワード

  • WordPress
  • RCE
  • Docker
  • Kubernetes
  • コンテナセキュリティ
  • IMDSv2
  • IAM
  • RBAC
  • bind mount
  • assume breach
A

Aikido Community Japan

公開日: 2026/7/18

最終更新: 2026/7/18

シェア:

ニュースを購読

セキュリティの最新情報をお届けします。

Aikido Community Japan からのお知らせの受信に同意します。詳しくはプライバシーポリシーをご確認ください。