
CVE-2026-102282: アーカイブ展開時のSUID/SGID保持によるadm-zipのLPE(0.6.1で修正)
npm パッケージ adm-zip における不適切なパーミッション割り当て。keepOriginalPermission が有効な場合、展開時に信頼できない zip から setuid、setgid、スティッキービットが保持される可能性があり、攻撃者が用意したアーカイブから root 所有の setuid 実行ファイルをディスク上に設置できてしまう。すなわち ローカル権限昇格 である。
| パッケージ | adm-zip (npm) |
| エコシステム | Node.js |
| CVE | CVE-2026-102282 |
| アドバイザリ | GHSA-j5f4-cc29-5x44 |
| CWE | CWE-732 重要なリソースに対する不適切なパーミッション割り当て |
| CVSS v3.1 | 7.1 High — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| 影響を受けるバージョン | < 0.6.1 (最後に確認された影響バージョン: <= 0.6.0) |
| 修正済み | 0.6.1 (2026年9月11日) |
| 公開日 | メンテナアドバイザリ 2026年9月11日、GitHub Advisory Database 2026年9月29日 |
このリポジトリには、解説記事、依存バージョンチェッカー、安全な ビヘイビアテスト、および資格情報コンテキストを表示するだけの無害な 検証用バイナリを中心に構成された自己完結型の概念実証が含まれている。
0.6.1 より前のすべてのリリースが影響を受け、0.6.0 も含まれる。
| 範囲 | 状態 |
|---|---|
>= 0 かつ < 0.6.1 | 影響あり |
<= 0.6.0 | 最後に確認された影響バージョン |
>= 0.6.1 | 修正済み |
このバグに到達できるのは、すべて の条件が真である場合のみである:
extractAllTo()、extractAllToAsync()、または extractEntryTo() に keepOriginalPermission=true を渡している。デフォルトの展開ではこのフラグはオフであり、影響を受けない。非特権ユーザーとしての展開でもモードビットは適用され得るが、生成されるファイルは同じユーザーが所有するため、特権境界を越えることはない。
Unix のモードビットは、zip エントリの外部ファイル属性の上位 16 ビットに格納される。adm-zip はそれらのビットを展開先のパスにコピーする。
| 場所 | 役割 |
|---|---|
headers/entryHeader.js — fileAttr | 外部属性から Unix モードを読み取る |
adm-zip.js — extractAllTo、extractAllToAsync、extractEntryTo | keepOriginalPermission が true の場合に fileAttr を使用する |
util/utils.js — ファイル書き込みヘルパー内の chmodSync | 作成されたばかりのパスにそのモードを適用する |
adm-zip.js のディレクトリ展開 | 展開されたディレクトリに同じモードを適用する |
0.6.0 以前では、fileAttr はシフトされた属性を 0xfff (0o7777) でマスクする。このマスクは通常の rwx ビットとともに 3 つの特殊ビットを保持し、書き込みパス内で chmod の前に 0o7000 をクリアするものは何もない:
// headers/entryHeader.js (<= 0.6.0)
get fileAttr() {
return (_attr || 0) >> 16 & 0xfff;
}
0o4000)0o2000)0o1000)展開はアーカイブで制御されたモードをそのままファイルシステムに渡す:
// adm-zip.js
const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
filetools.writeFileTo(entryName, content, overwrite, fileAttr);
// util/utils.js
self.fs.chmodSync(path, attr || 0o666);
0.6.x の呼び出しシグネチャに注意: extractAllTo(targetPath, overwrite, keepOriginalPermission) は第 3 引数としてリテラルの boolean を取る。
{ keepOriginalPermission: true } のようなオプションオブジェクトを渡すと
暗黙的に false に変換され、脆弱性を実証する代わりにテストでマスクしてしまう。
このリポジトリの両方の PoC スクリプトは true を直接渡している。
修正はコミット 6a63c339 (v0.6.1) である。fileAttr は 9 つの rwx ビットのみを保持するようになった:
get fileAttr() {
return (_attr || 0) >> 16 & 0o777;
}
keepOriginalPermission=true の場合、展開は fileAttr を通じてそのモードを読み取り、chmod に渡す。0o7777 であるため、setuid、setgid、スティッキーが保持される。chmod が root として実行されると、新しいファイルは root 所有となり、setuid を持つことができる。後でより低い特権のユーザーが実行すると、ファイル所有者のコードが root として実行される。フラグをデフォルトのままにしておくと、展開はアーカイブのモードを適用せず、特殊ビットは書き込まれない。
すべては poc/ の下にある。同梱のペイロードは
verify.c であり、実 uid/gid と実効 uid/gid を表示し、
デフォルトモードでは実効 ID でシェルを開く無害なプログラムである。
永続化や隠蔽などは行わず、資格情報コンテキストを実証する以外の
ことは何もしない。
| ファイル | 目的 |
|---|---|
poc/verify.c | 無害な setuid 検証用ペイロード |
poc/build-archive.js | モード 0o4755 を持つエントリを含む zip にファイルをパックする |
poc/extract.js | 影響を受けるアプリと同様に keepOriginalPermission=true でアーカイブを展開する |
poc/run-demo.sh | エンドツーエンド実行: ビルド、root として展開、呼び出しユーザーとして実行 |
git clone <this repo> && cd CVE-2026-102282
npm install [email protected] # the version under test
sudo apt install gcc # if needed
./poc/run-demo.sh # uses /var/tmp/cve-2026-102282-demo
./poc/run-demo.sh /path/to/out # or a custom output directory
0.6.0 に対する期待される出力:
== 3. extract as root with keepOriginalPermission ==
adm-zip version: 0.6.0
extracted /var/tmp/cve-2026-102282-demo/exploit.zip into /var/tmp/cve-2026-102282-demo/extracted
== 4. result on disk ==
4755 -rwsr-xr-x root:root
== 5. run as the unprivileged user (user) ==
uid=1000(user) ...
[*] real uid=1000 gid=1000
[*] eff. uid=0 gid=0
[+] SUID active: effective uid 0 differs from real uid 1000
0.6.1 に対する期待される出力:
== 4. result on disk ==
755 -rwxr-xr-x root:root
== 5. run as the unprivileged user (user) ==
uid=1000(user) ...
[*] real uid=1000 gid=1000
[*] eff. uid=1000 gid=1000
[-] effective and real ids match: no SUID context
デモの注意点:
nosuid でマウントされて いない ファイルシステム上にある必要がある。スクリプトはそのマウントオプションを検出すると警告する。sudo 経由で実行すること)。scripts/behavior-test.js は、root もペイロードも
使わずに、インストールされた adm-zip のコピーを直接検証する:
0o4755 を持つインメモリの zip エントリを構築し、
keepOriginalPermission=true で展開し、setuid ビットが保持されたかどうかを
チェックする。
npm install [email protected] && node scripts/behavior-test.js # -> exit 1, VULNERABLE
npm install [email protected] && node scripts/behavior-test.js # -> exit 0, not vulnerable
信頼できない zip を root として keepOriginalPermission=true で展開する
アプリケーションやパイプラインにおけるローカル権限昇格。
この組み合わせは、Docker イメージビルド (デフォルトで root)、CI ジョブ、
および意図的にパーミッションを保持するインストールやデプロイのステップに
現れる。ファイルはモード保持コピー (cp -a、rsync) を通じてビルドから
抜け出し、後でランタイムユーザーによって実行される可能性がある。
公開された CVSS ベクターでは、ホストの機密性と完全性の両方が高である。 可用性はスコアに含まれない。攻撃はローカルである: 攻撃者は特権プロセスに よって zip を展開させ、その後、その結果をより低い特権で実行させる必要がある。
adm-zip を 0.6.1 以降にアップグレードする。
npm install adm-zip@^0.6.1
推移的依存関係を含め、解決されたコピーを確認する:
npm ls adm-zip
node scripts/check-version.js .
すべてのコピーがアップグレードされるまで:
keepOriginalPermission=true で展開しないこと。npm audit、npm ls adm-zip、またはこのリポジトリのチェッカー (下記) が、プロジェクトやロックファイル内の脆弱なコピーを特定する。scripts/behavior-test.js は、特定の環境が依然として脆弱な展開ビヘイビアを持つかどうかに答える。find /path/extracted -type f -perm /7000。信頼できないアーカイブから脆弱なバージョンを通じて展開されたツリー内でのヒットは、敵対的なものとして扱うべきである。プロジェクト、adm-zip のインストール、またはパッケージ自体の package.json をチェックする:
node scripts/check-version.js /path/to/project
node scripts/check-version.js /path/to/project/node_modules/adm-zip/package.json
node scripts/check-version.js --self-test