Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
Strumenti/GitHubGitHub/ppwwiinn/cve-2026-2763-poc
Memory ForensicsAnalisi delle VulnerabilitàExploitSicurezza WebBinary Exploitation
GitHubppwwiinn/cve-2026-2763-poc

CVE-2026-2763-POC

Proof-of-concept exploit per CVE-2026-2763, una use-after-free nel motore JavaScript di Mozilla, che dimostra una primitiva di scrittura vincolata a 1 bit che porta a lettura/scrittura fuori dai limiti.

Vedi Repository
2166 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Vulnerabilità

La vulnerabilità deriva dall'implementazione della sintassi for-in.

L'ambito di un ciclo for-in è rappresentato da una coppia di bytecode: JSOP::Iter e 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]
    }

All'ingresso nell'ambito, ValueToIterator viene chiamato in [1] per creare un PropertyIteratorObject. L'interprete quindi lo memorizza (lo inserisce) nello stack dell'interprete in [2]. Quando l'ambito termina, il PropertyIteratorObject viene rimosso in [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();
}

All'interno di ValueToIterator, la creazione dell'oggetto viene eseguita come mostrato sopra. Dopo aver allocato l'oggetto iteratore in [1], questo viene collegato a una lista collegata globale in [2]. A questo punto, l'unico riferimento al PropertyIteratorObject è mantenuto nello stack dell'interprete.

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

Quando l'ambito termina, viene invocato CloseIterator, che scollega l'iteratore dalla lista precedentemente collegata, impedendo qualsiasi accesso al PropertyIteratorObject al di fuori dell'ambito.

Tuttavia, poiché yield non viene gestito correttamente, è possibile eseguire codice al di fuori dell'ambito del ciclo prima che venga eseguito CloseIterator.

In questa situazione, poiché l'unico riferimento forte al PropertyIteratorObject si trova nello stack dell'interprete, se yield viene eseguito e il generatore restituito non è anch'esso referenziato, quando il GC viene eseguito il PropertyIteratorObject può essere raccolto (liberato).

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

Questa funzione viene chiamata quando viene usato delete. Attraversa la lista collegata globale e aggiorna gli oggetti iteratore per tenere conto della proprietà eliminata. In [1], può essere invocato un callback; se questo attiva il GC, un PropertyIteratorObject non referenziato può essere liberato. Il suo finalizzatore libererà anche il corrispondente oggetto ni, portando a un use-after-free (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 può avere la sua dimensione controllata tramite il numero di proprietà sull'oggetto target, e cursor è un puntatore allo slot della proprietà che verrà marcato come eliminato. Poiché [1] effettivamente esegue un OR su un singolo bit, questo produce una primitiva vincolata: una scrittura di 1 bit a un indirizzo arbitrario (allineato a 8 byte).

NativeIterator non viene allocato tramite l'allocatore del GC; invece, viene allocato da js::MallocArena. Di conseguenza, non è possibile procedere con un tipico spray di oggetti JS per posizionare oggetti controllati dell'heap del GC adiacenti ad esso.

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 oggetto utile allocato in js::MallocArena è ImmutableScriptData, che memorizza il 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);
  }
}

Questo è il gestore del bytecode utilizzato durante l'inizializzazione di un array JS.

Legge un valore a 4 byte dal flusso di bytecode e lo usa per impostare InitializedLength dell'array in [2].

Se la scrittura di 1 bit viene applicata al bytecode in [1], diventa possibile impostare InitializedLength oltre la capacità effettiva dell'array. Da lì, l'exploit procede sfruttando le primitive di lettura/scrittura fuori dai limiti (OOB).

Scarica lo strumento