
React Server Components 버전 19.0.0, 19.1.0, 19.1.1, 19.2.0에서의 사전 인증 RCE
이 저장소에는 CVE-2025-55182의 PoC 재현 코드가 포함되어 있습니다. 이는 React 서버 컴포넌트(RSC)의 심각한 보안 취약점으로, 인증되지 않은 임의 코드 실행을 허용합니다.
취약점은 React 서버 컴포넌트가 클라이언트 요청에서 '서버 액션'을 역직렬화하는 방식에 존재합니다. 구체적으로, requireModule 함수가 요청된 내보내기 이름이 모듈의 직접적인 prop인지 검증하지 못했습니다. 이를 통해 공격자는 내보낸 함수의 constructor prop에 접근하여 전역 Function 생성자에 대한 참조를 얻을 수 있었으며, 이를 사용하여 임의 코드를 실행할 수 있습니다.
이 PoC는 최소한의 Node.js 환경을 사용하여 react-server-dom-webpack 라이브러리의 취약점을 격리합니다. 이는 익스플로잇이 라이브러리 자체의 버그를 증명하는 것이지, 프레임워크의 잘못된 설정이 아님을 확인하기 위함입니다.
npmnpm install
[!NOTE]
package.json은 취약한 버전19.0.0에 고정되어 있습니다.
이 스크립트는 취약한 React 런타임을 사용하여 요청을 디코딩하는 원시 HTTP 서버를 설정합니다.
# tty1
node --conditions react-server server.js
별도의 터미널에서 익스플로잇을 실행합니다. 이는 악의적인 Flight 페이로드를 서버에 전송합니다.
# tty2
node exploit.js id
응답에서 명령 출력이 반환되는 것을 확인할 수 있습니다:
예상 출력:
Response: uid=0(root) gid=0(root) groups=0(root)
왜 취약점이 발생했나요?
ReactFlightDOMServerNode.js의 requireModule 함수는 기본적으로 클라이언트가 보낸 name을 그대로 신뢰했습니다. 해당 속성이 실제로 노출되도록 의도되었는지 확인하지 않고 moduleExports[metadata[NAME]]를 수행했습니다. 따라서 클라이언트가 "야, 이 속성 줘"라고 하면 서버는 "그래, 여기 있어, 친구"라고 그냥 응답했습니다.
누구나 모든 속성에 접근할 수 있게 하는 것이 왜 나쁜 생각인가요?
기본적으로 누구나 프로토타입 체인에 접근할 수 있게 하며, 심지어 **constructor**에까지 접근할 수 있어 매우 위험합니다. 모듈이 함수를 내보내는 경우(예: module.exports = () => {}), 그 함수의 constructor는 말 그대로 전역 Function 생성자입니다.
Function 생성자를 얻는 것이 왜 RCE를 의미하나요?
공격자가 Function 생성자를 확보하면 '바인딩된 서버 액션' 기능을 남용할 수 있습니다. 악성 JavaScript를 포함한 문자열을 이 생성자에 바인딩하여 (사실상 new Function("악성 코드")로 변환) 실행시키면, 서버는 그 코드를 실행하게 됩니다.
React가 실제로 그 악성 함수를 실행하는 이유는 무엇인가요?
서버 액션은 ID로 트리거될 수 있기 때문입니다. 공격자가 자신의 module#constructor 참조를 가리키는 액션 ID로 페이로드를 제작하면, React는 이를 정상 액션처럼 해석하여 실행합니다. 그 '액션'은 실제로 공격자의 악성 함수입니다.
왜 이러한 것들이 검증되지 않았나요?
시스템은 서버 참조 메타데이터의 id와 name이 항상 개발자가 정의한 유효한 내보내기를 참조할 것이라고 가정했습니다. 요청된 prop이 실제 내보내기인지, 아니면 프로토타입 체인에서 상속된 것인지 확인하는 hasOwnProperty와 같은 안전 검사가 없었습니다.
server.js를 사용하고 Next.js를 사용하지 않는 이유원시 server.js (그리고 도우미 webpack-runtime.js)를 사용하여 React 서버 컴포넌트 런타임을 수동으로 구성합니다. 이를 통해 다음을 할 수 있습니다:
module.exports = fns). 실제 번들러는 구성에 따라 내보내기 방식이 달라질 수 있습니다.react-server-dom-webpack 내부에 있음을 보여줄 수 있습니다.react-server-dom-webpack은 Webpack 번들 내에서 실행된다고 가정합니다. 우리의 webpack-runtime.js는 예상되는 전역 변수(__webpack_require__, __webpack_chunk_load__)를 제공합니다.이는 취약점을 모의하는 것이 아니라, 라이브러리가 실제로 작동하는 데 필요한 최소한의 런타임을 제공하는 것입니다.
개발자가 의도적으로 child_process.exec와 같은 위험한 것을 노출할 때만 작동하는 '유효하지 않은 PoC'에 대한 논의가 있었습니다.
이 PoC는 그런 것이 아닙니다. 정상적이고 안전한 설정에서 작동합니다.
updateProfile 함수를 노출합니다. 셸 명령은 없습니다.updateProfile.constructor로 바로 이동할 수 있으며, 이는 전역 Function 생성자입니다..constructor에 대한 접근을 허용하지 말았어야 합니다. 개발자는 Function 생성자를 노출할 의도가 없었지만, 안전하지 않은 역직렬화가 대신 그렇게 했습니다.유일한 실제 요구 사항은 모듈이 함수를 직접 내보낸다는 것입니다 (module.exports = fn). 이는 CommonJS 및 많은 번들러 설정에서 매우 일반적입니다.
exploit.js의 페이로드는 세 개의 청크로 React Flight 메시지를 구성합니다:
id: "user-profile-action#constructor", "생성자를 줘"라는 의미.bound: 인수가 포함된 청크 2를 가리킵니다.["console.log('nice try, diddy!')"]: 악성 코드 문자열.React가 이를 역직렬화할 때:
user-profile-action을 해석합니다..constructor 속성을 읽어 전역 Function을 얻습니다.new Function("console.log('nice try, diddy!')")그리고 그것이 RCE입니다!
패치된 버전으로 즉시 업그레이드하십시오:
react-server-dom-webpack >= 19.0.1react-server-dom-parcel >= 19.0.1react-server-dom-turbopack >= 19.0.1패치는 상속된 속성에 접근하는 것을 방지하기 위해 hasOwnProperty 검사를 도입하고 base64 파일 업로드를 제한합니다.
패치된 버전에서 이 PoC를 실행하면 서버가 다음 오류로 충돌하거나 오류가 발생합니다:
$ node --conditions react-server server.js
Listening on http://localhost:3000
/path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726
resolvedValue = resolvedValue.bind.apply(
^
TypeError: Cannot read properties of undefined (reading 'bind')
at /path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726:43
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Node.js v20.19.3
이는 익스플로잇이 constructor 속성에 접근하는 데 실패했음을 확인합니다 (Function 대신 undefined 반환). 결과적으로 후속 .bind 호출이 실패했습니다.
이 코드는 교육 및 테스트 목적으로만 제공됩니다. 소유하지 않았거나 테스트 허가를 받지 않은 시스템에 이 익스플로잇을 사용하지 마십시오.
Released under DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE.