ApostropheCMS import-export tar guard misses './..' because it is a literal '../' substring test
Resumen
La protección de extracción tar agregada a @apostrophecms/import-export para GHSA-mwxc-m426-3f78 consiste en una comprobación literal de subcadena para ../, y ./.. no contiene dicha subcadena. Por lo tanto, una entrada llamada ./.. pasa la validación, path.join la normaliza hacia el directorio padre del directorio de exportación, y el fs.WriteStream resultante abre un directorio y emite EISDIR sin ningún listener error asignado, lo que finaliza el proceso de Node.
Confirmado en la rama por defecto HEAD 1bd5d0a7a6fe880836fa75adad6964f1c577d640, versión de paquete 3.6.2.
Detalles
packages/import-export/lib/formats/gzip.js, en el manejador entry del flujo tar dentro de extract(filepath, exportPath):
const name = header.name.replace(/\\/g, '/');
if (name.includes('../')) {
// skipped
}
...
stream.pipe(fs.WriteStream(path.join(exportPath, name)));
Existen dos problemas distintos en esas cuatro líneas. La protección verifica el nombre de entrada sin procesar buscando una subcadena en lugar de resolver la ruta combinada y requerir contención dentro del directorio, y el flujo de escritura carece de un manejador error, por lo que cualquier error del flujo se convierte en un evento 'error' no controlado.
Para alcanzarlo solo se requiere una cuenta con permisos de importación. packages/import-export/modules/@apostrophecms/import-export-page/index.js expone post.importExportImport, que llama a lib/methods/import.js; dicho manejador requiere req.user y luego llama a format.input(exportPath), que para una subida .gz consiste en gzip.js invocando extract() sobre el archivo subido.
PoC
Esto no es un exploit de extremo a extremo. A continuación se presentan cada una de las tres primitivas de las que depende la caída, ejecutadas individualmente, junto con un rastreo de la ruta a nivel de código fuente. La cadena desde post.importExportImport hasta extract() fue analizada en el código, no observada en ejecución.
La protección no detecta la carga útil:
node -e "console.log('./..'.includes('../')); console.log('..'.includes('../')); console.log('a/./..'.includes('../'));"
false
false
false
path.join luego la resuelve fuera del directorio de exportación:
node -e "const path=require('path'); const exportPath='/tmp/exportRoot'; console.log(path.join(exportPath,'./..'));"
/tmp
Y un flujo de escritura abierto sobre un directorio, sin un listener error, detiene el proceso en lugar de devolver un error en la solicitud:
node -e "const fs=require('fs'); const s=fs.createWriteStream('/tmp'); s.write('x'); s.end(); setTimeout(()=>console.log('still alive'),500);"
events.js:291
throw er; // Unhandled 'error' event
^
Error: EISDIR: illegal operation on a directory, open '/tmp'
Emitted 'error' event on WriteStream instance at:
at internal/fs/streams.js:361:12
at FSReqCallback.oncomplete (fs.js:156:23) {
errno: -21,
code: 'EISDIR',
syscall: 'open',
path: '/tmp'
}
El frame events.js:291 muestra que la salida provino de la línea de Node 14 presente en el entorno de auditoría. El comportamiento ante eventos error no manejados no es específico de una versión, aunque vale la pena volver a probar el mensaje exacto en las versiones de Node compatibles.
Confirmación en el código fuente sin ejecutar nada:
grep -n "includes('../')" packages/import-export/lib/formats/gzip.js
grep -n "WriteStream" packages/import-export/lib/formats/gzip.js
Impacto
Un usuario autenticado que pueda acceder a la interfaz de importación puede subir un archivo manipulado y provocar la detención del proceso de Node que lo gestiona. Cada worker que procese dicho archivo caerá de la misma manera.
Solución sugerida
Resolver la ruta de destino, exigir contención y asociar un manejador de errores:
const base = path.resolve(exportPath);
const target = path.resolve(path.join(exportPath, name));
if (target !== base && !target.startsWith(base + path.sep)) {
stream.on('end', next);
stream.resume();
return;
}
if (header.type === 'directory') {
fsp.mkdir(target).then(next).catch(reject);
} else {
const out = fs.WriteStream(target);
out.on('error', reject);
stream.pipe(out);
stream.on('end', next);
}
El manejador de errores se justifica de forma independiente. Cualquier entrada tar que coincida con el nombre de un directorio existente tiene el mismo efecto, implique o no un salto de directorio.
Por qué esto no es GHSA-mwxc-m426-3f78
La verificación name.includes('../') es la mitigación publicada para GHSA-mwxc-m426-3f78 / CVE-2026-32731. Detiene los nombres que contienen la secuencia literal ../. Sin embargo, no canoniza la ruta unida, por lo que ./.. y un simple .. siguen pasando, y no corrigió la falta del listener error. Este informe trata sobre lo que la prueba de subcadena deja desprotegido.
Se utilizó asistencia de IA para la auditoría de código y la redacción. Las líneas exactas de la protección y de WriteStream en packages/import-export/lib/formats/gzip.js en el HEAD indicado se verificaron manualmente, la ruta de importación desde el módulo de página a través de lib/methods/import.js hasta el manejador de entradas fue rastreada manualmente, y los tres comandos de una sola línea de Node se ejecutaron y copiaron de forma textual.
Qué hacer
Actualice @apostrophecms/import-export a la versión 3.7.0 o superior.
Software afectado
Haz clic en una tecnología para ver todas sus vulnerabilidades.
| Plataforma | Paquete | Versiones vulnerables | Corregida en |
|---|---|---|---|
| Paquete de npm | @apostrophecms/import-export | < 3.7.0 | 3.7.0 |
Referencias
- https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-97wv-p4xx-c7mg
- https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
- https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
- https://github.com/apostrophecms/apostrophe
- https://github.com/apostrophecms/apostrophe/releases/tag/@apostrophecms/import-export@3.7.0
- Vector CVSS
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N- Debilidad (CWE)
CWE-22- Plataforma
- npm
- Paquete
@apostrophecms/import-export- Publicada
- 20.10.2026
- Actualizado
- 20.10.2026 21:35
Fuentes y licencias · Editado o traducido por DockRay; se mantienen los derechos y las licencias de las fuentes.
OSV / GitHub Advisory Database - GHSA-97wv-p4xx-c7mg - CC BY 4.0
Historial de vulnerabilidades: @apostrophecms/import-export
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
- 4
- Primera → última
- 03.2026 10.2026
- Sin corrección
- 0
- Explotada en ataques
- 0
Distribución por gravedad
- Crítica 1
- Media 3
Divulgaciones por año
- 2026: 4
-
ApostropheCMS import-export tar guard misses './..' because it is a literal '../' substring test esta entrada
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.