freeBuf
主站

分类

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

特色

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

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

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

FreeBuf+小程序

FreeBuf+小程序

擅长捉弄的内存马同学:Filter内存马(高甜)
2022-02-15 19:22:10

前言

记着当初重新学Java的初衷就是因为内存马这块存在知识欠缺,之前脚本小子本小都是一键注入进去的,没有好好学习原理。这次年前把内存马的东西都整理好了,看了很多大师傅的文章学了不少东西,师傅们tql,这篇文章是属于内存马系列的第一篇,我主要说一下Filter型内存马,虽然现在师傅们都是用agent或者框架型的打,但咱们还是一个一个都说一遍吧,当作学习了解了,文章中引用了很多师傅的东西,强烈建议大家去看一看原文。废话不多说,直接开始干。

一、基础知识-Sevlet

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

参考链接:

https://www.runoob.com/servlet/servlet-intro.html

https://blog.csdn.net/qq_19782019/article/details/80292110

https://www.cnblogs.com/red-code/p/7049145.html

引用师傅的图片:

二、基础知识-Tomcat架构

首先在学习内存马之前我们需要先了解一下基础知识,要不之后看起来可能会很费劲,首先我们先学习一下Tomcat的架构。

参考链接:

https://blog.csdn.net/qq_34101364/article/details/120856415

https://www.cnblogs.com/nice0e3/p/14622879.html#servletcontext

Tomcat 是Web应用服务器,是一个Servlet/JSP容器,基本框架如下图所示,主要有server、service、connector、container。

1644902327_620b37b73dadb3b97f917.png!small?1644902327602

Server:

代表整个 Tomcat 服务器。

一个 Tomcat 只有一个 Server Server 中包含至少一个 Service 组件。

Service:

Service 主要是为了关联 Connector 和 Container,同时会初始化它下面的其它组件,在 Connector 和 Container 外面多包一层,把它们组装在一起,向外面提供服务,

一个 Service 可以设置多个 Connector,但只能有一个 Container 容器。

Connector:

Connector负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程。这里Tomcat中一个Connector对应了一个请求,所以service容器中可以同时有多个connector对象。

简单来说就是Connector将在某个指定的端口上来监听客户的请求,将 socket 连接封装成 request 和 response 对象,后续交给 Container (Engine)来处理,并从Engine处获得响应并返回给客户端。
1644903717_620b3d25526ad17f26169.png!small?1644903717678

引用一张师傅的图片。

Connector是使用protocolHandler来处理具体的请求(不同的protocolHandler代表不同的连接类型)。

而每种protocolHandler都使用了各自的3个重要组件来具体处理请求:

Endpoint:用于处理底层Socket连接(nio和nio2实现的是TCP/IP协议,Apr实现的是SSL/TLS协议);

Processer:用于将Endpoint接收到的Socket封装成Request(实现HTTP协议或websocket协议或AJP协议);

Adapter:用于将封装好的Request交给Container(将请求适配到servlet容器)。

Container(又名Catalina):

Container容器则是负责封装和管理Servlet 处理用户的servlet请求,并返回对象给web用户的模块。

Tomcat中有四种类型的Servlet容器,从上到下分别是 Engine、Host、Context、Wrapper。引用一张师傅的图片。

Engine:表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一Engine,但是一个引擎可包含多个 Host;

Host:代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context;

Context:表示一个 Web 应用程序,每一个Context都有唯一的path,一个Web应用可包含多个 Wrapper;

Wrapper:表示一个Servlet,负责管理整个 Servlet 的生命周期,包括装载、初始化、资源回收等

根据nice_0e3师傅的文章我们知道Container 处理请求,内部是使用Pipeline-Value管道来处理的,每个 Pipeline 都有特定的 Value(BaseValue),BaseValue 会在最后执行。这块建议大家去看看nice_0e3师傅的文章,引用nice_0e3师傅的图。

三、基础知识-Tomcat和Servlet的关系

我们根据上面的基础知识可以知道Tomcat 是Web应用服务器,是一个Servlet/JSP容器,而Servlet容器从上到下分别是 Engine、Host、Context、Wrapper。

Engine:实现类为 org.apache.catalina.core.StandardEngine

Host:实现类为 org.apache.catalina.core.StandardHost

Context:实现类为 org.apache.catalina.core.StandardContext

Wrapper:实现类为 org.apache.catalina.core.StandardWrapper

在Tomcat中Wrapper代表一个独立的servlet实例,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper 的主要任务就是载入Servlet类并且进行实例化),同时其从ContainerBase类继承过来,表示他是一个容器,只是他是最底层的容器,不能再含有任何的子容器了,且其父容器只能是context。而我们在也就是需要在这里去载入我们自定义的Servlet加载我们的内存马。

四、基础知识-Filter

参考链接:

https://www.runoob.com/servlet/servlet-writing-filters.html

https://www.cnblogs.com/zlbx/p/4888312.html

