日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java安全(五)java反序列化

發(fā)布時(shí)間:2025/3/8 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java安全(五)java反序列化 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

給個(gè)關(guān)注?寶兒!
給個(gè)關(guān)注?寶兒!
給個(gè)關(guān)注?寶兒!
關(guān)注公眾號(hào):b1gpig信息安全,文章推送不錯(cuò)過

1. 序列化

在調(diào)用RMI時(shí),發(fā)現(xiàn)接收發(fā)送數(shù)據(jù)都是反序列化數(shù)據(jù).

例如JSON和XML等語言,在網(wǎng)絡(luò)上傳遞信息,都會(huì)用到一些格式化數(shù)據(jù),大多數(shù)處理方法中,JSON和XML支持的數(shù)據(jù)類型就是基本數(shù)據(jù)類型,整型、浮點(diǎn)型、字符串、布爾等,如果開發(fā)者希望在傳輸數(shù)據(jù)的時(shí)候直接傳輸一個(gè)對(duì)象,那么就不得不想辦法擴(kuò)展基礎(chǔ)的JSON(XML)語法。比如,Jackson和Fastjson這類序列化庫,在JSON(XML)的基礎(chǔ)上進(jìn)行改造,通過特定的語法來傳遞對(duì)象.

RMI使用java等語言內(nèi)置的序列化方法,將一個(gè)對(duì)象轉(zhuǎn)化成一串二進(jìn)制數(shù)據(jù)進(jìn)行傳輸

2.反序列化

不管是Jackson、Fastjson還是編程語言內(nèi)置的序列化方法,一旦涉及到序列化與反序列化數(shù)據(jù),就可能會(huì)涉及到安全問題。但首先要理解的是,“反序列化漏洞”是對(duì)一類漏洞的泛指,而不是專指某種反序列化方法導(dǎo)致的漏洞,比如Jackson反序列化漏洞和Java readObject造成的反序列化漏洞就是完全不同的兩種漏洞。

在Java中實(shí)現(xiàn)對(duì)象反序列化非常簡單,實(shí)現(xiàn)java.io.Serializable(內(nèi)部序列化)java.io.Externalizable(外部序列化)接口即可被序列化,其中java.io.Externalizable接口只是實(shí)現(xiàn)了java.io.Serializable接口。

反序列化類對(duì)象時(shí)有如下限制:
1.被反序列化的類必須存在。
2. serialVersionUID值必須一致。

除此之外,反序列化類對(duì)象是不會(huì)調(diào)用該類構(gòu)造方法的,因?yàn)樵诜葱蛄谢瘎?chuàng)建類實(shí)例時(shí)使用了sun.reflect.ReflectionFactory.newConstructorForSerialization創(chuàng)建了一個(gè)反序列化專用的Constructor(反射構(gòu)造方法對(duì)象),使用這個(gè)特殊的Constructor可以繞過構(gòu)造方法創(chuàng)建類實(shí)例(前面章節(jié)講sun.misc.Unsafe 的時(shí)候我們提到了使用allocateInstance方法也可以實(shí)現(xiàn)繞過構(gòu)造方法創(chuàng)建類實(shí)例)。

3.反序列化方法的對(duì)比

在接觸Java反序列化之前,相比大家多少都了解過其他語言的反序列化漏洞,其中極為經(jīng)典的要數(shù)PHP
和Python。
那么,Java的反序列化,究竟和PHP、Python的反序列化有什么異同?
Java的反序列化和PHP的反序列化其實(shí)有點(diǎn)類似,他們都只能將一個(gè)對(duì)象中的屬性按照某種特定的格式
生成一段數(shù)據(jù)流,在反序列化的時(shí)候再按照這個(gè)格式將屬性拿回來,再賦值給新的對(duì)象。
但Java相對(duì)PHP序列化更深入的地方在于,其提供了更加高級(jí)、靈活地方法 writeObject ,允許開發(fā)者
在序列化流中插入一些自定義數(shù)據(jù),進(jìn)而在反序列化的時(shí)候能夠使用 readObject 進(jìn)行讀取。
當(dāng)然,PHP中也提供了一個(gè)魔術(shù)方法叫 __wakeup ,在反序列化的時(shí)候進(jìn)行觸發(fā)。很多人會(huì)認(rèn)為Java的 readObject 和PHP的 __wakeup 類似,但其實(shí)不全對(duì),雖然都是在反序列化的時(shí)候觸發(fā),但他們解決
的問題稍微有些差異。
Java設(shè)計(jì) readObject 的思路和PHP的 __wakeup 不同點(diǎn)在于:
readObject 傾向于解決“反序列化時(shí)如
何還原一個(gè)完整對(duì)象”這個(gè)問題,而PHP的 __wakeup 更傾向于解決“反序列化后如何初始化這個(gè)對(duì)象”的
問題。

4.PHP反序列化

