Tomcat解析XML引入的新颖webshell构造方式
LeeH 发表于 四川 WEB安全 5869浏览 · 2025-06-06 07:53

前言

分析Tomcat解析XML配置文件的策略以及这个过程中导致的安全问题

Tomcat解析XML机制

流程分析

web.xml的扫描

首先我们从Bootstrap#main Tomcat启动位置开始分析,其在启动过程中将会反射调用Catalina#load方法进行一个server实例的创建

image-20250326224617324.png


首先是创建了一个Digester

之后会通过一系列不同的方式获取到Tomcat的配置文件server.xml的输入流

image-20250326224858227.png


之后会调用前面创建的Digester实例的parse对文件内容进行解析

image-20250326225128065.png


image-20250326225206338.png


接下来会在Digester#startElement方法中对匹配到的标签按照对应的规则进行处理

image-20250326230136067.png


具体的处理逻辑大致存在三个步骤:

image-20250327111248607.png


1 如果匹配的标签存在有classname属性值,则将会调用ObjectCreateRule规则进行对应类的实例化,在实例化之后会将其加入digester的栈中
image-20250327112041134.png


2 在实例化之后,将会调用SetPropertiesRule#begin进行第二条规则的调用,进行属性的赋值其核心是首先获取到匹配到的标签的属性名
image-20250327114543567.png
之后通过调用IntrospectionUtils#setProperty方法对获取到的属性值进行赋值,具体的赋值过程如下:
image-20250327114705874.png
首先是将属性名进行规则化后同set关键词进行拼接,使得其满足setter方法的命名规则其次,通过反射获取上面实例化类的所有方法集合,若这个方法集合中存在有对应的setter方法,将会反射调用该setter方法后续同样对setFoo(int/boolean)等类型的setter方法进行了处理
image-20250327143139410.png
值得注意的是,在这里的setter方法调用过程中,针对setProperty的调用进行了特殊的处理,如果传入的参数invokeSetProperty为true时才允许对setProperty方法进行调用,动态跟踪了一下,在扫描XML配置文件的过程中,该过程仅会通过public static boolean setProperty(Object o, String name, String value)进行setter方法的处理,该方法对应的invokeSetProperty恒为true,这样就导致了我们能够通过调用setProperty函数进行系统属性的覆盖,达到高版本JNDI限制绕过等等目的。
image-20250327144047642.png


3 最后就是调用SetNextRule#begin默认未对其进行实现

Tomcat启动

image-20250327161655129.png


上图为Tomcat的架构图

在解析web.xml文件之后会创建一个server并启动

image-20250327161846379.png


在启动这个server之后,将会依次启动Server Service Engine Host

在Host层启动过程中,也即是HostConfig#start方法

image-20250327162802826.png


将会调用deployApps进行应用的部署

image-20250327163855020.png


存在三个核心方法

deployDescriptors调用

这个方法将会解析来自configBase的XML文件,具体是在catalina_home目录下的conf/Catalina/localhost目录下

image-20250327165529008.png


他会遍历在这个文件夹下的文件,并且判断是否为XML文件类型,如果没有被处理过的条件下,将会构建一个DeployDescriptor添加到es线程池中去,将会多线程执行DeployDescriptor#run方法

image-20250327170346369.png


这里核心是调用的HostConfig#deployDescriptor进行处理

image-20250327171629492.png


也会将这个目录下的XML文件通过调用Digester@parse进行解析,具体的解析步骤与前面的web.xml扫描类似,都是扫描特定的标签,之后检索实例化类的setter方法进行反射调用

deployWARs调用

这个函数方法将会对webapps/下的WAR包进行部署

image-20250327172641374.png


和上一种方式类似,经过一系列的检查之后,将会创建一个DeployWar对象加入到线程池中去,具体的部署方法实现是在DeployWar#run方法

image-20250327172751088.png


image-20250327172806497.png


默认是创建了一个StandardContext作为上下文添加到Host层中进行管理

image-20250327173631561.png


若开启了deployThisXML,也即是运行在SecurityManager中时将会对WAR包中的META-INF/context.xml文件调用Digester#parse进行解析,同样可以反射调用setter方法

deployDirectories调用

主要是针对在webapps/下解压的项目进行部署

大致分为以下步骤

1 尝试获取对应文件夹(也就是webapps/xxx/)下的META-INF/context.xml文件
image-20250327180836953.png


2 根据不同的情况创建一个Context,默认创建的是org.apache.catalina.core.StandardContext,之后将这个Context添加到Host中去

