java反序列化原理-Demo(二)
0x00 測試代碼以及運行結果
測試代碼:
package test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ReflectionPlay implements Serializable{ public static void main(String[] args) throws Exception { new ReflectionPlay().run(); } public void run() throws Exception { byte[] ObjectBytes=serialize(getObject()); deserialize(ObjectBytes); } //在此方法中返回惡意對象 public Object getObject() { String command = "calc.exe"; Object firstObject = Runtime.class; ReflectionObject[] reflectionChains = { /* * Object runtime = Class.forName("java.lang.Runtime").getMethod("getRuntime", new Class[] {}).invoke(null); Class.forName("java.lang.Runtime") .getMethod("exec", String.class) .invoke(runtime,"calc.exe"); * * */ //調用 Runtime.class 的getMethod方法,尋找 getRuntime方法,得到一個Method對象(getRuntime方法) //等同於 Runtime.class.getMethod("getRuntime",new Class[]{String.class,Class[].class}) new ReflectionObject("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), //調用 Method 的 invoker 方法可以得到一個Runtime對象 // 等同於 method.invoke(null),靜態方法不用傳入對象 new ReflectionObject("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), //調用RunTime對象的exec方法,並將 command作為參數執行命令 new ReflectionObject("exec", new Class[]{String.class}, new Object[]{command}) }; return new ReadObject(new ReflectionChains(firstObject, reflectionChains)); } /* * 序列化對象到byte數組 * */ public byte[] serialize(final Object obj) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(out); objOut.writeObject(obj); return out.toByteArray(); } /* * 從byte數組中反序列化對象 * */ public Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { ByteArrayInputStream in = new ByteArrayInputStream(serialized); ObjectInputStream objIn = new ObjectInputStream(in); return objIn.readObject(); } /* * 一個模擬擁有漏洞的類,主要提供的功能是根據自己的屬性中的值來進行反射調用 * */ class ReflectionObject implements Serializable{ private String methodName; private Class[] paramTypes; private Object[] args; public ReflectionObject(String methodName, Class[] paramTypes, Object[] args) { this.methodName = methodName; this.paramTypes = paramTypes; this.args = args; } //根據 methodName, paramTypes 來尋找對象的方法,利用 args作為參數進行調用 public Object transform(Object input) throws Exception { Class inputClass = input.getClass(); return inputClass.getMethod(methodName, paramTypes).invoke(input, args); } } /* * 一個用來模擬提供惡意代碼的類, * 主要的功能是將 ReflectionObject進行串聯調用,與ReflectionObject一起構成漏洞代碼的一部分 * */ class ReflectionChains implements Serializable{ private Object firstObject; private ReflectionObject[] reflectionObjects; public ReflectionChains(Object firstObject, ReflectionObject[] reflectionObjects) {//ReflectionChains構造方法 this.firstObject = firstObject; this.reflectionObjects = reflectionObjects; } public Object execute() throws Exception { Object concurrentObject = firstObject; for (ReflectionObject reflectionObject : reflectionObjects) { concurrentObject = reflectionObject.transform(concurrentObject); System.out.println(concurrentObject); } return concurrentObject; } } /** * 一個等待序列化的類,擁有一個屬性和一個重寫了的readObject方法 * 並且在readObject方法中執行了該屬性的一個方法 * */ class ReadObject implements Serializable { private ReflectionChains reflectionChains; public ReadObject(ReflectionChains reflectionChains) { this.reflectionChains = reflectionChains; } //當反序列化的時候,這個代碼會被調用 //該方法被調用的時候其屬性都是空 private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { try { //用來模擬當readObject的時候,對自身的屬性進行了一些額外的操作 reflectionChains= (ReflectionChains) stream.readFields().get("reflectionChains",null); reflectionChains.execute(); } catch (Exception e) { e.printStackTrace(); } } } }
運行結果:
0x01 測試代碼分析
涉及到的類:
類名 | 是否繼承 Serializable | 方法名 |
---|---|---|
ReflectionObject | 繼承 | transform |
ReflectionChains | 繼承 | execute |
ReadObject | 繼承 | readObject(重寫) |
註:要想產生反序列漏洞,需要重寫readObjec方法
代碼流程:
- serialize(getObject());將一個對象序列化
-
getObject(); 生成一個ReflectionObject數組,包含三個ReflectionObject對象;然後使用ReflectionObject數組生成一個ReflectionChains對象,繼續使用ReflectionChains對象生成一個ReadObject對象
new ReflectionObject("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), //調用 Method 的 invoker 方法可以得到一個Runtime對象 // 等同於 method.invoke(null),靜態方法不用傳入對象 new ReflectionObject("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), //調用RunTime對象的exec方法,並將 command作為參數執行命令 new ReflectionObject("exec", new Class[]{String.class}, new Object[]{command}) }; return new ReadObject(new ReflectionChains(firstObject, reflectionChains));
- deserialize(ObjectBytes);將序列化後的字節反序列化
將傳入的字節進行反序列化,因為傳入的字節是一個ReadObjec對象序列化後的,因此在反序列化時需要調用ReadObjec的readObjec方法(該方法已經被重寫)private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { try { //用來模擬當readObject的時候,對自身的屬性進行了一些額外的操作 reflectionChains= (ReflectionChains) stream.readFields().get("reflectionChains",null); reflectionChains.execute(); } catch (Exception e) { e.printStackTrace(); } }
在這裏我們需要註重看
在生成ReadObject對象時傳入的參數是ReflectionChains對象,所以這裏調用的execute()方法就是ReflectionChains的execute()方法。
接著分析ReflectionChains的execute()方法:public Object execute() throws Exception { Object concurrentObject = firstObject; for (ReflectionObject reflectionObject : reflectionObjects) { concurrentObject = reflectionObject.transform(concurrentObject); System.out.println(concurrentObject); } return concurrentObject; }
關鍵代碼:
由上面可知生成ReflectionChains對象時傳入的參數是ReflectionObject數組,這段代碼的含義便是:遍歷ReflectionObject數組,每一個元素(ReflectionObject)執行transform方法
繼續分析ReflectionObject的transform方法:public Object transform(Object input) throws Exception { Class inputClass = input.getClass(); 得到input對象是那個類的類名 return inputClass.getMethod(methodName, paramTypes).invoke(input, args); 通過類名調用該類的某一方法 }
註意:
通過以上分析可以了解到,進行反序列化的反射鏈為:
deserialize(ObjectBytes); 調用ReadObject類的readObject方法;接下來調用ReflectionChains類的execute方法;接下來通過遍歷ReflectionObject數組調用ReflectionObject類的transform方法
0x02 核心分析
通過以上代碼分析看,最終需要執行的便是以下代碼:
new ReflectionObject("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//調用 Method 的 invoker 方法可以得到一個Runtime對象
// 等同於 method.invoke(null),靜態方法不用傳入對象
new ReflectionObject("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//調用RunTime對象的exec方法,並將 command作為參數執行命令
new ReflectionObject("exec", new Class[]{String.class}, new Object[]{command})
new ReflectionObject("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
等同於執行了:Runtime.class.getMethod("getRuntime",new Class[]{String.class,Class[].class})
new ReflectionObject("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
等同於執行了:method.invoke(null),靜態方法不用傳入對象
new ReflectionObject("exec", new Class[]{String.class}, new Object[]{command})
RunTime對象的exec方法,並將 command作為參數執行命令
第一個對象執行完成:
Runtime.class.getMethod("getRuntime", new Class[0]);
輸出的結果:java.lang.Runtime.getRuntime() 輸出的是一個類
第二個對象執行完成:
Runtime.class.getMethod("getRuntime", new Class[0]).invoke(null, new Object[0]);
輸出結果:java.lang.Runtime@74a14482 輸出的是一個Runtime的對象
第二個對象執行完成:
Runtime t = (Runtime) Runtime.class.getMethod("getRuntime", new Class[0]).invoke(null, new Object[0]);
t.exec("calc.exe")
參考鏈接:http://www.freebuf.com/vuls/170344.html
java反序列化原理-Demo(二)