Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-2763-POC — Prueba de concepto de explotación para CVE-2026-2763, un use-after-free en el motor JavaScript de Mozilla, que demuestra una primitiva de escritura restringida de 1 bit que conduce a lectura/escritura fuera de límites. | Kitploit
Herramientas/GitHubGitHub/ppwwiinn/cve-2026-2763-poc
Forensia de MemoriaAnálisis de VulnerabilidadesExplotaciónSeguridad WebExplotación de Binarios
GitHubppwwiinn/cve-2026-2763-poc

CVE-2026-2763-POC

Prueba de concepto de explotación para CVE-2026-2763, un use-after-free en el motor JavaScript de Mozilla, que demuestra una primitiva de escritura restringida de 1 bit que conduce a lectura/escritura fuera de límites.

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio
216hace 6 mesesAún no revisado

Vulnerabilidad

La vulnerabilidad surge de la implementación de la sintaxis for-in.

El ámbito de un bucle for-in está representado por un par de bytecodes: JSOP::Iter y JSOP::EndIter.

root@kitploit:~
CASE(Iter) {
      MOZ_ASSERT(REGS.stackDepth() >= 1);
      HandleValue val = REGS.stackHandleAt(-1);
      JSObject* iter = ValueToIterator(cx, val); // [1]
      if (!iter) {
        goto error;
      }
      REGS.sp[-1].setObject(*iter); // [2]
    }

...

CASE(EndIter) {
      MOZ_ASSERT(REGS.stackDepth() >= 2);
      CloseIterator(&REGS.sp[-2].toObject());
      REGS.sp -= 2; // [3]
    }

En la entrada del ámbito, se llama a ValueToIterator en [1] para crear un PropertyIteratorObject. El intérprete lo almacena (lo empuja) en la pila del intérprete en [2]. Cuando el ámbito termina, el PropertyIteratorObject se elimina en [3].

root@kitploit:~
static PropertyIteratorObject* GetIteratorImpl(JSContext* cx, HandleObject obj,
                                               bool wantIndices,
                                               bool forObjectKeys) {
...
  PropertyIteratorObject* iterobj = CreatePropertyIterator(
      cx, obj, keys, supportsIndices, indicesPtr, cacheableProtoChainLength,
      ownPropertyCount, forObjectKeys); // [1]
  if (!iterobj) {
    return nullptr;
  }
  if (!forObjectKeys) {
    RegisterEnumerator(cx, iterobj->getNativeIterator(), obj); // [2]
  }
...
  return iterobj;
}

static inline void RegisterEnumerator(JSContext* cx, NativeIterator* ni,
                                      HandleObject obj) {
  ni->initObjectBeingIterated(*obj);

  // Register non-escaping native enumerators (for-in) with the current
  // context.
  ni->link(cx->compartment()->enumeratorsAddr());

  MOZ_ASSERT(!ni->isActive());
  ni->markActive();
}

Dentro de ValueToIterator, la creación del objeto se realiza como se muestra arriba. Después de asignar el objeto iterador en [1], se enlaza en una lista enlazada global en [2]. En este punto, la única referencia al PropertyIteratorObject se mantiene en la pila del intérprete.

root@kitploit:~
void js::CloseIterator(JSObject* obj) {
  if (!obj->is<PropertyIteratorObject>()) {
    return;
  }

  // Remove iterator from the active list, which is a stack. The shared iterator
  // used for for-in with null/undefined is immutable and unlinked.

  NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
  if (ni->isEmptyIteratorSingleton()) {
    return;
  }

  ni->unlink();

  MOZ_ASSERT(ni->isActive());
  ni->markInactive();

  ni->clearObjectBeingIterated();

  // Reset the enumerator; it may still be in the cached iterators for
  // this thread and can be reused.
  ni->resetPropertyCursorForReuse();
}

Cuando el ámbito termina, se invoca CloseIterator y desenlaza el iterador de la lista previamente enlazada, evitando cualquier acceso al PropertyIteratorObject fuera del ámbito.

Sin embargo, debido a que yield no se maneja correctamente, es posible ejecutar código fuera del ámbito del bucle antes de que se ejecute CloseIterator.

En esta situación, dado que la única referencia fuerte al PropertyIteratorObject está en la pila del intérprete, si se ejecuta yield y el generador devuelto tampoco tiene referencias, cuando el GC se ejecute, el PropertyIteratorObject puede ser recolectado (liberado).

