
デフォルトの非特権 Kubernetes Pod が、共有コンテナイメージレイヤーを介して Dirty Frag Linux カーネルページキャッシュ破損脆弱性を悪用し、Amazon EKS 上でノードレベルのコード実行を達成できることを実証する Proof-of-Concept です。
中核となる攻撃プリミティブは次のとおりです: 攻撃者が制御するコンテナとイメージレイヤーを共有する特権 DaemonSet は、コンテナエスケープのために武器化できる。この PoC は具体例として kube-proxy を使用していますが、この手法はクラスター上の任意の特権ワークロードに一般化できます。
Amazon EKS (カーネル 6.12.80) で検証済み — 非特権 Pod が特権 kube-proxy DaemonSet を介してホストファイルシステムに [*] success を書き込みます:

免責事項: このリポジトリは教育および防御目的でのみ公開されています。所有しているシステム、または明示的なテスト許可を得たシステムでのみ使用してください。
Dirty Frag (CVE-2026-43284) は、xfrm/ESP 受信パスにおける Linux カーネルのページキャッシュ破損脆弱性です。影響を受けるパスでは、esp_input() が frag_list を持たない非線形 skb に対して skb_cow_data() をスキップできるため、crypto_authenc_esn_decrypt() が splice() を介して到達したページキャッシュページに攻撃者が制御する 4 バイトのデータを格納できるようになります。
ディスク上のファイルは変更されません。破損したバイトはカーネルページキャッシュ内に存在し、同じキャッシュされたファイルページを後から読み取るプロセスによって観測されます。
元の脆弱性の詳細については、V4bel/dirtyfrag を参照してください。
この攻撃は、Kubernetes クラスターで一般的に共存する 3 つの特性を悪用します:
privileged: true、hostNetwork: true、広範なケーパビリティなど) を持つ DaemonSet を実行し、イメージからバイナリを定期的に実行します。これらの条件が揃うと、非特権 Pod が共有イメージレイヤー内のバイナリを破損させ、同じノード上の特権 DaemonSet がその破損したバイナリを昇格された特権で実行してしまい、完全なノードレベルのコード実行を達成します。
脆弱性のターゲットは kube-proxy に限定されません。 攻撃者が制御するイメージとレイヤーを共有するコンテナイメージを持つ任意の特権 DaemonSet (監視エージェント、CNI プラグイン、ログコレクター、セキュリティエージェントなど) が実行可能なターゲットです。
このプロジェクトは、Copy Fail Kubernetes PoC で文書化された Kubernetes 悪用モデルに触発されていますが、異なるカーネルプリミティブを使用しています。
攻撃チェーンには 3 つの段階があります: ページキャッシュ破損、コンテナ間伝播、特権実行。
PoC バイナリは、非特権コンテナから次のシーケンスを実行します:
unshare(CLONE_NEWUSER | CLONE_NEWNET) で新しいユーザーおよびネットワーク名前空間に入ります。splice() と細工された ESP 入力を使用して、脆弱なカーネルパスをトリガーします。ターゲットファイルへの書き込み権限は不要です。ディスク上のファイルは変更されません — メモリ内のページキャッシュのみが破損します。
コンテナランタイムは、カーネルページキャッシュを介して overlay の下位レイヤーからの読み取りを提供します。PoC コンテナと kube-proxy が同じ下位レイヤーファイルを共有する場合、両方が同じキャッシュされたページを観測します。
このリポジトリの EKS イメージは、以下から構築されています:
public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023
このベースは、検証環境で使用された EKS kube-proxy ユーザースペースツールチェーンレイヤーに一致するように選択されています。
kube-proxy が次にパッチされた iptables ファミリーバイナリを実行すると、カーネルは破損したキャッシュページをロードします。PoC ペイロードはホストルートデバイスをマウントし、/root/res にマーカーファイルを書き込みます。
期待されるマーカー内容は次のとおりです:
[*] success
┌──────────────────────────────┐ ┌────────────────────────┐ ┌──────────────────────────┐
│ PoC Pod │ │ カーネルページキャッシュ│ │ kube-proxy DaemonSet │
│ 非特権コンテナ │ │ │ │ 特権コンテナ │
│ │ │ │ │ │
│ 1. unshare user+net ns │ │ │ │ │
│ 2. xfrm SA をインストール │ │ │ │ │
│ 3. splice ターゲットバイナリ│────▶│ 共有レイヤーバイナリ │────▶│ パッチ済みバイナリを実行│
│ ESP パス経由 │ │ ページキャッシュパッチ │ │ ペイロードがノードレベル│
│ │ │ │ │ 特権で実行 │
└──────────────────────────────┘ └────────────────────────┘ └──────────────────────────┘
GKE および ACK クラスターでテストしました。すべて失敗しました。
Dirty Frag プリミティブは、新しいネットワーク名前空間内で CAP_NET_ADMIN を取得するためにユーザー名前空間の作成 (CLONE_NEWUSER) を必要とします。ACK と GKE はどちらも、異なるメカニズムでこれをノードレベルでブロックします:
user.max_user_namespaces=0) により、非特権ユーザー名前空間の作成が完全に防止されます。--seccomp-default フラグで有効化) が、名前空間制限に関係なく unshare システムコールをブロックします。これは、ユーザー名前空間を必要とせず、3 つのプラットフォームすべてで正常に悪用される Copy Fail (CVE-2026-31431) との重要な違いです。
提供されている EKS バリアントは、存在する場合に次のバイナリをパッチします:
/usr/sbin/xtables-legacy-multi
/usr/sbin/xtables-nft-multi
これらのバイナリは、kube-proxy が使用する iptables ツールチェーンによって呼び出されます。正確なトリガータイミングは、ノードとサービスの調整アクティビティによって異なります。検証環境では、ペイロードは通常の kube-proxy 調整によってトリガーされました。
重要な注意事項:
ipset を呼び出します。デフォルトモード (iptables) は ipset を使用しません。xtables-legacy-multi、xtables-nft-multi) をターゲットにしてさまざまなプロキシモードをカバーしていますが、それらが呼び出されるかどうかはクラスター構成によって異なります。クラスターで kube-proxy が特権でない場合でも、攻撃原理は依然として有効です — ビルドできるベースイメージとイメージレイヤーを共有する、別の特権 DaemonSet を特定するだけです。
.
├── exploit/
│ └── dirtyfrag.c # xfrm/ESP ページキャッシュライター
├── payload/
│ ├── payload-eks.c # ホスト上で /root/res を書き込む nolibc ペイロード
│ └── nolibc/ # Linux nolibc ヘッダー
├── deploy/
│ └── poc-eks.yaml # 非特権 EKS Deployment マニフェスト
├── scripts/
│ ├── setup-eks.sh # EKS ノードにイメージをコピー、ビルド、インポート
│ ├── run-poc.sh # デプロイしてマーカーを確認
│ └── cleanup.sh # Pod、マーカー、キャッシュページ、ローカルイメージを削除
├── Dockerfile.eks # eks-distro-minimal-base-iptables に基づく EKS イメージ
├── Makefile # payload、exploit、Docker、nerdctl ビルドターゲット
└── .github/workflows/
└── docker-publish.yml # GHCR 公開ワークフロー
# ペイロード + エクスプロイトバイナリをビルド
make build-eks CC=x86_64-linux-gnu-gcc
# Docker イメージをビルド
make docker-build-eks
# デプロイ (非特権 Pod)
kubectl apply -f deploy/poc-eks.yaml
# ログを確認
kubectl logs deployment/dirtyfrag-poc-eks
# ノードでエスケープを検証
ssh ec2-user@<node-ip> "sudo cat /root/res"
# 期待される出力: [*] success
GitHub Actions ワークフロー (.github/workflows/docker-publish.yml) は、main へのプッシュまたはタグ作成時にイメージを GHCR に公開します。deploy/poc-eks.yaml の <owner> を、フォークを所有する GitHub ユーザーまたは組織に置き換えてください。
kubectl delete -f deploy/poc-eks.yaml --ignore-not-found
ssh ec2-user@<node-ip> "sudo rm -f /root/res"
kubectl delete pod -n kube-system -l k8s-app=kube-proxy --force
f4c50a4034e6) より前のすべてのバージョン。f4c50a4034e6 またはベンダーのバックポートを含む、Dirty Frag 修正を含むカーネルに更新します。esp4 と esp6 をブロックします。user.max_user_namespaces=0 を設定すると、この PoC が新しいネットワーク名前空間で CAP_NET_ADMIN を取得するのを防ぎます (これは ACK ではすでにデフォルトです)。privileged: true と広範なホストアクセスを避けます。モジュールブロックの例:
printf 'install esp4 /bin/false\ninstall esp6 /bin/false\n' | sudo tee /etc/modprobe.d/dirtyfrag.conf
sudo rmmod esp4 esp6 2>/dev/null || true
エクスプロイトコードは、MIT ライセンスに基づき V4bel/dirtyfrag から改作されています。
ペイロードコードは tgies/copy-fail-c から派生しており、LGPL-2.1-or-later OR MIT のデュアルライセンスです。
nolibc ヘッダーは Linux カーネルセルフテストインフラストラクチャからのものです。
| プロパティ | Copy Fail | Dirty Frag |
|---|
| CVE | CVE-2026-31431 | CVE-2026-43284 |
| カーネルパス | AF_ALG + splice() | xfrm/ESP + splice() |
| 名前空間要件 | 不要 | ユーザー名前空間が必要 |
| 使用する主なケーパビリティ | 初期コンテナでは不要 | 新しいネット名前空間内での CAP_NET_ADMIN |
| 関連モジュール | algif_aead | esp4 |
| 実用的な違い | AF_ALG ベクターがブロックされると失敗 | AF_ALG が利用できないが ESP/ユーザー名前空間が有効な場合でも有効 |
| プロパティ | 値 |
|---|
| プラットフォーム | Amazon Elastic Kubernetes Service (EKS) |
| ノードカーネル | 6.12.80-106.156.amzn2023.x86_64 |
| パッチ状態 | 修正前カーネル、f4c50a4034e6 が欠落 |
esp4 モジュール | ロード済み |
| ユーザー名前空間 | 有効 (user.max_user_namespaces=15030) |
| SELinux | Permissive |
| Seccomp | テストした Pod コンテキストでは Unconfined |
| ターゲット DaemonSet | kube-proxy |
| ターゲット特権 | privileged: true、hostNetwork: true |
| プロキシモード | iptables |
| マーカーパス | /root/res |
| プラットフォーム | 結果 | 理由 |
|---|
| Alibaba Cloud ACK | 失敗 | デフォルトのノードイメージで user.max_user_namespaces が 0 に設定されているため、非特権ユーザーは CLONE_NEWUSER unshare を使用できません。 |
| Google GKE | 失敗 | user.max_user_namespaces は 15426 ですが、kubelet が --seccomp-default を有効にしています。デフォルトの seccomp ポリシーが unshare システムコールを無効にします。 |