http://wjlshare.com/archives/1529

Java Servlet API是Servlet容器和Servlet之间的接口,而Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。它由 Servlet容器进行调用和执行。过滤器通过web.xml中的 XML 标签来声明,然后映射到应用程序的部署描述符中的 Servlet 名称或 URL 模式,当我们在web.xml注册了Filter时,它就会将某个Servlet进行拦截,而这里我们就可以通过自定义FIlter来对用户的请求进行一系列的操作。

五、Filter流程分析

根据我们上面说的基础知识我们已经知道了,Tomcat和Servlet之间的关系了,Tomcat中的Servlet容器从上到下分别是 Engine、Host、Context、Wrapper四种,而我们上面说过StandardWrapper(Wrapper的实现类)的主要任务就是载入Servlet类并且进行实例化。在Servlet API中提供了一个Filter接口,所以我们这里写一个简单的Filter,然后打断点开始进行分析,我们通过这样来看一看StandardWrapper是如何载入Servlet类。

(一)ContextConfig读取配置文件(应用启动过程)

首先在创建Filter之前先会读取当前应用的配置,我们启动容器的过程中首先定位到configureContext.class,我们找到读取filter部分,查看该位置代码。这里webxml中的filter信息被添加到了FilterDef和FilterMap中。我们简单查看一下这两个add方法。

addFilterDef

addFilterMap

这里我们可以看到,ContextConfig是读取配置文件,将这两个filter的配置设置到当前应用的StandardContext中的两个属性当中,而这两个属性实际就是web.xml中的filter和filter-mapping这两个节点的映射。

(二)StandardContext执行filterStart(应用启动过程)

我们读取完配置文件,当应用启动的时候,StandardContext会去调用filterStart方法,创建ApplicationFilterConfig,将filterDef以ApplicationFilterConfig的形式存在,并且将其放到了filterConfig中。

(三)StandardWrapperValve执行流程分析

①StandardWrapperValve调用invoke方法

再此之前首先我们要了解一下,对于每一个连接,连接器都会调用关联容器的invoke方法。接下来容器调用它的所有子容器的invoke方法。例如,如果一个连接器跟一个StadardContext实例相关联,那么连接器会调用StandardContext实例的invoke方法,该方法会调用所有它的子容器的invoke方法。

https://blog.csdn.net/u012233580/article/details/79316294

根据我们之前引用nice_0e3师傅的图和上图,我们的请求通过Pipeline-Value管道这种形式执行到了StandardWrapperValue,其中细节可以看一下参考链接。而我们也就是在StandardWrapperValue中我们加载到了Servlet。所以我们对StandardWrapperValue进行调试,启动容器后在页面发起请求。

可以看到我们发起请求后,跳转到了StandardWrapperValue的invoke方法。

1644913723_620b643bd2cd7e20f0a97.png!small?1644913726860

我们也可以在这里可以观察一下栈信息,可以看到这些就是调用管道的invoke方法。

1644913836_620b64ac9169d5d4edab7.png!small?1644913839772

②StandardWrapperValve创建FilterChain

继续分析代码,StandardWrapperValve在获取到wrapper和servlet等一系列信息后,我们走到了图中的位置,这里StandardWrapperValve通过ApplicationFilterFactory.createFilterChain(request, wrapper, servlet)来创建一个filterChain链,我们跟进去分析。

1644914363_620b66bb072c62805e46d.png!small?1644914366753

进入createFilterChain方法中,我们分析代码可以看到,createFilterChain首先获取一个filterChain,之后通过findFilterMaps方法获取filterMap,最后将filterMap放到filterMaps中。

这里findFilterMaps方法我们通过名称就可以大概猜出来是获取filterMap,这里我们点进去看一下。 

此时我们就将filterMap都获取到了,我们继续向下分析代码。

我们继续向下走,我们获取到了url信息,开始将当前url和filterMap进行匹配。如果匹配成功,我们执行(ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName()),我们跟到findFilterConfig方法去查看一下。

PS1:matchFiltersURL,这个匹配是是精确匹配,直接通过包名+类名去匹配。

PS2:matchFilterServlet,这个是模糊匹配,匹配规则和servlet的规则很类似。

我们跟到findFilterConfig,如图所示,我们在这里获取到了StandContext中的filterConfigs,而这里存储着我们当时在filterStart存入的filterConfig。我们回到ApplicationFilterFactory.createFilterChain位置继续分析。

回到ApplicationFilterFactory.createFilterChain,我们已经获得我们的filterConfig,继续向下,我们执行filterChain.addFilter(filterConfig),我们跟进去查看。

我们跟到addFilter方法开始分析代码,这里addFilter方法做了三件事,一个是循环去重,接下来扩容,最后将我们匹配好的filterConfig放入filters中。

我们回到ApplicationFilterFactory,循环执行完之后,我们的ApplicationFilterChain链子算是做好了。我们返回到StandardWrapperValue中。