root@kitploit:~
static bool SuppressDeletedPropertyHelper(JSContext* cx, HandleObject obj,
                                          Handle<JSLinearString*> str) {
  NativeIteratorListIter iter(obj->compartment()->enumeratorsAddr());
  while (!iter.done()) {
    NativeIterator* ni = iter.next();
    if (!SuppressDeletedProperty(cx, ni, obj, str)) { // [1]
      return false;
    }
  }

  return true;
}

Esta función se llama cuando se usa delete. Recorre la lista enlazada global y actualiza los objetos iteradores para tener en cuenta la propiedad eliminada. En [1], se puede invocar una devolución de llamada; si esto desencadena el GC, un PropertyIteratorObject sin referencias puede ser liberado. Su finalizador también liberará el objeto ni correspondiente, lo que conduce a un use-after-free (UAF).


Explotación

root@kitploit:~
static bool SuppressDeletedProperty(JSContext* cx, NativeIterator* ni,
                                    HandleObject obj,
                                    Handle<JSLinearString*> str) {
  ...
  // Check whether id is still to come.
  Rooted<JSLinearString*> idStr(cx);
  IteratorProperty* cursor = ni->nextProperty();
  for (; cursor < ni->propertiesEnd(); ++cursor) {
    idStr = cursor->asString();
    // Common case: both strings are atoms.
    if (idStr->isAtom() && str->isAtom()) {
      if (idStr != str) {
        continue;
      }
    } else {
      if (!EqualStrings(idStr, str)) {
        continue;
      }
    }
    ...
    cursor->markDeleted(); // [1]
    ni->markHasUnvisitedPropertyDeletion();
    return true;
  }

  return true;
}

Un NativeIterator puede tener su tamaño controlado mediante el número de propiedades del objeto objetivo, y cursor es un puntero a la ranura de propiedad que se marcará como eliminada. Dado que [1] efectivamente aplica un OR con un solo bit, esto produce una primitiva restringida: una escritura de 1 bit en una dirección arbitraria (alineada a 8 bytes).

NativeIterator no se asigna mediante el asignador del GC; en su lugar, se asigna desde js::MallocArena. Como resultado, no se puede proceder con un spray típico de objetos JS para colocar objetos controlados del heap del GC adyacentes a él.

root@kitploit:~
js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_(
    FrontendContext* fc, uint32_t codeLength, uint32_t noteLength,
    uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes) {
  auto size = sizeFor(codeLength, noteLength, numResumeOffsets, numScopeNotes,
                      numTryNotes);
  if (!size.isValid()) {
    ReportAllocationOverflow(fc);
    return nullptr;
  }

  // Allocate contiguous raw buffer.
  void* raw = fc->getAllocator()->pod_malloc<uint8_t>(size.value());
  MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0);
  if (!raw) {
    return nullptr;
  }

  // Constuct the ImmutableScriptData. Trailing arrays are uninitialized but
  // GCPtrs are put into a safe state.
  UniquePtr<ImmutableScriptData> result(new (raw) ImmutableScriptData(
      codeLength, noteLength, numResumeOffsets, numScopeNotes, numTryNotes));
  if (!result) {
    return nullptr;
  }

  // Sanity check
  MOZ_ASSERT(result->endOffset() == size.value());

  return result;
}

Un objeto útil asignado en js::MallocArena es ImmutableScriptData, que almacena bytecode de JS.

root@kitploit:~
static MOZ_ALWAYS_INLINE void InitElemArrayOperation(JSContext* cx,
                                                     jsbytecode* pc,
                                                     Handle<ArrayObject*> arr,
                                                     HandleValue val) {
  MOZ_ASSERT(JSOp(*pc) == JSOp::InitElemArray);

  // The dense elements must have been initialized up to this index. The JIT
  // implementation also depends on this.
  uint32_t index = GET_UINT32(pc); // [1]
  MOZ_ASSERT(index < arr->getDenseCapacity());
  MOZ_ASSERT(index == arr->getDenseInitializedLength());

  // Bump the initialized length even for hole values to ensure the
  // index == initLength invariant holds for later InitElemArray ops.
  arr->setDenseInitializedLength(index + 1); // [2]

  if (val.isMagic(JS_ELEMENTS_HOLE)) {
    arr->initDenseElementHole(index);
  } else {
    arr->initDenseElement(index, val);
  }
}

Este es el manejador de bytecode que se utiliza al inicializar un array de JS.

Lee un valor de 4 bytes del flujo de bytecode y lo usa para establecer el InitializedLength del array en [2].

Si la escritura de 1 bit se aplica al bytecode en [1], es posible establecer InitializedLength más allá de la capacidad real del array. A partir de ahí, la explotación continúa aprovechando primitivas de lectura/escritura fuera de límites (OOB).

Descargar herramienta