PHP的序列化是開發(fā)者不能參與的,開發(fā)者調(diào)用 serialize 函數(shù)后,序列化的數(shù)據(jù)就已經(jīng)完成了,你得到的是一個(gè)完整的對(duì)象,你并不能在序列化數(shù)據(jù)流里新增某一個(gè)內(nèi)容,你如果想插入新的內(nèi)容,只有將其保存在一個(gè)屬性中。也就是說PHP的序列化、反序列化是一個(gè)純內(nèi)部的過程,而其 __sleep 、 __wakeup 魔術(shù)方法的目的就是在序列化、反序列化的前后執(zhí)行一些操作。

一個(gè)非常典型的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中,資源類型的對(duì)象默認(rèn)是不會(huì)寫入序列化數(shù)據(jù)中的。那么上述Connection類的 $link 屬性在序
列化后就是null,反序列化時(shí)拿到的也是null。
那么,如果我想要反序列化時(shí)拿到的 $link 就是一個(gè)數(shù)據(jù)庫連接,我就需要編寫 __wakeup 方法:

<?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); } public function __sleep() { return array('dsn', 'username', 'password'); } public function __wakeup() { $this->connect(); }

可見,這里 __wakeup 的工作就是在反序列化拿到Connection對(duì)象后,執(zhí)行 connect() 函數(shù),連接數(shù)
據(jù)庫。
__wakeup 的作用在反序列化后,執(zhí)行一些初始化操作。但其實(shí)我們很少利用序列化數(shù)據(jù)傳遞資源類型
的對(duì)象,而其他類型的對(duì)象,在反序列化的時(shí)候就已經(jīng)賦予其值了。
所以你會(huì)發(fā)現(xiàn),PHP的反序列化漏洞,很少是由 __wakeup 這個(gè)方法觸發(fā)的,通常觸發(fā)在析構(gòu)函數(shù)
__destruct 里。其實(shí)大部分PHP反序列化漏洞,都并不是由反序列化導(dǎo)致的,只是通過反序列化可以
控制對(duì)象的屬性,進(jìn)而在后續(xù)的代碼中進(jìn)行危險(xiǎn)操作。

5.Java反序列化

在Java中實(shí)現(xiàn)對(duì)象反序列化非常簡單,實(shí)現(xiàn)java.io.Serializable(內(nèi)部序列化)java.io.Externalizable(外部序列化)接口即可被序列化,其中java.io.Externalizable接口只是實(shí)現(xiàn)了java.io.Serializable接口。

反序列化類對(duì)象時(shí)有如下限制:
1.被反序列化的類必須存在。
2. serialVersionUID值必須一致。

除此之外,反序列化類對(duì)象是不會(huì)調(diào)用該類構(gòu)造方法的,因?yàn)樵诜葱蛄谢瘎?chuàng)建類實(shí)例時(shí)使用了sun.reflect.ReflectionFactory.newConstructorForSerialization創(chuàng)建了一個(gè)反序列化專用的Constructor(反射構(gòu)造方法對(duì)象),使用這個(gè)特殊的Constructor可以繞過構(gòu)造方法創(chuàng)建類實(shí)例(前面章節(jié)講sun.misc.Unsafe 的時(shí)候我們提到了使用allocateInstance方法也可以實(shí)現(xiàn)繞過構(gòu)造方法創(chuàng)建類實(shí)例)。

使用反序列化方式創(chuàng)建類實(shí)例代碼片段:

package com.anbai.sec.serializes;import sun.reflect.ReflectionFactory;import java.lang.reflect.Constructor;/*** 使用反序列化方式在不調(diào)用類構(gòu)造方法的情況下創(chuàng)建類實(shí)例* https://www.iteye.com/topic/850027*/ public class ReflectionFactoryTest {public static void main(String[] args) {try {// 獲取sun.reflect.ReflectionFactory對(duì)象ReflectionFactory factory = ReflectionFactory.getReflectionFactory();// 使用反序列化方式獲取DeserializationTest類的構(gòu)造方法Constructor constructor = factory.newConstructorForSerialization(DeserializationTest.class, Object.class.getConstructor());// 實(shí)例化DeserializationTest對(duì)象System.out.println(constructor.newInstance());} catch (Exception e) {e.printStackTrace();}}}

輸出

6.ObjectInputStream、ObjectOutputStream

java.io.ObjectOutputStream類最核心的方法是writeObject方法,即序列化類對(duì)象。

java.io.ObjectInputStream類最核心的功能是readObject方法,即反序列化類對(duì)象。

所以,只需借助ObjectInputStream和ObjectOutputStream類我們就可以實(shí)現(xiàn)類的序列化和反序列化功能了。

7.java.io.Serializable

java.io.Serializable是一個(gè)空的接口,我們不需要實(shí)現(xiàn)java.io.Serializable的任何方法,代碼如下:

public interface Serializable { }

您可能會(huì)好奇我們實(shí)現(xiàn)一個(gè)空接口有什么意義?其實(shí)實(shí)現(xiàn)java.io.Serializable接口僅僅只用于標(biāo)識(shí)這個(gè)類可序列化。實(shí)現(xiàn)了java.io.Serializable接口的類原則上都需要生產(chǎn)一個(gè)serialVersionUID常量,反序列化時(shí)如果雙方的serialVersionUID不一致會(huì)導(dǎo)致InvalidClassException 異常。如果可序列化類未顯式聲明 serialVersionUID,則序列化運(yùn)行時(shí)將基于該類的各個(gè)方面計(jì)算該類的默認(rèn) 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對(duì)象序列化輸出流對(duì)象ObjectOutputStream out = new ObjectOutputStream(baos);// 序列化DeserializationTest類out.writeObject(t);out.flush();out.close();// 打印DeserializationTest類序列化以后的字節(jié)數(shù)組,我們可以將其存儲(chǔ)到文件中或者通過Socket發(fā)送到遠(yuǎn)程服務(wù)地址System.out.println("DeserializationTest類序列化后的字節(jié)數(shù)組:" + Arrays.toString(baos.toByteArray()));// 利用DeserializationTest類生成的二進(jìn)制數(shù)組創(chuàng)建二進(jìn)制輸入流對(duì)象用于反序列化操作ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());// 通過反序列化輸入流(bais),創(chuàng)建Java對(duì)象輸入流(ObjectInputStream)對(duì)象ObjectInputStream in = new ObjectInputStream(bais);// 反序列化輸入流數(shù)據(jù)為DeserializationTest對(duì)象DeserializationTest test = (DeserializationTest) in.readObject();System.out.println("用戶名:" + test.getUsername() + ",郵箱:" + test.getEmail());// 關(guān)閉ObjectInputStream輸入流in.close();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}

輸出:

核心邏輯其實(shí)就是使用ObjectOutputStream類的writeObject方法序列化DeserializationTest類,使用ObjectInputStream類的readObject方法反序列化DeserializationTest類而已。

簡化后的代碼片段如下:

// 序列化DeserializationTest類 ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(t);// 反序列化輸入流數(shù)據(jù)為DeserializationTest對(duì)象 ObjectInputStream in = new ObjectInputStream(bais); DeserializationTest test = (DeserializationTest) in.readObject();

ObjectOutputStream序列化類對(duì)象的主要流程是首先判斷序列化的類是否重寫了writeObject方法,如果重寫了就調(diào)用序列化對(duì)象自身的writeObject方法序列化,序列化時(shí)會(huì)先寫入類名信息,其次是寫入成員變量信息(通過反射獲取所有不包含被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ù)組,我們可以將其存儲(chǔ)到文件中或者通過Socket發(fā)送到遠(yuǎn)程服務(wù)地址System.out.println("ExternalizableTest類序列化后的字節(jié)數(shù)組:" + Arrays.toString(baos.toByteArray()));System.out.println("ExternalizableTest類反序列化后的字符串:" + new String(baos.toByteArray()));// 利用DeserializationTest類生成的二進(jìn)制數(shù)組創(chuàng)建二進(jìn)制輸入流對(duì)象用于反序列化操作ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());// 通過反序列化輸入流創(chuàng)建Java對(duì)象輸入流(ObjectInputStream)對(duì)象ObjectInputStream in = new ObjectInputStream(bais);// 反序列化輸入流數(shù)據(jù)為ExternalizableTest對(duì)象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)

實(shí)現(xiàn)了java.io.Serializable接口的類,還可以定義如下方法(反序列化魔術(shù)方法),這些方法將會(huì)在類序列化或反序列化過程中調(diào)用:

  • private void writeObject(ObjectOutputStream oos),自定義序列化。
  • private void readObject(ObjectInputStream ois),自定義反序列化。
  • private void readObjectNoData()。
  • protected Object writeReplace(),寫入時(shí)替換對(duì)象。
  • protected Object readResolve()。
  • 具體的方法名定義在java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>),其中方法有詳細(xì)的聲明。

    序列化時(shí)可自定義的方法示例代碼:

    public class DeserializationTest implements Serializable {/*** 自定義反序列化類對(duì)象** @param ois 反序列化輸入流對(duì)象* @throws IOException IO異常* @throws ClassNotFoundException 類未找到異常*/private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {System.out.println("readObject...");// 調(diào)用ObjectInputStream默認(rèn)反序列化方法ois.defaultReadObject();// 省去調(diào)用自定義反序列化邏輯...}/*** 自定義序列化類對(duì)象** @param oos 序列化輸出流對(duì)象* @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...");}/*** 寫入時(shí)替換對(duì)象** @return 替換后的對(duì)象*/protected Object writeReplace() {System.out.println("writeReplace....");return null;}protected Object readResolve() {System.out.println("readResolve....");return null;}}

    當(dāng)我們對(duì)DeserializationTest類進(jìn)行序列化操作時(shí),會(huì)自動(dòng)調(diào)用(反射調(diào)用)該類的writeObject(ObjectOutputStream oos)方法,對(duì)其進(jìn)行反序列化操作時(shí)也會(huì)自動(dòng)調(diào)用該類的readObject(ObjectInputStream)方法,也就是說我們可以通過在待序列化或反序列化的類中定義readObject和writeObject方法,來實(shí)現(xiàn)自定義的序列化和反序列化操作,當(dāng)然前提是,被序列化的類必須有此方法,并且方法的修飾符必須是private。

    總結(jié)

    以上是生活随笔為你收集整理的java安全(五)java反序列化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。