3 后续将会通过ContextConfig#init进行Context层的初始化
image-20250327181334833.png
这里首先会通过createContextDigester创建context.xml文件的解析规则
image-20250327181505849.png
之后调用contextConfig进行配置文件的解析
image-20250327181903433.png
对于context.xml文件的获取,这里优先使用的是webapps/xx/下存在的content.xml文件配置,若不存在该配置文件则采用的是默认的context.xml配置,也就是conf/context.xml文件后续在获取到配置文件后,通过processContextConfig对其进行解析
image-20250327182150182.png
核心也是利用了Digester#parse进行了解析,在这个过程中和上面的流程类似,也会造成setter方法的调用

动态恶意文件加载

上述流程主要是分析了在Tomcat的启动过程中将会通过调用deployApps方法进行运行在Tomcat容器下的应用进行部署,核心是通过deployDescriptors / deployWARs / deployDirectories来分别加载部署conf/Catalina/localhostwebapps/下未解压的WARs包和webapps/下已解压的文件夹应用

其中主要核心分析了XML文件的解析过程中将会触发setter方法的反射调用

然而,这仅仅是在Tomcat启动的初始阶段才会对其进行加载,只会在收到供应链攻击的情况下才会存在该类漏洞的触发!

如何在运行时Tomcat动态触发这类XML文件解析导致的setter触发呢?

具体是在ContainerBase#startInternal的控制下,在启动完成各个组件后,将会创建一个后台线程

image-20250328200946347.png


image-20250328201049321.png


这个线程用来定期的检查各个应用是否存在变动,当前会话是否过时

image-20250328205256666.png


将会遍历所有的子层进行处理,当处理Host层时,其backgroundProcess方法实现使用的是父类ContainerBase的实现

image-20250328205633903.png


最终将会触发HostConfig的生成周期事件

image-20250328210217186.png


对Host层部署的各类应用进行周期性检查

image-20250328210256505.png


这里存在一个if语句的判断,判断其是否开启动auto deploy的机制,其赋值阶段是在Host层的启动过程中通过判断tomcat的appBase是否是一个目录来进行决定

image-20250328210813571.png


默认都是为true的,则默认是会对webapps目录下的应用进行热部署

image-20250328210922256.png


这个方法同前面Tomcat启动分析的部署方法相同,通过这类热部署的机制也造成了能够针对tomcat这类容器进行动态恶意文件的加载,只要能上传恶意XML文件到指定目录下

总结

通过分析Tomcat启动过程中从Server -> Service -> Engine -> Host -> Context的全流程机制,分析学习了Tomcat针对配置文件XML的处理方式,以及为什么在XML文件的处理过程中将会导致setter方法的反射调用,同时分析了Tomcat扫描的XML文件包括有web.xml以及conf/Catalina/localhost/xxx.xml conf/context.xml webapps/xx/META-INF/context.xml webapps/xx.war#META-INF/context.xml

同时除了在Tomcat启动过程中将会导致XML文件的解析,同样在开启了热部署机制的前提下,利用Tomcat的动态检查的方式,其仍然能能够对运行时的Tomcat相应目录下的XML文件进行解析导致setter调用

上述分析基于Tomcat 8.5.60,经测试Tomcat9也可行



Tomcat XML webshell构建

概述

前面分析了Tomcat通过Digester#parse进行XML配置文件的流程,以及为什么这个过程中会触发任意类方法的setter方法,这里主要是学习学习如何使用这种机制将其转化成一个jsp webshell

流程分析

前文回顾

上文中,我们通过搭建了Tomcat 8.5.60,详细的分析了解析XML文件的过程

其核心的解析流程是在ContextConfig#init方法中

image-20250329225155520.png


创建了一个Digester对象,将创建的对象传入到contextConfig进行XML的解析

image-20250329225533432.png


这里优先待解析的XML文件源是来自StandContext的defaultContextXml属性值,若不存在这个属性值则选择使用Tomcat默认的conf/context.xml文件

其具体的解析步骤在processContextConfig方法的实现中

image-20250329230048071.png


首先获取目标文件的输入流,之后调用digester#parse进行XML的解析,在这个步骤中将会进行标签的匹配,之后经过以下三步

image-20250329230858320.png


1第一步,根据其中的classname属性值指定的类名创建一个实例化类

2 第二步,根据标签中的属性名同set进行赋值及处理得到一个标准的setter方法,若在上步中实例化的类中存在该方法,则反射调用该方法,这一步中触发了setter方法

webshell实现

这里直接借用了y4tacker师傅的实现方式

分为了四个步骤

1 模拟tomcat的处理方式,首先直接实例化一个ContextConfig对象,方便存储context属性对象,以及调用其contextConfig方法进行核心的XML文件解析逻辑

