Java序列化的狀態
關鍵要點
多年來,Java的序列化功能飽受ofollow,noindex" target="_blank">安全漏洞 和zero-day攻擊,為此贏得了“持續奉獻的禮物 ”和“第四個不可饒恕的詛咒 ”的綽號。
作為迴應,OpenJDK貢獻者團隊討論了一些用於限制序列化訪問的方法,例如將其提取到可以被移除的jigsaw模組中 ,讓黑客無法攻擊那些不存在的東西。
一些文章(例如“序列化必須死 ”)提出了這樣的建議,將有助於防止某些流行軟體(如VCenter 6.5)的漏洞被利用 。
什麼是序列化?
自從1997年釋出JDK 1.1 以來,序列化已經存在於Java平臺中。
它用於在套接字之間共享物件表示,或者將物件及其狀態儲存起來以供將來使用(反序列化)。
在JDK 10及更低版本中,序列化作為java.base包和java.io.Serializable方法的一部分存在於所有的系統中。
GeeksForGeeks對序列化的工作原理 進行了詳細的描述。
有關更多如何使用序列化的程式碼示例,可以參看Baeldung對Java序列化的介紹 。
序列化的挑戰和侷限
序列化的侷限主要表現在以下兩個方面:
進行序列化漏洞攻擊的基本前提是找到對反序列化的資料執行特權操作的類,然後傳給它們惡意的程式碼。為了理解完整的攻擊過程,可以參看Matthias Kaiser在2015年發表的“Exploiting Deserialization Vulnerabilities in Java ”一文,其中幻燈片第14頁開始提供了相關示例。
其他大部分與序列號有關的安全研究 都是基於Chris Frohoff、Gabriel Lawrence和Alvaro Munoz的工作成果。
序列化在哪裡?如何知道我的應用程式是否用到了序列化?
要移除序列化,需要從java.io包開始,這個包是java.base模組的一部分。最常見的使用場景是:
- 實現Serializable介面和(可選)serialversionuid長整型欄位。
- 使用ObjectInputStream或ObjectOutputStream。
- 使用嚴重依賴序列化 的庫,例如:Xstream、Kryo、BlazeDS和大多數應用程式伺服器 。
使用這些方法的開發人員應考慮使用其他儲存和讀回資料的替代方法。Eishay Smith釋出了幾個不同序列化庫的效能指標 。在評估效能時,需要在基準度量指標中包含安全方面的考慮。預設的Java序列化“更快”一些,但漏洞也會以同樣的速度找上門來。
我們該如何降低序列化缺陷的影響?
專案Amber 包含了一個關於將序列化API隔離出來的討論。我們的想法是將序列化從java.base移動到單獨的模組,這樣應用程式就可以完全移除它。在確定JDK 11功能集 時並沒有針對該提議得出任何結果,但可能會在未來的Java版本中繼續進行討論。
通過執行時保護來減少序列化暴露
一個可以監控風險並自動化可重複安全專業知識的系統對於很多企業來說都是很有用的。Java應用程式可以將JVMTI工具嵌入到安全監控系統中,通過插樁的方式將感測器植入到應用程式中。Contrast Security是這個領域的一個免費產品,它是JavaOne大會的Duke's Choice大獎得主 。與其他軟體專案(如MySQL或GraalVM)類似,Contrast Security的社群版 對開發人員是免費的。
將執行時插樁應用在Java安全性上的好處是它不需要修改程式碼,並且可以直接整合到JRE中。
它有點類似於面向切面程式設計,將非侵入式位元組碼嵌入到源端(遠端資料進入應用程式的入口)、接收端(以不安全的方式使用資料)和轉移(安全跟蹤需要從一個物件移動到另一個物件)。
通過整合每個“接收端”(如ObjectInputStream),執行時保護機制可以新增額外的功能。在從JDK 9移植反序列化過濾器之前,這個功能對序列化和其他攻擊的型別(如SQL注入)來說至關重要。
整合這個執行時保護機制只需要修改啟動標誌,將javaagent新增到啟動選項中。例如,在Tomcat中,可以在bin/setenv.sh中新增這個標誌:
CATALINA_OPTS=-javaagent:/Users/ecostlow/Downloads/Contrast/contrast.jar
啟動後,Tomcat將會初始化執行時保護機制,並將其注入到應用程式中。關注點的分離讓應用程式可以專注在業務邏輯上,而安全分析器可以在正確的位置處理安全性。
其他有用的安全技術
在進行維護時,可以不需要手動列出一長串東西,而是使用像OWASP Dependency-Check 這樣的系統,它可以識別出已知安全漏洞的依賴關係,並提示進行升級。也可以考慮通過像DependABot 這樣的系統進行庫的自動更新。
雖然用意很好,但預設的Oracle序列化過濾器 存在與SecurityManager和相關沙箱漏洞相同的設計缺陷。因為需要混淆角色許可權並要求提前瞭解不可知的事物,限制了這個功能的大規模採用:系統管理員不知道程式碼的內容,所以無法列出類檔案,而開發人員不瞭解環境,甚至DevOps團隊通常也不知道系統其他部分(如應用程式伺服器)的需求。
移除未使用模組的安全隱患
Java 9的模組化JDK能夠建立自定義執行時映象 ,移除不必要的模組,可以使用名為jlink的工具將其移除。這種方法的好處是黑客無法攻擊那些不存在的東西。
從提出模組化序列化到應用程式能夠實際使用以及使用其他序列化的新功能需要一段時間,但正如一句諺語所說:“種樹的最佳時間是二十年前,其次是現在”。
剝離Java的原生序列化功能還應該為大多數應用程式和微服務提供更好的互操作性。通過使用標準格式(如JSON或XML),開發人員可以更輕鬆地在使用不同語言開發的服務之間進行通訊——與Java 7的二進位制blob相比,python微服務通常具有更好的讀取JSON文件的整合能力。不過,雖然JSON格式簡化了物件共享,針對Java和.NET解析器的“Friday the 13th JSON attacks ”證明了銀彈是不存在的(白皮書 )。
在進行剝離之前,序列化讓然保留在java.base中。這些技術可以降低與其他模組相關的風險,在序列化被模組化之後,仍然可以使用這些技術。
為Apache Tomcat 8.5.31模組化JDK 10的示例
在這個示例中,我們將使用模組化的JRE來執行Apache Tomcat,並移除任何不需要的JDK模組。我們將得到一個自定義的JRE,它具有更小的攻擊表面,仍然能夠用於執行應用程式。
確定需要用到哪些模組
第一步是檢查應用程式實際使用的模組。OpenJDK工具jdeps可以對JAR檔案的位元組碼執行掃描,並列出這些模組。像大多數使用者一樣,對於那些不是自己編寫的程式碼,我們根本就不知道它們需要哪些依賴項或模組。因此,我使用掃描器來檢測並生成報告。
列出單個JAR檔案所需模組的命令是:
jdeps -s JarFile.jar
它將列出模組資訊:
tomcat-coyote.jar -> java.base tomcat-coyote.jar -> java.management tomcat-coyote.jar -> not found
最後,每個模組(右邊的部分)都應該被加入到一個模組檔案中,成為應用程式的基本模組。這個檔案叫作module-info.java,檔名帶有連字元,表示不遵循標準的Java約定,需要進行特殊處理。
下面的命令組合將所有模組列在一個可用的檔案中,在Tomcat根目錄執行這組命令:
find . -name *.jar ! -path "./webapps/*" ! -path "./temp/*" -exec jdeps -s {} \; | sed -En "s/.* -\> (.*)/requires \1;/p" | sort | uniq | grep -v "not found" | xargs -0 printf "module com.infoq.jdk.TomcatModuleExample{\n%s}\n"
這組命令的輸出將被寫入lib/module-info.java檔案,如下所示:
module com.infoq.jdk.TomcatModuleExample{ requires java.base; requires java.compiler; requires java.desktop; requires java.instrument; requires java.logging; requires java.management; requires java.naming; requires java.security.jgss; requires java.sql; requires java.xml.ws.annotation; requires java.xml.ws; requires java.xml; }
這個列表比整個Java模組列表要短得多。
下一步是將這個檔案放入JAR中:
javac lib/module-info.java jar -cf lib/Tomcat.jar lib/module-info.class
最後,為應用程式建立一個JRE:
jlink --module-path lib:$JAVA_HOME/jmods --add-modules ThanksInfoQ_Costlow --output dist
這個命令的輸出是一個執行時,包含了執行應用程式所需的恰到好處的模組,沒有任何效能開銷,也沒有了未使用模組中可能存在的安全風險。
與基礎JDK 10相比,只用了98個核心模組中的19個。
java --list-modules com.infoq.jdk.TomcatModuleExample [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected]
執行這個命令後,就可以使用dist資料夾中的執行時來執行應用程式。
看看這個列表:部署外掛(applet)消失了,JDBC(SQL)消失了,JavaFX也不見了,很多其他模組也消失了。從效能角度來看,這些模組不再產生任何影響。從安全形度來看,黑客無法攻擊那些不存在的東西。保留應用程式所需的模組非常重要,因為如果缺少這些模組,應用程式也無法正常執行。
關於作者
Erik Costlow是甲骨文的Java 8和9產品經理,專注於安全性和效能。他的安全專業知識涉及威脅建模、程式碼分析和安全感測器增強。在進入技術領域之前,Erik是一位馬戲團演員,可以在三輪垂直獨輪車上玩火。