
CVE-2026-17106 (CopyEscape) の機能的な PoC: docker cp における TOCTOU レースにより、Docker ホストへの任意書き込みが可能になります。Docker ラボ + inotify モニター + LD_PRELOAD。macOS では無害、Linux では破壊的な亜種です。
CopyEscape (CVE-2026-17106) の教育的な Proof of Concept。
docker cpにおける TOCTOU 競合状態の脆弱性で、悪意のあるコンテナが Docker ホスト上に任意のファイルを書き込むことを可能にします。この脆弱性を発見・文書化した masasron 氏によるブログ記事 "CopyEscape: Taking Over Docker Hosts with docker cp" に触発されたものです。
CVE-2026-17106 は、docker cp <contenedor>:<ruta> <destino> の際に Docker デーモンが生成する tar ファイルの展開処理における競合状態 (TOCTOU) の脆弱性です。Docker CLI 29.6.1 以前に影響し、29.7.0 以降で修正されています。このバグはデーモンではなく CLI (ホスト上での tar 展開) に存在します。
CopyEscape と名付けられたこの攻撃チェーンは、特別な権限を持たない実行中コンテナのみを起点として、攻撃者が以下を行えることを示します:
LD_PRELOAD ライブラリを使って、コンテナ内でそれを開くあらゆるプロセスに対して、実際のディレクトリ /watched/file.txt/ を通常のファイルとして偽装する。このライブラリは open、openat、openat2、fopen、stat、lstat、fstatat、statx および 64 系の各バリアントをインターセプトする。aaa.txt を維持し、デーモンが tar を生成するのに十分な時間をかけさせ、モニタが競合に勝てるようにする。inotify でデーモンのアクセスを監視し、競合ウィンドウの間に、escape/ サブディレクトリを、ロールバック付きの 2 つのアトミックな rename(2) によってホストのターゲットへの絶対 symlink (macOS では /Users/<usuario>、Linux では /usr/bin) に置き換える。escape と、それに続く子エントリ escape/<basename> が含まれる。脆弱な CLI はクライアント側で symlink を作成し、その 経由で 子エントリを展開するため、ユーザーが選んだ宛先の外に書き込む。結果として、docker cp を実行するプロセスの権限でホスト上への任意ファイル書き込みが可能になります。Linux バリアントは /usr/bin/runc を上書きし、次回の docker run / exec でホスト上でのroot によるコード実行を達成します。
このリポジトリには、厳密に教育・防御目的の資料が含まれています。
runc を破壊します。検証済みのバックアップがある使い捨て VM でのみ使用してください。この PoC には無害な macOS バリアント (~/pwnd をマーカーとして書き込む) が同梱されており、その唯一の目的は再現可能なデモンストレーションとして機能することです。
┌─────────────────────────────────────────────────────────────────────┐
│ 1. LD_PRELOAD camufla /watched/file.txt como archivo regular │
│ open/openat/openat2/fopen/stat/lstat/fstatat/statx → decoy │
│ docker exec ... cat /watched/file.txt → "top-level file" │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 2. Layout real dentro del contenedor │
│ /watched/file.txt/ │
│ ├── aaa.txt (16 MiB de 'B', cebo lento) │
│ ├── escape/ (directorio real inicial) │
│ ├── escape/<basename> (payload: runc o pwnd) │
│ └── .swap-escape (symlink → /Users/<usr> o /usr/bin) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 3. docker cp pide tar al daemon │
│ daemon recorre /watched/file.txt/ y abre aaa.txt │
│ inotify detecta IN_OPEN sobre aaa.txt │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 4. Monitor pivot: dos rename(2) atomicos con rollback │
│ escape/ → .old-escape/ (backup) │
│ .swap-escape → escape/ (symlink al target del host) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 5. Tar resultante contiene: │
│ escape → /Users/<usr> (symlink absoluto) │
│ escape/<basename> (entrada hija que atraviesa el symlink) │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 6. CLI vulnerable extrae symlink + hija │
│ crea escape → /Users/<usr> en el destino local │
│ extrae escape/<basename> A TRAVES del symlink │
│ escribe /Users/<usr>/<basename> en el host │
└──────────────────────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ macOS: ~/pwnd con "COPYESCAPE_MACOS_DEMO" (demo inocua) │
│ Linux: /usr/bin/runc sobrescrito (RCE como root) │
└─────────────────────────────────────────────────────────────────────┘
この PoC は macOS での実行を想定して構築されており、以下が必要です:
curl、tar、git、docker が PATH に含まれていること。alpine:3.21 をダウンロードするためのインターネット接続。HOME が /Users/<usuario> の形式であること。macOS バリアントでは root は不要です。Linux バリアントでは root と使い捨て VM が必要です。
ランナー run-poc.sh は、Docker 公式サイト (download.docker.com) から脆弱な CLI 29.6.1 のバイナリを自動的にダウンロードします。システムの Docker Desktop インストールは変更しません。現在のデーモンを使用しますが、docker cp 操作にはダウンロードしたバイナリを呼び出します。
POC CopyEscape (CVE-2026-17106)/
├── README.md ← Este archivo
├── LICENSE ← Licencia MIT
├── .gitignore
├── Dockerfile ← Dockerfile unificado (macos | linux)
├── entrypoint.sh ← Arranque del monitor sin LD_PRELOAD
├── monitor.c ← Monitor inotify unificado (una sola fuente)
├── watched_preload.c ← LD_PRELOAD con cobertura ampliada
└── run-poc.sh ← Runner robusto con reintentos
ディレクトリ
cli-vulnerable/(ダウンロードされた CLI 29.6.1) は.gitignoreでリポジトリから除外されます。 ランナーは存在しない場合、自動的にダウンロードします。
cd poc-v2
./run-poc.sh
ランナーは次の処理を行います:
copyescape-macos-pocv2:local をビルドする。COPYESCAPE_HOST_HOME=$HOME を設定した準備済みコンテナを起動する。/watched/file.txt が読み取り可能になるのを待つ。docker cp を実行する。~/pwnd の作成を検証する。./run-poc.sh 10 # 10 intentos (la carrera es probabilistica)
./run-poc.sh --cleanup # 5 intentos y borra la imagen al final
[run-poc] CLI vulnerable: 29.6.1
[run-poc] Intento 1/5 (contenedor copyescape-pocv2-NNNN-RRRR)
[run-poc] /watched/file.txt lee: top-level file
[run-poc] docker cp (CLI 29.6.1)...
[run-poc] EXITO en intento 1: se creo /Users/<tu-usuario>/pwnd
Contenido del marcador: COPYESCAPE_MACOS_DEMO
[run-poc] Logs del monitor:
[monitor] plataforma=macos target_dir=/Users/<tu-usuario> target_file=/Users/<tu-usuario>/pwnd quiet_ms=400 trigger=aaa.txt
[monitor] pivoted /watched/file.txt/escape -> /Users/<tu-usuario>
[run-poc] Resumen: 1 exito en 1 intento(s).
rm -f -- "$HOME/pwnd"
rm -rf -- poc-v2/file.txt
docker image rm copyescape-macos-pocv2:local
攻撃チェーンが完了すると、以下の要素が証拠となります:
アイデアと戦略は masasron 氏のものです: tar 生成プロセスに対する TOCTOU 競合、LD_PRELOAD による偽装、16 MiB の囮、2 つの rename(2) によるピボット、そして escape/ + .swap-escape 構造。この設計がなければ、この書き直しは存在しませんでした。概念的なスキームは同一です。
約 15〜20 % は文字通り一致する行 (短いユーティリティとレイアウト定数) です。残りは新しいコードです。
docker cp を実行しない。Linux では絶対に root として実行しない。docker cp の展開中に宛先内で絶対 symlink が作成されていないか監視するか、デーモン側で symlink 配下の子エントリを含む tar をブロックする。MIT ライセンスの下で配布されています。詳細については LICENSE ファイルを参照してください。
| リソース | 期待される証拠 |
|---|
~/pwnd | 内容が COPYESCAPE_MACOS_DEMO\n のファイル |
poc-v2/file.txt/ | escape -> /Users/<tu-usuario> (symlink) と aaa.txt (16 MiB) を含むディレクトリ (ファイルではない) |
docker logs <contenedor> | 行 pivoted /watched/file.txt/escape -> /Users/<tu-usuario> |
CLI 29.7.2 (修正版 CLI での docker cp) | マーカーは作成されない。PoC は失敗する |
| ファイル | 元の行数 | 新しい行数 | 変更内容 |
|---|
watched_preload.c | 89 | 257 | 書き直し: openat2、fstatat、statx、stat64、lstat64、open64、fopen64 のラッパー、スレッドセーフなコンストラクタ、dlsym == NULL に対するガード、O_TMPFILE |
monitor.c (macos + linux) | 259 + 233 = 492 (重複) | 344 (統合) | -DCOPYESCAPE_TARGET_* で書き直し・統合。try_pivot のロールバック、設定可能な exit_quiet_ms とトリガー、堅牢な検出 |
entrypoint.sh | 3 | 5 | unset の代わりに env -u LD_PRELOAD |
Dockerfile | 22 | 43 | 統合、digest 固定、--platform、-Werror なし、-static、LABEL |
demo-macos.sh → run-poc.sh | 88 | 166 | 書き直し: 競合の再試行、失敗時のログ、--cleanup、バイナリへの検証委任 |