因为平日做开发比较多,对"马"的一些概念基本是一无所知,最近读到了一篇往Tomcat Filter里种内存马的文章挺有意思,因为是无文件落地(其实还是有吧,就是删完后也无影响),直接就可以把马注入到内存里。所以持久性肯定会强,而且今年的hvv对内存马的应用也多了起来,所以在这篇文章里就简单研究一下这个技术点:
贴个内存马的概念:
我学习了几篇文章,对内存马不会有文件落地还是比较疑惑,因为文章里无论是filter型还是servlet api型还都是要上传一个jsp或者用冰蝎连下远程才可以,虽说jsp文件只要上传一次就可以删除了,但是实际上还是有上传的动作在了,然后又学习了几篇文章,给出了如下答案:
比如这篇文章就记录了内存马需要上传文件和无需上传文件(利用了shiro反序列化漏洞进行注入)两种方式:
https://blog.csdn.net/Finlinlts/article/details/119989783
这篇文章的开篇也有提到jsp内存马需要上传文件的问题:
https://www.freeaihub.com/post/106851.html
理解的不够深,下面对Filter型内存马的操作我就都是在可以做文件上传的前提条件下去尝试了:
参考文章:
2、IntelliJ IDEA创建Servlet最新方法 Idea版本2020.2.2以及IntelliJ IDEA创建Servlet 404问题(超详细)
7、一文看懂内存马
一、环境搭建
之前写过一篇文章是研究Tomcat原理架构,结果Container容器那块没有好好研究,这里贴两张图,因为Filter型内存马实际上都是在Container容器中运行的,后面跟进源码也会用到这两张图:
每个 Context 都代表一个具体的Web应用,在一个 Context 下可以有着多个 Wrapper
Wrapper 主要负责管理 Servlet ,包括的 Servlet 的装载、初始化、执行以及资源回收
我们的请求会经过 filter 之后才会到 Servlet ,那么如果我们动态创建一个 filter 并且将其放在最前面,我们的 filter
就会最先执行,当我们在 filter 中添加恶意代码,就会进行命令执行,这样也就成为了一个内存 Webshell
所以我们后文的目标:动态注册恶意 Filter,并且将其放到 最前面
在注入 Filter内存马 之前要先看下正常 Filter 在 Tomcat中的流程是怎么样的,主要跟进doFilter的相关代码,首先需要创建Servlet:
我这里已经建好了,可以参考文章2来一步步创建。
参照文章3通过web.xml注册filter并重启tomcat,请求filter的url可以输出结果:
filter1和filter2内容一样,在这个filterchain中由于web.xml设置,1排在2的前面
/**
* @author jinyunlong
* @date 2021/10/25 13:36
* @profession ICBC锅炉房保安
*/
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class filterDemo1 implements Filter{
/*
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
System.out.println("我是FilterDemo1,客户端向Servlet发送的请求被我拦截到了");
//对请求放行,进入下一个过滤器FilterDemo2
chain.doFilter(request, response);
System.out.println("我是FilterDemo1,Servlet向客户端发送的响应被我拦截到了");
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>filterDemo1</filter-name>
<filter-class>filterDemo1</filter-class>
</filter>
<filter>
<filter-name>filterDemo2</filter-name>
<filter-class>filterDemo2</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo1</filter-name>
<url-pattern>/*</url-pattern>
<!-- /*是对所有的文件进行拦截 -->
</filter-mapping>
<filter-mapping>
<filter-name>filterDemo2</filter-name>
<url-pattern>/*</url-pattern>
<!-- /*是对所有的文件进行拦截 -->
</filter-mapping>
</web-app>
关键是在这一行:
chain.doFilter(servletRequest,servletResponse);
chain.doFilter将请求转发给过滤器链下一个filter,如果没有filter那就是转发给Servlet,所以说过滤链是可以由多个filter组成的。
二、上传内存马
跟进代码流程比较长而且比较复杂,有不少还没弄懂,就不截图了。根据文章3、4可以总结出生成filter型内存马基本流程如下:
1、创建一个恶意 Filter
2、利用 FilterDef 对 Filter 进行一个封装
3、将 FilterDef 添加到 FilterDefs 和 FilterConfig
4、创建 FilterMap ,将我们的 Filter 和 urlpattern 相对应,存放到 filterMaps中(由于 Filter 生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的 Filter 最先触发)
每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启
构造jsp代码并上传至Tomcat服务器(我这里是Tomcat8.5.42),访问即创建恶意filter成功,之后便可以命令执行。
三、小结
从面上记了下这次实例的过程,针对filter型内存马,源码的跟进步骤还有点不清楚,参考文章3、4、5写的非常清晰,日后可以继续学习,而且filter型内存马只是非agent型内存马的一种,还有servlet api型和无需上传文件的漏洞型;还有agent型内存马这一大类问题也有待学习。
作为普通开发人员来说,先从面上对这个技术点有个了解,至少知道遇到filter型内存马该如何应对了,(重启Tomcat服务哈哈哈哈)。