java安全(五)java反序列化
給個關(guān)注?寶兒!
給個關(guān)注?寶兒!
給個關(guān)注?寶兒!
關(guān)注公眾號:b1gpig信息安全,文章推送不錯過
1. 序列化
在調(diào)用RMI時,發(fā)現(xiàn)接收發(fā)送數(shù)據(jù)都是反序列化數(shù)據(jù).
例如JSON和XML等語言,在網(wǎng)絡(luò)上傳遞信息,都會用到一些格式化數(shù)據(jù),大多數(shù)處理方法中,JSON和XML支持的數(shù)據(jù)類型就是基本數(shù)據(jù)類型,整型、浮點型、字符串、布爾等,如果開發(fā)者希望在傳輸數(shù)據(jù)的時候直接傳輸一個對象,那么就不得不想辦法擴展基礎(chǔ)的JSON(XML)語法。比如,Jackson和Fastjson這類序列化庫,在JSON(XML)的基礎(chǔ)上進行改造,通過特定的語法來傳遞對象.
RMI使用java等語言內(nèi)置的序列化方法,將一個對象轉(zhuǎn)化成一串二進制數(shù)據(jù)進行傳輸
2.反序列化
不管是Jackson、Fastjson還是編程語言內(nèi)置的序列化方法,一旦涉及到序列化與反序列化數(shù)據(jù),就可能會涉及到安全問題。但首先要理解的是,“反序列化漏洞”是對一類漏洞的泛指,而不是專指某種反序列化方法導(dǎo)致的漏洞,比如Jackson反序列化漏洞和Java readObject造成的反序列化漏洞就是完全不同的兩種漏洞。
在Java中實現(xiàn)對象反序列化非常簡單,實現(xiàn)java.io.Serializable(內(nèi)部序列化)java.io.Externalizable(外部序列化)接口即可被序列化,其中java.io.Externalizable接口只是實現(xiàn)了java.io.Serializable接口。
反序列化類對象時有如下限制:
1.被反序列化的類必須存在。
2. serialVersionUID值必須一致。
除此之外,反序列化類對象是不會調(diào)用該類構(gòu)造方法的,因為在反序列化創(chuàng)建類實例時使用了sun.reflect.ReflectionFactory.newConstructorForSerialization創(chuàng)建了一個反序列化專用的Constructor(反射構(gòu)造方法對象),使用這個特殊的Constructor可以繞過構(gòu)造方法創(chuàng)建類實例(前面章節(jié)講sun.misc.Unsafe 的時候我們提到了使用allocateInstance方法也可以實現(xiàn)繞過構(gòu)造方法創(chuàng)建類實例)。
3.反序列化方法的對比
在接觸Java反序列化之前,相比大家多少都了解過其他語言的反序列化漏洞,其中極為經(jīng)典的要數(shù)PHP
和Python。
那么,Java的反序列化,究竟和PHP、Python的反序列化有什么異同?
Java的反序列化和PHP的反序列化其實有點類似,他們都只能將一個對象中的屬性按照某種特定的格式
生成一段數(shù)據(jù)流,在反序列化的時候再按照這個格式將屬性拿回來,再賦值給新的對象。
但Java相對PHP序列化更深入的地方在于,其提供了更加高級、靈活地方法 writeObject ,允許開發(fā)者
在序列化流中插入一些自定義數(shù)據(jù),進而在反序列化的時候能夠使用 readObject 進行讀取。
當然,PHP中也提供了一個魔術(shù)方法叫 __wakeup ,在反序列化的時候進行觸發(fā)。很多人會認為Java的 readObject 和PHP的 __wakeup 類似,但其實不全對,雖然都是在反序列化的時候觸發(fā),但他們解決
的問題稍微有些差異。
Java設(shè)計 readObject 的思路和PHP的 __wakeup 不同點在于:
readObject 傾向于解決“反序列化時如
何還原一個完整對象”這個問題,而PHP的 __wakeup 更傾向于解決“反序列化后如何初始化這個對象”的
問題。
4.PHP反序列化
PHP的序列化是開發(fā)者不能參與的,開發(fā)者調(diào)用 serialize 函數(shù)后,序列化的數(shù)據(jù)就已經(jīng)完成了,你得到的是一個完整的對象,你并不能在序列化數(shù)據(jù)流里新增某一個內(nèi)容,你如果想插入新的內(nèi)容,只有將其保存在一個屬性中。也就是說PHP的序列化、反序列化是一個純內(nèi)部的過程,而其 __sleep 、 __wakeup 魔術(shù)方法的目的就是在序列化、反序列化的前后執(zhí)行一些操作。
一個非常典型的PHP序列化例子,就是含有資源類型的PHP類,如數(shù)據(jù)庫連接:
<?php class Connection { protected $link; private $dsn, $username, $password; public function __construct($dsn, $username, $password) { $this->dsn = $dsn; $this->username = $username; $this->password = $password; $this->connect(); } private function connect() { $this->link = new PDO($this->dsn, $this->username, $this- >password); } }PHP中,資源類型的對象默認是不會寫入序列化數(shù)據(jù)中的。那么上述Connection類的 $link 屬性在序
列化后就是null,反序列化時拿到的也是null。
那么,如果我想要反序列化時拿到的 $link 就是一個數(shù)據(jù)庫連接,我就需要編寫 __wakeup 方法:
可見,這里 __wakeup 的工作就是在反序列化拿到Connection對象后,執(zhí)行 connect() 函數(shù),連接數(shù)
據(jù)庫。
__wakeup 的作用在反序列化后,執(zhí)行一些初始化操作。但其實我們很少利用序列化數(shù)據(jù)傳遞資源類型
的對象,而其他類型的對象,在反序列化的時候就已經(jīng)賦予其值了。
所以你會發(fā)現(xiàn),PHP的反序列化漏洞,很少是由 __wakeup 這個方法觸發(fā)的,通常觸發(fā)在析構(gòu)函數(shù)
__destruct 里。其實大部分PHP反序列化漏洞,都并不是由反序列化導(dǎo)致的,只是通過反序列化可以
控制對象的屬性,進而在后續(xù)的代碼中進行危險操作。
5.Java反序列化
在Java中實現(xiàn)對象反序列化非常簡單,實現(xiàn)java.io.Serializable(內(nèi)部序列化)java.io.Externalizable(外部序列化)接口即可被序列化,其中java.io.Externalizable接口只是實現(xiàn)了java.io.Serializable接口。
反序列化類對象時有如下限制:
1.被反序列化的類必須存在。
2. serialVersionUID值必須一致。
除此之外,反序列化類對象是不會調(diào)用該類構(gòu)造方法的,因為在反序列化創(chuàng)建類實例時使用了sun.reflect.ReflectionFactory.newConstructorForSerialization創(chuàng)建了一個反序列化專用的Constructor(反射構(gòu)造方法對象),使用這個特殊的Constructor可以繞過構(gòu)造方法創(chuàng)建類實例(前面章節(jié)講sun.misc.Unsafe 的時候我們提到了使用allocateInstance方法也可以實現(xiàn)繞過構(gòu)造方法創(chuàng)建類實例)。
使用反序列化方式創(chuàng)建類實例代碼片段:
package com.anbai.sec.serializes;import sun.reflect.ReflectionFactory;import java.lang.reflect.Constructor;/*** 使用反序列化方式在不調(diào)用類構(gòu)造方法的情況下創(chuàng)建類實例* https://www.iteye.com/topic/850027*/ public class ReflectionFactoryTest {public static void main(String[] args) {try {// 獲取sun.reflect.ReflectionFactory對象ReflectionFactory factory = ReflectionFactory.getReflectionFactory();// 使用反序列化方式獲取DeserializationTest類的構(gòu)造方法Constructor constructor = factory.newConstructorForSerialization(DeserializationTest.class, Object.class.getConstructor());// 實例化DeserializationTest對象System.out.println(constructor.newInstance());} catch (Exception e) {e.printStackTrace();}}}輸出
6.ObjectInputStream、ObjectOutputStream
java.io.ObjectOutputStream類最核心的方法是writeObject方法,即序列化類對象。
java.io.ObjectInputStream類最核心的功能是readObject方法,即反序列化類對象。
所以,只需借助ObjectInputStream和ObjectOutputStream類我們就可以實現(xiàn)類的序列化和反序列化功能了。
7.java.io.Serializable
java.io.Serializable是一個空的接口,我們不需要實現(xiàn)java.io.Serializable的任何方法,代碼如下:
public interface Serializable { }您可能會好奇我們實現(xiàn)一個空接口有什么意義?其實實現(xiàn)java.io.Serializable接口僅僅只用于標識這個類可序列化。實現(xiàn)了java.io.Serializable接口的類原則上都需要生產(chǎn)一個serialVersionUID常量,反序列化時如果雙方的serialVersionUID不一致會導(dǎo)致InvalidClassException 異常。如果可序列化類未顯式聲明 serialVersionUID,則序列化運行時將基于該類的各個方面計算該類的默認 serialVersionUID值。
DeserializationTest.java測試代碼如下:
package com.anbai.sec.serializes;import java.io.*; import java.util.Arrays;/*** Creator: yz* Date: 2019/12/15*/ public class DeserializationTest implements Serializable {private String username;private String email;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public static void main(String[] args) {ByteArrayOutputStream baos = new ByteArrayOutputStream();try {// 創(chuàng)建DeserializationTest類,并類設(shè)置屬性值DeserializationTest t = new DeserializationTest();t.setUsername("yz");t.setEmail("admin@.com");// 創(chuàng)建Java對象序列化輸出流對象ObjectOutputStream out = new ObjectOutputStream(baos);// 序列化DeserializationTest類out.writeObject(t);out.flush();out.close();// 打印DeserializationTest類序列化以后的字節(jié)數(shù)組,我們可以將其存儲到文件中或者通過Socket發(fā)送到遠程服務(wù)地址System.out.println("DeserializationTest類序列化后的字節(jié)數(shù)組:" + Arrays.toString(baos.toByteArray()));// 利用DeserializationTest類生成的二進制數(shù)組創(chuàng)建二進制輸入流對象用于反序列化操作ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());// 通過反序列化輸入流(bais),創(chuàng)建Java對象輸入流(ObjectInputStream)對象ObjectInputStream in = new ObjectInputStream(bais);// 反序列化輸入流數(shù)據(jù)為DeserializationTest對象DeserializationTest test = (DeserializationTest) in.readObject();System.out.println("用戶名:" + test.getUsername() + ",郵箱:" + test.getEmail());// 關(guān)閉ObjectInputStream輸入流in.close();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}輸出:
核心邏輯其實就是使用ObjectOutputStream類的writeObject方法序列化DeserializationTest類,使用ObjectInputStream類的readObject方法反序列化DeserializationTest類而已。
簡化后的代碼片段如下:
// 序列化DeserializationTest類 ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(t);// 反序列化輸入流數(shù)據(jù)為DeserializationTest對象 ObjectInputStream in = new ObjectInputStream(bais); DeserializationTest test = (DeserializationTest) in.readObject();ObjectOutputStream序列化類對象的主要流程是首先判斷序列化的類是否重寫了writeObject方法,如果重寫了就調(diào)用序列化對象自身的writeObject方法序列化,序列化時會先寫入類名信息,其次是寫入成員變量信息(通過反射獲取所有不包含被transient修飾的變量和值)。
8.java.io.Externalizable.java:
public interface Externalizable extends java.io.Serializable {void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;}ExternalizableTest.java測試代碼如下:
package com.anbai.sec.serializes;import java.io.*; import java.util.Arrays;/*** Creator: yz* Date: 2019/12/15*/ package com.anbai.sec.serializes;import java.io.*; import java.util.Arrays;/*** Creator: yz* Date: 2019/12/15*/ public class ExternalizableTest implements java.io.Externalizable {private String username;private String email;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(username);out.writeObject(email);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {this.username = (String) in.readObject();this.email = (String) in.readObject();}public static void main(String[] args) {ByteArrayOutputStream baos = new ByteArrayOutputStream();try {// 創(chuàng)建ExternalizableTest類,并類設(shè)置屬性值ExternalizableTest t = new ExternalizableTest();t.setUsername("yz");t.setEmail("admin@javaweb.org");ObjectOutputStream out = new ObjectOutputStream(baos);out.writeObject(t);out.flush();out.close();// 打印ExternalizableTest類序列化以后的字節(jié)數(shù)組,我們可以將其存儲到文件中或者通過Socket發(fā)送到遠程服務(wù)地址System.out.println("ExternalizableTest類序列化后的字節(jié)數(shù)組:" + Arrays.toString(baos.toByteArray()));System.out.println("ExternalizableTest類反序列化后的字符串:" + new String(baos.toByteArray()));// 利用DeserializationTest類生成的二進制數(shù)組創(chuàng)建二進制輸入流對象用于反序列化操作ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());// 通過反序列化輸入流創(chuàng)建Java對象輸入流(ObjectInputStream)對象ObjectInputStream in = new ObjectInputStream(bais);// 反序列化輸入流數(shù)據(jù)為ExternalizableTest對象ExternalizableTest test = (ExternalizableTest) in.readObject();System.out.println("用戶名:" + test.getUsername() + ",郵箱:" + test.getEmail());// 關(guān)閉ObjectInputStream輸入流in.close();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}輸出:
兩者之間沒有多大差別
9.自定義序列化(writeObject)和反序列化(readObject)
實現(xiàn)了java.io.Serializable接口的類,還可以定義如下方法(反序列化魔術(shù)方法),這些方法將會在類序列化或反序列化過程中調(diào)用:
具體的方法名定義在java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>),其中方法有詳細的聲明。
序列化時可自定義的方法示例代碼:
public class DeserializationTest implements Serializable {/*** 自定義反序列化類對象** @param ois 反序列化輸入流對象* @throws IOException IO異常* @throws ClassNotFoundException 類未找到異常*/private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {System.out.println("readObject...");// 調(diào)用ObjectInputStream默認反序列化方法ois.defaultReadObject();// 省去調(diào)用自定義反序列化邏輯...}/*** 自定義序列化類對象** @param oos 序列化輸出流對象* @throws IOException IO異常*/private void writeObject(ObjectOutputStream oos) throws IOException {oos.defaultWriteObject();System.out.println("writeObject...");// 省去調(diào)用自定義序列化邏輯...}private void readObjectNoData() {System.out.println("readObjectNoData...");}/*** 寫入時替換對象** @return 替換后的對象*/protected Object writeReplace() {System.out.println("writeReplace....");return null;}protected Object readResolve() {System.out.println("readResolve....");return null;}}當我們對DeserializationTest類進行序列化操作時,會自動調(diào)用(反射調(diào)用)該類的writeObject(ObjectOutputStream oos)方法,對其進行反序列化操作時也會自動調(diào)用該類的readObject(ObjectInputStream)方法,也就是說我們可以通過在待序列化或反序列化的類中定義readObject和writeObject方法,來實現(xiàn)自定義的序列化和反序列化操作,當然前提是,被序列化的類必須有此方法,并且方法的修飾符必須是private。
總結(jié)
以上是生活随笔為你收集整理的java安全(五)java反序列化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 淘宝上可以借钱吗
- 下一篇: 海南大学计算机原理,海南大学微机原理课件