原文始发于先知社区(1z520520):Tapestry4 RCE分析
前言
遇到有人问起这个漏洞,我去查了下Tapestry 4,这尼玛在2008年就停止更新了,现在是5,这算是一个上古时代的框架了,他类似于springMVC,也是有一个特殊的servlet做请求处理分发。
基本找不到这个漏洞的分析,只有一段关于该漏洞的描述( 2020年12月8),source是sp参数,sink看描述应该是readObject
https://lists.apache.org/thread/mcl3xzw50vjb7rv76nsgq5zorhbg5gyy
在 Apache Tapestry 4 中发现了一个 Java 序列化漏洞。Apache Tapestry 4 甚至会尝试反序列化“sp”参数在调用页面的 validate 方法之前,导致反序列化无需身份验证。
环境搭建
要测试漏洞,得找个环境,这里搜了一篇文章,顺利搭建成功
https://blog.csdn.net/lovebunny/article/details/83222881
几个注意事项吧
- Tapestry4 在 github有份源码,但我用mvn死活编译不出来,幸好maven仓库有,项目直接引用就行了。
- 因为类似springMVC,可以用tomcat、resin等容器部署,我选择了tomcat,web.xml如下
<?xml version="1.0"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>ApplicationServlet</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ApplicationServlet</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
</web-app>
- IDEA里调试,我使用jdk1.8跑不起来,换成1.6就ok了
- 这里不用修改,就根目录就行,context路径在run configurations里配置
- tapestry4一般配置一个页面,有三个文件,模板文件[name].html,page文件[name].page,以及处理类,整个调用过程:Home.html -> Home.page -> Home.class;html模板文件和page文件名字一定要一致,这是自动关联的,而处理类名字没有要求,是显示和page文件关联
- 默认请求是访问的Home.html,所以一般来说这个文件是必须有的
漏洞分析
sp及路由分析
- 首先搜了下sp参数,他是被定义成一个变量PARAMETER了,简单看了下sp是service parameter的缩写。
在tapestry4-trunk\tapestry-framework\src\java\org\apache\tapestry\engine\ExternalService.java里看到sp的参考使用,大概就是每个service服务的传参,那这个service是怎么选择的呢。
/app?service=external/[PageName]&sp=[param 0]&sp=param[1]
这篇文章有提到一些 https://blog.csdn.net/CSDN__Java/article/details/49100277,这里用的service是page
再仔细看看/app?service=external&sp=[param 0]对应的刚好是类名前缀小写,是IEngineService
接口的实现
这个接口有很多实现类,包括page啥的
尝试构造访问,可以看到和我们默认访问的页面是一样的,说明默认就是PageService请求。
而service=external也其实可以访问到ExternalService
这里提示home未实现IExternalPage
因为我Home.class是IPage的实现,正常开发external服务,就实现IExternalPage即可
接口的描述,getLink主要用于构建一个service的URL,而service方法就是处理请求,并返回一个page。
ExternalService在getLink里有获取sp参数,但只是构造link,而service里却没看到获取sp参数的代码
Object[] parameters = this._linkFactory.extractListenerParameters(cycle);
内部是有sp参数获取的。(上面说是用PARAMETER,这里是找的编译好的jar包,所以是sp)
关于sp参数的调用,大概搜了下源码,先排除getLink里的
xxx-call->LinkFactoryImpl.extractListenerParameters
// 下面这两个后面搜索了下,XTileService和ViewPageEncoder不在framwork里
XTileService.service
ViewPageEncoder.encode
tabby搜索
用tabby搜索一下调用
match (:Class)-[:HAS]->(source:Method {SUB_SIGNATURE: "void service(org.apache.tapestry.IRequestCycle)"})
match (sink:Method {NAME: "extractListenerParameters"})
call apoc.algo.allSimplePaths(source,sink, "CALL>|ALIAS>", 10) yield path
return path limit 20
有几个IEngineService实现类是有调用的
org.apache.tapestry.engine.DirectEventService
org.apache.tapestry.engine.ExternalService
org.apache.tapestry.engine.DirectService
然后就可以进一步搜索extractListenerParameters
方法是否有进一步的利用,如下搜索,可以发现确实有一条调用readObject的调用链。
PS: 这里用ALIAS避免一些接口实现调用的问题,可以看到如下确实有接口实现问题,没有ALIAS是找不到完整利用链的。
match (:Class)-[:HAS]->(source:Method {SUB_SIGNATURE: "java.lang.Object[] extractListenerParameters(org.apache.tapestry.IRequestCycle)"})
match (sink:Method {NAME: "readObject"})
call apoc.algo.allSimplePaths(source,sink, "CALL>|ALIAS>", 10) yield path
return path limit 20
调用链打印如下,最终是在SerializableAdaptor里调用了readObject
match (:Class)-[:HAS]->(source:Method {SUB_SIGNATURE: "java.lang.Object[] extractListenerParameters(org.apache.tapestry.IRequestCycle)"})
match (sink:Method {NAME: "readObject"})
call apoc.algo.allSimplePaths(source,sink, "CALL>|ALIAS>", 10) yield path
return [n in nodes(path) | n.CLASSNAME +"#" + n.NAME] limit 1
"org.apache.tapestry.services.impl.LinkFactoryImpl#extractListenerParameters"
"org.apache.tapestry.services.DataSqueezer#unsqueeze"
"org.apache.tapestry.util.io.DataSqueezerImpl#unsqueeze"
"org.apache.tapestry.util.io.DataSqueezerImpl#unsqueeze"
"org.apache.tapestry.util.io.SqueezeAdaptor#unsqueeze"
"org.apache.tapestry.util.io.SerializableAdaptor#unsqueeze"
"java.io.ObjectInputStream#readObject"
利用链构造
对于传入的encoded,base64解码直接反序列化,接下来就看是否可以触发利用了。
上面三个source,找一个比如DirectService.class,如下,想走到extractListenerParameters
,那么他对应的page页面中,应该存在IDirect组件,component是tapestry里的一个概念,是一组特殊的标签,用来动态生成页面。
如下jwcid里就设置了组件类型,内置的几个常用组件有@PageLink
、@DirectLink
、@Insert
、@Form
,这里没有指定组件名,一般格式是test@DirectLink
,那么组件名就是test,下面例子是匿名组件,需要调用的话,传参如component=$Form
而DrectService需要的组件类型必须是实现了IDirect,如下Form和DirectLink都行。
尝试传参service=direct&sp=123&page=Form&component=%24Form
,成功进入下面。
进一步到如下位置,这个函数就是对参数解码的,可以看到他会根据首字母-33
来判断使用哪个adaptor进行解码
我们需要的SerializableAdaptor
也是其中一个,所以接下来就需要知道this._adaptorByPrefix
哪个索引对应的是SerializableAdaptor
如下46和57对应的就是SerializableAdaptor
,也就是传入79=O或90=Z
为啥有两个值其实之前已经提示了,传入Z则会进行GZIP解压,O则只是Base64
接着就可以构造反序列化数据了,先看下默认依赖,可以看到里面有commons-beanutils,如此甚好。
这里不用GZIP,简单Base64编码,发送,触发利用,进一步回显、内存马就是一些常规操作了。
利用条件
上面只测试了DirectService的Form组件,其实sp想要正确传参,Form组件需要设置允许提交String类型,否则会存在sp转换报错。
总结下
DirectService
- 页面中存在Form或DirectLink组件,也就是jwcid设置,并且知道componentId
- 组件设置允许用户输入String类型数据
那么黑盒如何找Form这种页面了,其实这种页面很正常,在Tapestry里,遇到表单提交的场景,基本都是用的Form,比如我本地测试的
再看一下html代码,有几个hidden的属性,根据seedids、component这些就能确认目标是否使用了Form,并且可以根据这个判断是Tapestry
总结
这个洞其实看分析,调用链比较明显,但其实蛮多坑的,最开始自己搭建Tapestry环境,存在不少问题,Form组件要能够接收用户输入,这个还是找了个demo才搞定的。
最开始分析这个洞的时候,没有补丁也没有分析,调了一会没耐心就放弃了,害不知道是不是太晚了、还是因为项目没结束,总感觉太急躁了,其实仔细看看他的使用文档,应该能很快找到sp参数的利用,棋差一步,搞研究还是得静下心来。
还有tabby还是蛮好用的,之前没发现利用链也是因为忘了ALIAS,算是又学习了一些cypher的使用技巧吧。之前使用tabby太过粗糙,因为他还没有污点跟踪,所以不能完全依赖他做分析,像这个漏洞,最好手动搜索下sp参数的相关方法,这些方法是否可被路由到,这时候就需要学习应用框架,比如Tapestry里的service,然后找到sp可用方法,进一步搜索是否存在到sink(readObject)的调用链,这样就能比较快速找到利用链了。
这个方法是在知道source的情况下去分析的,一般来说需要先人工分析sink,最好是项目里的sink(也就是对更底层sink的封装),对一些可用的项目类中的方法做提取,然后以这些方法为sink,可以大大缩小范围。然后再看端点到这些sink的调用链是否存在。
如果找项目类中sink太麻烦,也可以先结合tabby去筛选,找到可用sink。
补充
关于tapestry里的一些问题,比如某个类为什么无法调试,他很可能是tapestry动态创建了你当前类的子类
参考