Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-2763-POC — Prova de conceito de exploração para CVE-2026-2763, um use-after-free no motor JavaScript da Mozilla, demonstrando uma primitiva de escrita de 1 bit restrita que leva a leitura/escrita fora dos limites. | Kitploit
Ferramentas/GitHubGitHub/ppwwiinn/cve-2026-2763-poc
Forensia de MemóriaAnálise de VulnerabilidadesExploraçãoSegurança WebExploração de Binários
GitHubppwwiinn/cve-2026-2763-poc

CVE-2026-2763-POC

Prova de conceito de exploração para CVE-2026-2763, um use-after-free no motor JavaScript da Mozilla, demonstrando uma primitiva de escrita de 1 bit restrita que leva a leitura/escrita fora dos limites.

Ver Repositório
2há 5 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Vulnerabilidade

A vulnerabilidade surge da implementação da sintaxe for-in.

O escopo de um loop for-in é representado por um par de bytecodes: 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]
    }

Na entrada do escopo, ValueToIterator é chamado em [1] para criar um PropertyIteratorObject. O interpretador então armazena (empurra) ele na pilha do interpretador em [2]. Quando o escopo termina, o PropertyIteratorObject é removido em [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, a criação do objeto é realizada como mostrado acima. Após alocar o objeto iterador em [1], ele é vinculado a uma lista encadeada global em [2]. Neste ponto, a única referência ao PropertyIteratorObject é mantida na pilha do interpretador.

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 o escopo termina, CloseIterator é invocado e desvincula o iterador da lista previamente vinculada, impedindo qualquer acesso ao PropertyIteratorObject fora do escopo.

No entanto, como yield não é tratado corretamente, é possível executar código fora do escopo do loop antes que CloseIterator seja executado.

Nessa situação, como a única referência forte ao PropertyIteratorObject está na pilha do interpretador, se yield for executado e o gerador retornado também não for referenciado, então quando o GC for executado, o PropertyIteratorObject pode ser coletado (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 função é chamada quando delete é usado. Ela percorre a lista encadeada global e atualiza os objetos iteradores para considerar a propriedade excluída. Em [1], um callback pode ser invocado; se isso acionar o GC, um PropertyIteratorObject não referenciado pode ser liberado. Seu finalizador também liberará o objeto ni correspondente, levando a um 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;
}

Um NativeIterator pode ter seu tamanho controlado por meio do número de propriedades no objeto alvo, e cursor é um ponteiro para o slot de propriedade que será marcado como excluído. Como [1] efetivamente aplica um OR em um único bit, isso produz uma primitiva restrita: uma escrita de 1 bit em um endereço arbitrário (alinhado a 8 bytes).

NativeIterator não é alocado pelo alocador do GC; em vez disso, é alocado a partir de js::MallocArena. Como resultado, você não pode prosseguir com um spray típico de objetos JS para colocar objetos controlados do heap do GC adjacentes a ele.

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

Um objeto útil alocado em js::MallocArena é o ImmutableScriptData, que armazena 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);
  }
}

Este é o manipulador de bytecode usado ao inicializar um array JS.

Ele lê um valor de 4 bytes do fluxo de bytecode e o usa para definir o InitializedLength do array em [2].

Se a escrita de 1 bit for aplicada ao bytecode em [1], torna-se possível definir InitializedLength além da capacidade real do array. A partir daí, o exploit prossegue aproveitando primitivas de leitura/escrita fora dos limites (OOB).

Baixar ferramenta