这里我贴一个菜鸟教程的链接,大家可以看一下Filter、FilterChain、FilterConfig 介绍。

https://www.runoob.com/w3cnote/filter-filterchain-filterconfig-intro.html

③StandardWrapperValve执行FilterChain

我们创建好了FilterChain链,回到StandardWrapperValue中继续向下调试代码,最终代码执行到了filterChain.doFilter。

我们跟进查看,这里我们跳到了ApplicationFilterChain的doFilter方法,然后走到internalDoFilter方法中。

进入internalDoFilter方法后,首先会将filters全部取出,之后调用getFilter()将filter从filterConfig 中取出,最后执行filter.doFilter方法这里也就是我们想要去执行的位置。一气呵成。

向下调试一下看看,此时执行到我们的过滤器了。到这里我们需要了解的Filter的内容就结束了。

六、Filter存储分析

FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息,对应web.xml中的filter节点的映射

FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern,对应web.xml中的filter-mapping的映射

FilterConfigs:存放filterConfig的数组,在 FilterConfig 主要存放所有与过滤器对应的filterDef信息及Filter实例

这些信息之前我们在Filter流程分析中已经提到过它的调用流程,我们知道filter实例实际上是存储在FilterDefs、FilterMaps、FilterConfigs 这三个变量当中,所以我们的内存马只需要将我们恶意的filter注入到这三个变量当中,然后根据这三个变量,通过createFilterChain动态生成一条过滤链。这样也就达到了注入内存马的目的,而StandardContext又会一直保留到Tomcat生命周期结束,所以内存马就可以一直驻留下去,直到Tomcat重启。

七、Filter内存马实现

通过Filter的存储分析我们知道关键的三个变量都在StandardContext中,且StandardContext又会一直保留到Tomcat生命周期结束所以内存马可以一直驻留。我们想要进行注入首先我们就需要获取StandardContext,而这就是个比较阿巴阿巴阿巴的问题了。

该说不说,这个内存马真是站在巨人的肩膀上进行学习,bitterz师傅在先知里写的文章就总结了很好的方法。

参考链接:

https://xz.aliyun.com/t/9914

StandardContext的获取主要是分两种,分别是已有request对象的情况没有request对象的情况

这里我就不去多说太多了,之后有机会菜鸡我会一个一个去学,小伙伴们这块去看看bitterz师傅的文章吧。我在下面贴两个获取StandardContext的代码。

已有request对象的情况

javax.servlet.ServletContext servletContext = request.getServletContext();

Field appctx = servletContext.getClass().getDeclaredField("context");  // 获取属性
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);  //从servletContext中获取context属性->applicationContext

Field stdctx = applicationContext.getClass().getDeclaredField("context");  // 获取属性
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);  // 从applicationContext中获取context属性->standardContext,applicationContext构造时需要传入standardContext

没有request对象的情况

org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();

StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

System.out.println(standardContext);

现在我们已经可以获取到了StandardContext了,那我们就可以通过反射把我们的恶意filter添加到三个变量当中,内存马的编写思路如下:

制作恶意filter过滤器;

反射获取FilterDef,将配置好的FilterDef通过addFilterDef将FilterDef添加;

反射获取FilterMap,将配置好的FilterMap通过addFilterMapBefore将FilterMap添加

反射获取ApplicationFilterConfig,通过构造函数将StandardContext和FilterDef配置进filterConfig,之后将FilterName、filterConfig配置到StandardContext的filterConfigs中。

大家可以试着去写,网上的很多内存马都可以做参考,执行效果如下。

1644920380_620b7e3c7d25126875ee1.png!small?1644920380701

可爱小尾巴~~~

我只是个基础很差 技术很菜 脚本小子里面的小菜鸡,文章里面有什么写的不对的地方,望师傅们多加指正。这篇文章是我对内存马的粗浅认识,很多知识点都是看着各种师傅的文章去学习的,真的是站在巨人的肩膀上啊。内存马我记得自己在很早之前就了解过了,但是实战中只是通过各种工具直接打。去年学习完内存马后一直也没有对知识进行梳理。就酱,先写一个Filter内存马把最基本的东西先搞出来,下一篇应该还是写Servlet的内存马。今年应该会把常见的几种内存马都写出来,学吧,干安全的0点睡觉,养生呢啊。

参考文章

tomcat及servlet内存码、Filter内存码、Listener内存码

Java安全之基于Tomcat实现内存马

Java内存马:一种Tomcat全版本获取StandardContext的新方法

Java Web(一) Servlet详解!!

Tomcat 内存马学习(一):Filter型

filter在Tomcat的实现(转)

Servlet之Filter详细讲解

JavaWeb——Servlet(全网最详细教程包括Servlet源码分析)

StandardWrapper

深入剖析Tomcat(中文版)— Budi Kurniawan、Paul Deck

# web安全 # 代码审计 # Java代码审计 # JAVA安全 # 内存马
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录