Apache Tapestry4 反序列化
date
Apr 4, 2022
slug
Apache-Tapestry4-deserialize
status
Published
tags
Java安全
安全研究
summary
陈师傅知识星球里面看到的,刚好有空跟着学习一下,学习的本质了
type
Post
Tapestry 4,在2008年就停止更新了,现在是5,这算是一个上古时代的框架了,他类似于springMVC,也是有一个特殊的servlet做请求处理分发。
基本找不到这个漏洞的分析,只有一段关于该漏洞的描述( 2020年12月8),source是sp参数,sink看描述应该是readObject
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fe7bead66-ce1d-40db-9613-a6888f90bf25%2FUntitled.png%3Fid%3D1fce54b8-b774-4e35-99b0-e61dc3554460%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DVLnTRHNNKfa3M2wLmAnV2QBUGTFk_bLbxzq4e1l8MDA?table=block&id=1fce54b8-b774-4e35-99b0-e61dc3554460&cache=v2)
自己搭建环境
用的JDK 7 + Tomcat 8环境搭建的
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>trs4de</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>trs4de Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.apache.tapestry</groupId>
<artifactId>tapestry-framework</artifactId>
<version>4.1.6</version>
</dependency>
</dependencies>
<build>
<finalName>trs4de</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
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>
配置 Tapestry4 应用环境
Tapestry4一般配置一个页面,有三个文件
- 模版文件 [name].html : webapp 目录下,必须叫 Home.html,Tapestry 程序入口
- page文件[name].page:WEB-INF 目录下,需要与模版文件一致,自动关联
- 处理类 [name].class:无其它要求,需与 page 中内容关联
Home.html
<span jwcid ="@Insert" value ="ognl:Tapestry" />
Home.page
<?xml version="1.0"?>
<!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 4.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<page-specification class ="cn.oversec.Home"> </page-specification>
Home.class
package cn.d4rksec;
import org.apache.tapestry.html.BasePage;
public abstract class Home extends BasePage {
public String getTapestry(){
return "hello ,Tapestry4..";
}
}
使用Tomcat启动
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F2f7596ed-bdf9-4813-9b53-1eeb443ece25%2FUntitled.png%3Fid%3Db1f9e048-2f36-41e1-8889-d46271db7a35%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3D4edHYYCibvRz5B2OHS6gOrj3CJ_UkUh0gdcPQdK1VRI?table=block&id=b1f9e048-2f36-41e1-8889-d46271db7a35&cache=v2)
直接食用环境
为什么要用到这个环境,因为这个环境满足了漏洞的触发条件是一个表格提交的内容:
但是这个环境死活不能直接起来,后来是一个个文件copy过来的,重新整起来的
记得pom文件里面加上这两个内容,保证Tomcat可以正确解析,无语了,这个搞了半天,没有这两个Tomcat会报一堆乱七八糟的错误
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
终于搭建起来了,原来是IDEA的bug
漏洞分析
审计通常采用正向、反向、混合,在知道漏洞触发点的情况下,采用反向审计方式往往会事半功倍,但是对于分析一个新接触的事物而言,大部分人采用这样的分析思路可能只知其然而不知其所以然(如在哪里下断点,为什么要在这里下断点),所以在分析一个已知的漏洞情况下怎么断点执行到框架内并且定位到反序列化过程才是漏洞分析的意义。
从web.xml 里面找到了路由的相关定义,找到了这个框架的入口点
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fe4ba5ec2-7481-4af3-bc29-f220ed73449e%2FUntitled.png%3Fid%3D8de1df68-6ba4-415e-8f60-e89f07cd416b%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DaG7UfqNlppBFDENHqtePgJ1_AZEt9-JA4A5I4MXeij4?table=block&id=8de1df68-6ba4-415e-8f60-e89f07cd416b&cache=v2)
继续跟进到service函数里面,request参数进入到
_webRequestServicer.service
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F852edb1f-d552-4daf-83fa-522aa0cc66f5%2FUntitled.png%3Fid%3D5596d033-71b5-407b-9ff9-1997706f44a5%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DrryupgGuu5G-1_7y-HVlMym8bnAnUBtCeYkM4azETsY?table=block&id=5596d033-71b5-407b-9ff9-1997706f44a5&cache=v2)
org.apache.tapestry.services.impl.InvokeEngineTerminator#service
→ org.apache.tapestry.engine.AbstractEngine#service
然后获取service参数内容,进行具体的操作![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F40cd20ba-224b-4db1-bbad-492a2651da76%2FUntitled.png%3Fid%3D114def75-5408-40e9-a92d-e2d41576c3d2%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3Dzrnl2IoJW2QAn3Ypd93CzbgrMTxLV2zeIuoX13s2klc?table=block&id=114def75-5408-40e9-a92d-e2d41576c3d2&cache=v2)
但在
org.apache.tapestry.engine.AbstractEngine#service
里面更加需要注意的是cycle = this._infrastructure.getRequestCycleFactory().newRequestCycle(this)
这一句代码,这一句代码newRequestCycle
函数则是处理了全局的request变量,使用了一个Map去封装![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F97964833-51f9-4bc0-ae22-47bc2c4d1dd2%2FUntitled.png%3Fid%3D945873f1-f4c1-48a6-8bb3-004d6072ab3c%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DEnfZLCJet8wGg0A0YssC9_MWc3hZgr8XAiYe2NO4Ppo?table=block&id=945873f1-f4c1-48a6-8bb3-004d6072ab3c&cache=v2)
这上面的findService函数同样也是从request里面获取内容的
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Ffc5c85e4-967f-421a-9be2-3f26f57814a1%2FUntitled.png%3Fid%3D1a67e9df-6ad0-4840-b19c-191852671197%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DYTVryQsd8n2HHHmIgzQUf9JRyrUq5kjQ09fwqfb8yjI?table=block&id=1a67e9df-6ad0-4840-b19c-191852671197&cache=v2)
其实这个
newRequestCycle
函数返回的内容是IRequestCycle
这个类,可以从这个类里面的getter函数看出他能获取一些什么内容,也就对应cycle变量可以操作的内容了![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fd7fb4328-b17d-4ded-8c66-8d27f0230a5f%2FUntitled.png%3Fid%3D26951175-675c-408a-92f9-c5c61ee14507%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DTXaTbznKnz29JpTlSDdr0V0FvhuXCI6Ih13ocU-FNlA?table=block&id=26951175-675c-408a-92f9-c5c61ee14507&cache=v2)
回到
org.apache.tapestry.engine.AbstractEngine#service
继续跟进service.service(cycle)
,在org.apache.tapestry.engine.DirectService#service
函数里面获取cycle里面的各个参数值,进一步处理,到这里路由部分传进来的内容已经处理得差不多了![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F2b051376-28a4-4877-9275-932e410f88a8%2FUntitled.png%3Fid%3D902a3c9f-1d2f-4a6b-880c-bf207ba20726%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3Dzwb5bi91m8OReJvH6W38iBWu5C-W7kL-m6i1VQVMpiA?table=block&id=902a3c9f-1d2f-4a6b-880c-bf207ba20726&cache=v2)
加载 Page 并从 IPage 对象中获取 componentId ,在该方法中 getNestedComponent()
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fd2083c17-2560-40a2-abc3-97e7f2169752%2FUntitled.png%3Fid%3D7f0d3682-c0c9-46d7-b717-9feefb76cf78%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3D7FtAz5rO8K_3XG0E0JRELZe9FURuRnQ84x7q33eXwyI?table=block&id=7f0d3682-c0c9-46d7-b717-9feefb76cf78&cache=v2)
进去之后可以发现该函数参数是$Form ,这里面还涉及到
getComponent
函数,这个函数是用来查找之前页面加载存入的 jwcid 组件,如传入的请求无对应 jwcid,result 则为null 并抛出 ApplicationRuntimeException 异常![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F21a54e6e-a3ce-496e-aa13-d70dc7f1abb0%2FUntitled.png%3Fid%3D4fd90228-5f8f-4c9c-a863-f4ce6574d91a%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DltsooikIOWJ3w1V23x91WffkSwE--vJCkVZDNIPu1nI?table=block&id=4fd90228-5f8f-4c9c-a863-f4ce6574d91a&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F82d58e1f-83aa-44b5-89e6-5efc47150332%2FUntitled.png%3Fid%3D4b912b2d-488e-41d0-8594-4d6386cf0e93%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DVs7OlsY93ZDZw2PfjuYTYIkGCnCLvkkm47AtcgclYuI?table=block&id=4b912b2d-488e-41d0-8594-4d6386cf0e93&cache=v2)
后续返回到
org/apache/tapestry/tapestry-framework/4.1.2/tapestry-framework-4.1.2.jar!/org/apache/tapestry/engine/DirectService.class:85
执行org.apache.tapestry.services.LinkFactory#extractListenerParameters
,到了这个函数就开始获取sp参数了,也就是那个截图上说的触发点sp参数![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F2cf77ae0-da1e-424a-83c1-777c2809b6c4%2FUntitled.png%3Fid%3Df5129241-c21d-47f7-a55c-8032052c0c07%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3D_TRbVRCve0OhL9SdHpb566ujNVu_b8ziwbHYD_5L4d8?table=block&id=f5129241-c21d-47f7-a55c-8032052c0c07&cache=v2)
继续跟,会发现执行到
org.apache.tapestry.util.io.DataSqueezerImpl#unsqueeze(java.lang.String)
函数![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fe0ce8c6e-4b41-459c-8b53-8a4342bc510a%2FUntitled.png%3Fid%3D113eb99c-d28a-44fe-8d8b-2af2bd56c5b0%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3D8HstlWhirmv1jjJDKAY1Y3eMMV8tlUfFuUP5wa0arbU?table=block&id=113eb99c-d28a-44fe-8d8b-2af2bd56c5b0&cache=v2)
此处获取了 sp 参数中的第一位字符并减去33,来决定该字符串交给this._adaptorByPrefix 中的谁进行处理
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fd983e6c9-b8bd-4eb5-999e-5dcc0f336b99%2FUntitled.png%3Fid%3Db572e41c-290c-4c76-b7e1-076f76a6b6c8%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3DgJlL5Z_rSYZEorZGIoNZC9egWmVqPjIFKKMEkFXCQ58?table=block&id=b572e41c-290c-4c76-b7e1-076f76a6b6c8&cache=v2)
其中 46 和 57 对应 org.apache.tapestry.util.io.SerializableAdaptor 类,传入 46+33 / 57+33 对应的 Ascii 会走到该类中 unsqueeze() 方法中,分别为 O / Z
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fe751f4a4-9512-4837-9699-183484e07bf5%2FUntitled.png%3Fid%3D88439e25-2811-4c16-b593-b623fbbf7324%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3D8_cleVZedyZ9Y5od6e28nCPJ3p5NJ3yVfGtx3FfN354?table=block&id=88439e25-2811-4c16-b593-b623fbbf7324&cache=v2)
也就是构造个链条,可能会有两种情况,一个是直接base64处理,另外一个是GZIP处理,但是他这里的处理流程机会加上一个前缀O/Z ,有点不同,但是这个项目也太老了,导致我的CB链一直都没有触发。。。。不知道啥原因,学习了反序列化数据开头的特殊字符就行
先放着。。。。。
这里还得注意一下利用条件,可能会遇到呢:
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F45d7c98c-80af-4709-909b-e9149c4996b9%2FUntitled.png%3Fid%3D6b414885-14cd-4933-948d-39a84c4ec6ee%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722067200000%26signature%3Dzjkuo9NvLTpAZ3ZYX6ycEcVp-IwHAwLi9RzBiORipKM?table=block&id=6b414885-14cd-4933-948d-39a84c4ec6ee&cache=v2)
小结
这是提供一个提供个RCE的思路以及警觉性的文章例子:
正常遇到 H4sI ,rO0A开可以联想到反序列化,可以去尝试一下