Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-2763-POC — Preuve de concept pour CVE-2026-2763, une utilisation après libération dans le moteur JavaScript de Mozilla, démontrant une primitive d'écriture contrainte à 1 bit menant à une lecture/écriture hors limites. | Kitploit
Outils/GitHubGitHub/ppwwiinn/cve-2026-2763-poc
Criminalistique MémoireAnalyse des VulnérabilitésExploitationSécurité WebExploitation de Binaires
GitHubppwwiinn/cve-2026-2763-poc

CVE-2026-2763-POC

Preuve de concept pour CVE-2026-2763, une utilisation après libération dans le moteur JavaScript de Mozilla, démontrant une primitive d'écriture contrainte à 1 bit menant à une lecture/écriture hors limites.

Voir le dépôt
2il y a 5 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Vulnérabilité

La vulnérabilité provient de l'implémentation de la syntaxe for-in.

La portée d'une boucle for-in est représentée par une paire de bytecodes : JSOP::Iter et 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]
    }

À l'entrée de la portée, ValueToIterator est appelé en [1] pour créer un PropertyIteratorObject. L'interpréteur le stocke ensuite (le pousse) sur la pile de l'interpréteur en [2]. Lorsque la portée se termine, le PropertyIteratorObject est retiré 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();
}

À l'intérieur de ValueToIterator, la création de l'objet est effectuée comme indiqué ci-dessus. Après l'allocation de l'objet itérateur en [1], il est lié à une liste chaînée globale en [2]. À ce stade, la seule référence au PropertyIteratorObject est détenue sur la pile de l'interpréteur.

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();
}

Lorsque la portée se termine, CloseIterator est invoqué et détache l'itérateur de la liste précédemment liée, empêchant tout accès au PropertyIteratorObject en dehors de la portée.

Cependant, comme yield n'est pas géré correctement, il est possible d'exécuter du code en dehors de la portée de la boucle avant que CloseIterator ne s'exécute.

Dans cette situation, puisque la seule référence forte au PropertyIteratorObject se trouve sur la pile de l'interpréteur, si yield est exécuté et que le générateur retourné n'est pas non plus référencé, alors lorsque le GC s'exécute, le PropertyIteratorObject peut être collecté (libéré).

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;
}

Cette fonction est appelée lorsque delete est utilisé. Elle parcourt la liste chaînée globale et met à jour les objets itérateurs pour tenir compte de la propriété supprimée. En [1], un rappel peut être invoqué ; si cela déclenche le GC, un PropertyIteratorObject non référencé peut être libéré. Son finaliseur libérera également l'objet ni correspondant, conduisant à une utilisation après libération (UAF).


Exploit

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 peut voir sa taille contrôlée via le nombre de propriétés de l'objet cible, et cursor est un pointeur vers l'emplacement de la propriété qui sera marquée comme supprimée. Comme [1] effectue un OU binaire sur un seul bit, cela donne une primitive contrainte : une écriture d'un bit à une adresse arbitraire (alignée sur 8 octets).

NativeIterator n'est pas alloué via l'allocateur du GC ; il est plutôt alloué depuis js::MallocArena. Par conséquent, vous ne pouvez pas procéder avec un spray d'objets JS typique pour placer des objets contrôlés du tas GC à proximité.

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 objet utile alloué dans js::MallocArena est ImmutableScriptData, qui stocke le bytecode 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);
  }
}

C'est le gestionnaire de bytecode utilisé lors de l'initialisation d'un tableau JS.

Il lit une valeur de 4 octets depuis le flux de bytecode et l'utilise pour définir la InitializedLength du tableau en [2].

Si l'écriture d'un bit est appliquée au bytecode en [1], il devient possible de définir InitializedLength au-delà de la capacité réelle du tableau. À partir de là, l'exploit procède en exploitant des primitives de lecture/écriture hors limites (OOB).

Télécharger l’outil