??當(dāng)序列化在1997年添加到Java中時(shí)盈蛮,它被認(rèn)為有一定的風(fēng)險(xiǎn)匪凡。這種方法曾在研究語言(模塊3)中嘗試過路媚,但從未在生產(chǎn)語言中使用過骡和。雖然程序員不費(fèi)什么力氣就能實(shí)現(xiàn)分布式對象的承諾很吸引人箍鼓,代價(jià)是看不見的構(gòu)造函數(shù)和API與實(shí)現(xiàn)之間模糊的界限崭参,在性能、安全性和維護(hù)的正確性上存在潛在的問題款咖。支持者認(rèn)為收益大于風(fēng)險(xiǎn)何暮,但歷史證明并非如此。
??在本書之前的版本中描述的安全問題铐殃,結(jié)果和一些人擔(dān)心的一樣嚴(yán)重海洼。早期討論過的漏洞在接下來的十年里,21世紀(jì)頭十年變成了嚴(yán)重的網(wǎng)絡(luò)攻擊富腊,其中著名的包括針對舊金山大都會運(yùn)輸署(San Francisco Metropolitan Transit Agency)的勒索軟件攻擊2016年11月坏逢,市政鐵路(SFMTA Muni)關(guān)閉了整個(gè)收費(fèi)系統(tǒng)兩天[Gallagher16]。
??序列化的一個(gè)基本問題是它的攻擊面太大而無法保護(hù)赘被,并且不斷增長:通過調(diào)用ObjectInputStream上的readObject方法來反序列化對象圖是整。這個(gè)方法本質(zhì)上是一個(gè)神奇的構(gòu)造函數(shù),可以用來實(shí)例化類路徑上幾乎任何類型的對象民假,只要該類型實(shí)現(xiàn)Serializable接口浮入。在反序列化字節(jié)流的過程中,此方法可以執(zhí)行來自任何這些類型的代碼羊异,因此所有這些類型的代碼都是攻擊面的一部分事秀。
??攻擊面包括Java平臺庫彤断、第三方庫(如Apache Commons collection)和應(yīng)用程序本身中的類。即使您堅(jiān)持所有相關(guān)的最佳實(shí)踐并成功地編寫了不受攻擊的可序列化類秽晚,您的應(yīng)用程序仍然可能是脆弱的瓦糟。引用CERT協(xié)調(diào)中心技術(shù)經(jīng)理Robert Seacord的話:
(Java反序列化是一個(gè)明顯而又存在的危險(xiǎn),因?yàn)樗粦?yīng)用程序直接和間接地廣泛使用赴蝇,比如RMI
(遠(yuǎn)程方法調(diào)用)菩浙、JMX (Java管理擴(kuò)展)和
JMS (Java消息傳遞系統(tǒng))。不可信流的反序列化可能導(dǎo)致遠(yuǎn)程代碼執(zhí)行(RCE)句伶、拒絕服務(wù)(DoS)和一系列其他攻擊劲蜻。應(yīng)用程序很容易受到這些攻擊,即使它們沒有做錯(cuò)什么考余。(Seacord17))
??攻擊者和安全研究人員研究Java庫和常用的第三方庫中的可序列化類型先嬉,尋找在反序列化過程中調(diào)用的執(zhí)行潛在危險(xiǎn)活動的方法。這種方法稱為gadget楚堤。多個(gè)gadget可以同時(shí)使用疫蔓,形成一個(gè)gadget鏈。偶爾會發(fā)現(xiàn)一個(gè)小部件鏈身冬,它的功能足夠強(qiáng)大衅胀,允許攻擊者在底層硬件上執(zhí)行任意的本機(jī)代碼,只允許提交精心設(shè)計(jì)的字節(jié)流進(jìn)行反序列化酥筝。這正是SFMTA Muni襲擊中發(fā)生的事情滚躯。這次襲擊并不是孤立的,已經(jīng)有了,而且還會有更多嘿歌。
??不使用任何小工具掸掏,您就可以通過導(dǎo)致需要很長時(shí)間反序列化的短流反序列化,輕松地發(fā)起拒絕服務(wù)攻擊宙帝。種流被稱為反序列化炸彈[Svoboda16]丧凤。下面是Wouter Coekaerts的一個(gè)例子,它只使用哈希集和字符串[Coekaerts15]:
??對象圖由201個(gè)HashSet實(shí)例組成步脓,每個(gè)實(shí)例包含3個(gè)或更少的對象引用愿待。整個(gè)流的長度為5,744字節(jié),但是在您對其進(jìn)行反序列化之前沪编,太陽就已經(jīng)耗盡了呼盆。問題是反序列化HashSet實(shí)例需要計(jì)算其元素的哈希碼。根哈希集的兩個(gè)元素本身就是包含哈希集的哈希集2個(gè)哈希集合元素蚁廓,每個(gè)哈希集合元素包含2個(gè)哈希集合元素访圃,以此類推,100等級深度.因此相嵌,反序列化set會導(dǎo)致hashCode方法被調(diào)用超過2^100次腿时。除了反序列化會持續(xù)很長時(shí)間之外况脆,反序列化器沒有任何錯(cuò)誤的跡象。生成的對象很少批糟,并且堆棧深度是有界的格了。
??那么你能做些什么來抵御這些問題呢?當(dāng)您反序列化一個(gè)您不信任的字節(jié)流時(shí),您就會受到攻擊徽鼎。避免序列化利用的最佳方法是永遠(yuǎn)不要反序列化任何東西盛末。用1983年電影《戰(zhàn)爭游戲》(WarGames)中名為約書亞(Joshua)的電腦的話來說,“唯一的制勝招就是不玩否淤∏牡”沒有理由在編寫的任何新系統(tǒng)中使用Java序列化。還有其他一些機(jī)制可以在對象和字節(jié)序列之間進(jìn)行轉(zhuǎn)換石抡,從而避免了Java序列化的許多危險(xiǎn)檐嚣,同時(shí)提供了許多優(yōu)勢,比如跨平臺支持啰扛、高性能嚎京、大型工具生態(tài)系統(tǒng)和廣泛的專家社區(qū)。在本書中隐解,我們將這些機(jī)制稱為跨平臺結(jié)構(gòu)數(shù)據(jù)表示鞍帝。雖然其他人有時(shí)將它們稱為序列化系統(tǒng),但本書避免使用這種用法厢漩,以免與Java序列化混淆膜眠。
??這些表示的共同之處在于岩臣,它們要比原來Java序列化簡單得多.它們不支持任意對象圖的自動序列化和反序列化溜嗜。相反,它們支持由一組屬性值對組成的簡單結(jié)構(gòu)化數(shù)據(jù)對象架谎。只支持少數(shù)基本數(shù)據(jù)類型和數(shù)組數(shù)據(jù)類型炸宵。事實(shí)證明,這個(gè)簡單的抽象足以構(gòu)建功能極其強(qiáng)大的分布式系統(tǒng)谷扣,而且足夠簡單土全,可以避免Java序列化從一開始就存在的嚴(yán)重問題.
??領(lǐng)先的跨平臺結(jié)構(gòu)化數(shù)據(jù)表示是JSON [JSON]和協(xié)議緩沖區(qū),也稱為protobuf [protobuf]会涎。JSON是由
Douglas Crockford用于瀏覽器-服務(wù)器通信裹匙,協(xié)議緩沖區(qū)由谷歌設(shè)計(jì)用于在其服務(wù)器之間存儲和交換結(jié)構(gòu)化數(shù)據(jù)。盡管這些表示有時(shí)被稱為語言中立的末秃,
JSON最初是為JavaScript開發(fā)的概页,而protobuf最初是為c++開發(fā)的;這兩種表述都保留了其起源的痕跡。
??SON和protobuf之間最顯著的區(qū)別是JSON是基于文本的练慕,并且是人類可讀的惰匙,而protobuf是二進(jìn)制的技掏,而且本質(zhì)上更有效;JSON是一種專門的數(shù)據(jù)表示,而protobuf提供模式(類型)來記錄和執(zhí)行適當(dāng)?shù)挠梅ㄏ罟怼km然protobuf比JSON更有效哑梳,但是JSON對于基于文本的表示非常有效。雖然protobuf是一種二進(jìn)制表示绘盟,但它確實(shí)提供了另一種文本表示鸠真,可用于需要人類可讀性的地方(pbtxt)。
??如果您不能完全避免Java序列化龄毡,可能是因?yàn)槟谛枰倪z留系統(tǒng)上下文中工作弧哎,那么您的下一個(gè)最佳選擇就是永遠(yuǎn)不要反序列化不可信的數(shù)據(jù)。特別是稚虎,您不應(yīng)該接受來自不可信源的RMI流量撤嫩。Java的官方安全編碼指南說
“不可信數(shù)據(jù)的反序列化本質(zhì)上是危險(xiǎn)的,應(yīng)該避免蠢终⌒蛉粒”:這個(gè)句子是用大的、粗體的寻拂、斜體的程奠、紅色的字體設(shè)置的,它是整個(gè)文檔中唯一得到這種處理的文本[Java-secure]祭钉。
??如果無法避免序列化瞄沙,并且不能絕對確定反序列化的數(shù)據(jù)的安全性,請使用Java9添加進(jìn)來的對象反序列化篩選
并向后移植到早期版本(Java .io. objectinputfilter)慌核。該工具允許您指定一個(gè)過濾器距境,該過濾器在反序列化數(shù)據(jù)流之前應(yīng)用于數(shù)據(jù)流。它在類粒度上運(yùn)行垮卓,允許您接受或拒絕某些類垫桂。默認(rèn)接受類并拒絕潛在危險(xiǎn)類的列表稱為黑名單;在缺省情況下拒絕類并接受假定安全的類的列表稱為白名單。比起黑名單粟按,更喜歡白名單诬滩,因?yàn)楹诿麊沃槐Wo(hù)你免受已知的威脅。一個(gè)工具叫串行白名單應(yīng)用培訓(xùn)器(SWAT)可用于為您的應(yīng)用程序自動準(zhǔn)備白名單[Schneider16]灭将。過濾工具還將保護(hù)您免受過度內(nèi)存使用和過于深入的對象圖的影響疼鸟,但它不能保護(hù)您免受如上面所示的序列化炸彈的影響。
??不幸的是庙曙,序列化在Java生態(tài)系統(tǒng)中仍然很普遍空镜。如果您正在維護(hù)一個(gè)基于Java序列化的系統(tǒng),請認(rèn)真考慮遷移到跨平臺的結(jié)構(gòu)化數(shù)據(jù)表示,盡管這可能是一項(xiàng)耗時(shí)的工作姑裂。實(shí)際上馋袜,您可能仍然需要編寫或維護(hù)一個(gè)可序列化的類。編寫一個(gè)正確舶斧、安全欣鳖、高效的可序列化類需要非常小心。本章的其余部分將提供何時(shí)以及如何進(jìn)行此操作的建議茴厉。
??總之泽台,序列化是危險(xiǎn)的,應(yīng)該避免矾缓。如果您從頭開始設(shè)計(jì)一個(gè)系統(tǒng)怀酷,可以使用跨平臺的結(jié)構(gòu)化數(shù)據(jù)表示,如JSON或protobuf嗜闻。不要反序列化不可信的數(shù)據(jù)蜕依。如果必須這樣做,請使用對象反序列化過濾琉雳,但要注意样眠,它不能保證阻止所有攻擊。避免編寫可序列化的類翠肘。如果你必須這樣做檐束,一定要非常小心。
本文寫于2019.7.23束倍,歷時(shí)1天