Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-2763-POC — # CVE-2026-2763 개념 증명 익스플로잇 Mozilla JavaScript 엔진의 use-after-free 취약점에 대한 개념 증명 익스플로잇으로, 제한된 1비트 쓰기 프리미티브를 시연하여 out-of-bounds 읽기/쓰기로 이어집니다. | Kitploit
도구/GitHubGitHub/ppwwiinn/cve-2026-2763-poc
Memory ForensicsVulnerability AnalysisExploitationWeb SecurityBinary Exploitation
GitHubppwwiinn/cve-2026-2763-poc

CVE-2026-2763-POC

# CVE-2026-2763 개념 증명 익스플로잇 Mozilla JavaScript 엔진의 use-after-free 취약점에 대한 개념 증명 익스플로잇으로, 제한된 1비트 쓰기 프리미티브를 시연하여 out-of-bounds 읽기/쓰기로 이어집니다.

저장소 보기
25개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

취약점

이 취약점은 for-in 구문의 구현에서 발생합니다.

for-in 루프의 범위는 JSOP::Iter와 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]
    }

범위 진입 시, [1]에서 PropertyIteratorObject를 생성하기 위해 ValueToIterator가 호출됩니다. 그런 다음 인터프리터는 [2]에서 이를 인터프리터 스택에 저장(푸시)합니다. 범위가 종료되면 [3]에서 PropertyIteratorObject가 제거됩니다.

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

ValueToIterator 내부에서는 위와 같이 객체 생성이 수행됩니다. [1]에서 반복자 객체를 할당한 후, [2]에서 전역 연결 리스트에 연결됩니다. 이 시점에서 PropertyIteratorObject에 대한 유일한 참조는 인터프리터 스택에 보관됩니다.

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

범위가 종료되면 CloseIterator가 호출되어 이전에 연결된 리스트에서 반복자를 연결 해제하므로, 범위 외부에서 PropertyIteratorObject에 접근할 수 없게 됩니다.

그러나 yield가 올바르게 처리되지 않기 때문에, CloseIterator가 실행되기 전에 루프 범위 외부에서 코드를 실행하는 것이 가능합니다.

이 상황에서 PropertyIteratorObject에 대한 유일한 강한 참조는 인터프리터 스택에 있으므로, yield가 실행되고 반환된 제너레이터도 참조되지 않는다면, GC가 실행될 때 PropertyIteratorObject가 수집(해제)될 수 있습니다.

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

이 함수는 delete가 사용될 때 호출됩니다. 전역 연결 리스트를 순회하며 삭제된 속성을 반영하도록 반복자 객체를 업데이트합니다. [1]에서 콜백이 호출될 수 있으며, 이 콜백이 GC를 트리거하면 참조되지 않는 PropertyIteratorObject가 해제될 수 있습니다. 해당 객체의 파이널라이저는 대응하는 ni 객체도 해제하므로, use-after-free(UAF)로 이어집니다.


익스플로잇

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

NativeIterator의 크기는 대상 객체의 속성 수를 통해 제어할 수 있으며, cursor는 삭제됨으로 표시될 속성 슬롯을 가리키는 포인터입니다. [1]이 효과적으로 단일 비트를 OR 연산하므로, 이는 제한된 프리미티브를 제공합니다: 임의의(8바이트 정렬된) 주소에 대한 1비트 쓰기입니다.

NativeIterator는 GC 할당자를 통해 할당되지 않습니다. 대신 js::MallocArena에서 할당됩니다. 결과적으로, 제어된 GC 힙 객체를 인접하게 배치하기 위한 일반적인 JS 객체 스프레이를 진행할 수 없습니다.

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

js::MallocArena에서 할당되는 유용한 객체 중 하나는 JS 바이트코드를 저장하는 ImmutableScriptData입니다.

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

이것은 JS 배열을 초기화할 때 사용되는 바이트코드 핸들러입니다.

바이트코드 스트림에서 4바이트 값을 읽어 [2]에서 배열의 InitializedLength를 설정하는 데 사용합니다.

1비트 쓰기를 [1]의 바이트코드에 적용하면, 배열의 실제 용량을 초과하도록 InitializedLength를 설정하는 것이 가능해집니다. 여기서부터 익스플로잇은 OOB 읽기/쓰기 프리미티브를 활용하여 진행됩니다.

도구 다운로드