2 之后是获取StandardContext:通过jsp的request域进行StandardContext对象的获取,并设置了defaultContextXml值指代恶意的XML文件位置

3之后又创建Digester对象,设置匹配的标签规则

4 最后反射调用contextConfig方法进行恶意XML文件的解析

实测能够成功:

image-20250330205011584.png


这里的报错不用管,只是因为本地没有添加tomcat的相关依赖,运行tomcat会自动使用你tomcat目录下的依赖

image-20250330205118603.png


上述的JSP webshell利用方式需要首先上传一个恶意的XML文件后才会进行生效

基于上文中对XML文件解析细节的分析,我们知道,后续传入给Digester#parse的参数值仅仅是通过这里的文件得到的输入流,y4tacker师傅这里也使用了get的传参的方式进行恶意XML文件的传入

总的来说,对于XML文件的解析核心就是三个部分

1配置XML解析的规则

2获取到待解析的XML输入流

3 直接使用Digester#parse进行解析,这里就会触发setter方法调用

上面构造的JSP webshell存在很多冗余操作,我们看看如何将其剔除后进行简化,我们逆向思维解决上面的三个步骤

1 使用Digester#parse进行解析:tomcat中是使用的parse方法传入一个InputSource对象进行解析
image-20250330214639792.png
观察parse这个方法重载
image-20250330214742754.png
我们可以直接传入一个InputStream即可,不需要重复将其包装成InputSource对象,同时,我们需要首先创建一个Digester对象才可以使用它的parse方法,这里直接使用默认的构造函数即可本部分构造如下

1 获取到待解析的XML输入流:我们只需要待传入文件的输入流,并不需要类似tomcat处理样指定一个本地文件,然后之后获取这个本地文件的输入流,最后在传给parse函数进行解析我们这里直接通过base64编码的方法进行文件内容的传输,之后封装成一个输入流类进行解析的操作本部分构造如下:

1 配置XML解析的规则:那么最后就需要配置对应的解析规则,毕竟直接通过new的方式创建的Digester对象是一个“干净”的类对象,需要对其添加匹配规则我们首先看看tomcat是如何对其进行规则的添加的,我们回到ContextConfig#createContextDigester方法
image-20250330215955955.png
具体添加的规则为addRuleSet调用添加的ContextRuleSetNamingRuleSet首先来看下ContextRuleSet添加的逻辑这里ContextRuleSet的构造函数没有什么特别的,就是规定一下匹配的前缀,以及定义context实例是否需要被创建
image-20250330221659567.png
我们这里核心分析下addRuleSet的过程
image-20250330221832419.png
前面没有什么特别的,我们重点关注,这里将会执行我们传入的RuleSet对象的addRuleInstances方法进行规则的创建,这里也是ContextRuleSet#addRuleInstances
image-20250330222122027.png
这里才是添加具体规则的核心逻辑so,我们可以设定我们自定义的规则进行更好的解析,比如我们可以配置对标签Test/Loader进行解析,甚至使用哪个属性进行类名的传递都可以自定义本部分的构造如下:

经过测试,上面构造的webshell是有效的

image-20250330223652896.png


输入的XML文件如下:

总结

分析学习了Tomcat在解析XML配置文件的内在逻辑,同时了解tomcat后台线程将会动态的对某些目录下的xml文件进行扫描

也分析了通过这类XML解析的作用机制制作jsp webshell的方式方法,同时通过分析解析规则的内在逻辑,发现可以自定义标签、自定义属性名等等,在实际运行时,也发现了相比于y4tacker师傅的jsp webshell,采用自定义标签的方式不存在有错误回显提示,能够更好的隐藏自身



参考

http://www.lvyyevd.cn/archives/tomcat%E4%B8%8B%E7%9A%84%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0rce%E5%A7%BF%E5%8A%BF

https://y4tacker.github.io/2022/02/03/year/2022/2/jsp%E6%96%B0webshell%E7%9A%84%E6%8E%A2%E7%B4%A2%E4%B9%8B%E6%97%85%2F%23%E6%B5%81%E7%A8%8B&%E5%AE%9E%E7%8E%B0%E6%9E%84%E9%80%A0Webshell

https://y4tacker.github.io/2022/02/03/year/2022/2/jsp%E6%96%B0webshell%E7%9A%84%E6%8E%A2%E7%B4%A2%E4%B9%8B%E6%97%85/#%E5%8F%91%E7%8E%B0

https://blog.csdn.net/qq_44377709/article/details/122652081

http://www.lvyyevd.cn/archives/tomcat%e4%b8%8b%e7%9a%84%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0rce%e5%a7%bf%e5%8a%bf

0 条评论
某人
表情
可输入 255