
CVE-2025-55182에 대한 상세 기술 분석, Next.js + React 19.0.0에서의 중요한 인증되지 않은 원격 코드 실행(RCE)입니다. 연구 과정, 패치 분석, 프로토타입 트래버설, 그리고 애플리케이션별 가젯 없이도 완전한 익스플로잇을 가능하게 하는 Blob 역직렬화 싱크에 대해 설명합니다.
이 문서는 CVE-2025-55182에 대한 개인적인 연구 과정과 확인된 결과 및 수행한 실험을 설명합니다. 이는 초기의 잘못된 가정과 최종 돌파구를 포함한 연구 조사의 정직한 기록을 나타냅니다.
CVE-2025-55182(CVSS 10.0)에 대한 광범위한 조사 후, 나의 초기 결론은 자동 RCE가 공개적으로 입증되지 않았으며 익스플로잇에 애플리케이션별 가젯이 필요하다는 것이었습니다. 이 결론은 틀렸습니다.
2025년 12월 5일, X(@maple3142)의 또 다른 독립 연구원으로부터 추가 정보를 얻은 후, 애플리케이션별 코드 취약점 없이 기본 Next.js에서 완전한 인증되지 않은 RCE를 성공적으로 재현했습니다.
나의 초점은 React 19.0.1 패치의 첫 번째 변경 사항에 있었습니다:
취약한 버전 (19.0.0):
return fn.bind.apply(fn, [null].concat(_ref));
패치된 버전 (19.0.1):
if (Array.isArray(promiseValue)) {
promiseValue = promiseValue.slice(0);
} else {
promiseValue = [];
}
나는 배열 대신 악의적인 객체를 사용한 fn.bind.apply()를 통한 공격 경로를 가정했습니다. $ACTION_REF_와 공격자가 제어하는 bound를 사용하여 Server Actions에 인자 주입을 시연할 수 있었습니다:
curl -X POST http://localhost:9000/ \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"<ACTION_ID>","bound":["; id #","/etc/passwd"]}'
결과: Server Action에 인자가 성공적으로 주입되었습니다. 그러나 이는 대상 함수가 해당 인자를 안전하지 않게 사용하는 경우에만 RCE로 이어집니다.
패치를 더 자세히 살펴보면 getOutlinedModel()에서 또 다른 중요한 변경 사항이 드러났습니다:
취약한 버전:
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]];
패치된 버전:
if (hasOwnProperty.call(value, name)) {
value = value[name];
}
이 취약한 동작은 다음과 같은 참조를 사용하여 프로토타입 체인 탐색을 허용했습니다:
$1:__proto__:constructor:constructor
연구 중에 .then 속성을 포함하는 thenable 객체를 테스트했습니다:
{"then": "$1:__proto__:constructor:constructor"}
JavaScript가 await을 통해 이를 처리할 때:
.then 속성을 보고 객체를 Promise로 취급합니다.obj.then(resolve, reject)를 호출합니다.then이 Function.constructor로 해석되면 JavaScript는 Function(resolve, reject)를 실행하려고 시도합니다.관찰된 결과:
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>)
Function.constructor가 다음과 같이 호출될 때:
Function(resolve, reject)
// resolve.toString() = "function () { [native code] }"
// Function attempts to parse this as code → SyntaxError
인자 resolve와 reject는 항상 네이티브 Promise 함수입니다. Function은 첫 번째 인자를 소스 코드로 해석하려고 시도하며, 이는 유효하지 않은 JavaScript입니다.
여기서 내 연구는 정체되었습니다. 애플리케이션별 가젯 없이는 Function.constructor에 대한 인자 제어가 불가능하다고 결론지었습니다.
초기 발견 사항을 게시한 후, 다른 독립 연구원이 내가 놓친 중요한 부분을 지적했습니다: '$B' (Blob) 역직렬화 싱크.
컴파일된 React Flight 서버 코드(TypeScript 소스에는 보이지 않음)에는 다음이 존재합니다:
case "B":
return response._formData.get(response._prefix + id);
위치:
[email protected]cjs/react-server-dom-webpack-server.node.unbundled.development.js[email protected]/dist/compiled/react-server-dom-webpack/이 코드는 React가 유효성 검사 없이 공격자가 제어하는 입력에서 파생된 값으로 response._formData.get()을 호출할 수 있도록 합니다.
| 접근 방식 | Function.constructor에 전달된 인자 | 결과 |
|---|---|---|
| Thenable (3단계) | resolve, reject (네이티브 함수) | ❌ SyntaxError |
| Blob + 오염된 Response | _prefix (공격자가 제어하는 문자열) | ✅ RCE |
다음을 결합하여:
$1:__proto__:then → Chunk.prototype.then)_response 객체:
_formData.get이 Function.constructor로 설정됨_prefix가 임의 JavaScript 코드로 설정됨$B 참조를 포함하는 내부 모델case "B": 핸들러가 실행됩니다:
Function.constructor("<attacker code>" + id)
이것은 인자 바인딩 제한을 완전히 우회합니다.
RCE를 주장하는 인기 GitHub PoC는 다음과 같은 Action ID를 사용합니다:
"child_process#execSync""vm#runInThisContext"이것들은 가짜입니다. Next.js는 애플리케이션에서 정의한 Action ID만 허용합니다. 잘못된 ID는 다음을 생성합니다:
TypeError: Cannot read properties of undefined (reading 'workers')
그러나 실제 익스플로잇에는 가짜 Action ID가 필요하지 않습니다. 유효한 Server Action ID면 충분합니다.
최소 Next.js 15.0.3 + React 19.0.0 애플리케이션에서 이 익스플로잇 체인을 테스트했으며, 다음만 포함했습니다:
async function myAction(data) {
"use server";
console.log("Server Action called with:", data);
return { success: true, received: data };
}
결과: 완전한 RCE 확인. 애플리케이션에는 안전하지 않은 코드, eval, execSync, 가젯이 없었습니다.
| 측면 | 발견 사항 |
|---|---|
| 인증 필요? | ❌ 아니요 |
| 애플리케이션 가젯 필요? | ❌ 아니요 |
| 기본 Next.js에서 작동? | ✅ 예 |
| 필요한 요청 수 | 1회 POST |
| 영향을 받는 버전 | Next.js ≤15.0.4 + React 19.0.0 |
| CVSS 점수 | 10.0 (정당함) |
2025년 12월 5일 기준, $B 싱크는 여전히 Next.js 15.0.5 (패치되었다고 주장된 버전)에 존재합니다.
검증:
$ npm pack [email protected]
$ tar -xzf next-15.0.5.tgz
$ grep -A5 'case "B":' package/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
결과:
case "B":
return response._formData.get(response._prefix + obj);
코드는 취약한 버전과 동일합니다.
취약점이 주장된 "수정된" 버전에서 완전히 패치되지 않았을 가능성이 있다는 발견으로 인해, Vercel 및 Meta의 보안 팀과의 검증이 완료될 때까지 완전한 개념 증명 페이로드를 공개하지 않습니다.
이 문서에 제공된 기술 세부 사항은 취약점 메커니즘을 이해하기에 충분하지만, 즉각적인 악용을 방지하기 위해 의도적으로 불완전합니다.
| 기술 | 상태 |
|---|---|
bound를 통한 인자 주입 | ✅ 확인됨 (제한된 영향) |
| 프로토타입 탐색 | ✅ 확인됨 |
thenable을 통한 Function.constructor 접근 | ✅ 확인됨 (단독으로는 악용 불가) |
| 취약한 버전 탐지 | ✅ 확인됨 |
| 문제 | 영향 |
|---|---|
$B (Blob) 역직렬화 싱크 | ❌ 중요 - 인자 제어 가능 |
| 컴파일된 코드 vs 소스 코드 검토 | ❌ 싱크는 컴파일된 출력에만 존재 |
| Response 객체 오염 메커니즘 | ❌ 모든 보호 우회 가능 |
CVE-2025-55182는 기본 Next.js 애플리케이션에서 완전한 인증되지 않은 RCE로 악용 가능합니다.
$B 역직렬화에 대한 통찰력 수신$B 역직렬화가 여전히 임의의 _response 객체를 허용하는지 테스트하세요.Next-Action 헤더$@, __proto__, $B 패턴이 있는 복잡한 멀티파트 페이로드$B 역직렬화에 관한 획기적인 통찰력은 X(@maple3142)의 연구원이 제공했습니다.최종 업데이트: 2025년 12월 5일