这次分了两天,第一天是传统 CTF,第二天是 A&D 赛制,简单来说A&D就是awd,但是他和传统AWD有所不同,他的附件一般不止一个漏洞,而且进攻扣对方%30的分,也就是说只要能找到一个全场没有的洞,打一遍全场就站起来了🤬,但是我被打爆了,千万不要宕机啊🤯
CTF
wallet
首先看前后端依赖,一个是 express

一个是 fastify/http-proxy

前端js如下
| |
有一个 waf 对于 transfer 路由,限制转账金额,以及admin自己给自己转账刷钱,直接 utf-16 编码绕过
| |
不用想都是解析漏洞,自己给自己转钱
| |
这里的逻辑是(amount^0.25)^4。由于计算机在处理浮点数时存在精度限制, score 的计算结果会与原始的 amount 有一个微小的偏差。所以就能获得 flag 了,弄个exp
| |
kinding(revenge)
开局给了个一句话
| |
拿到disabled
| |
过滤了很多函数,绕过disable_functions和open_basedir(/var/www/html:/tmp),可以直接上传 so 文件,这让我想起kengwang师傅在 LILCTF2025 出的一道题,里面使用 curl 直接加载 so 文件进行getshell
https://blog.kengwang.com.cn/archives/668/ 但是现在的版本比较新,是8.4.x?所以就需要bypass,整了半天整不出来,后来看到存在sqlite3 扩展,推测其在putenv 被ban情况下也能完成任意路径下 so 加载
存在SQLite3::loadExtension方法可以加载库,但库必须位于配置选项sqlite3.extension_dir中指定的目录中 https://www.php.net/manual/zh/sqlite3.loadextension.php
接着找到在Pdo\Sqlite::loadExtension也存在可以加载库的方法,好像没有配置限制
| |
写入 so 文件
| |
然后就getshell了
| |
发包
| |
go-server
go 语言的 xss,还有 docker daemon集群的点,非常不错的一道题
| |
Go 语言 os.CreateTemp 的特殊行为:
- 当 pattern 中包含
*时,*会被替换为随机字符串,例如:*.html→abc123def.html - 当 pattern 不包含
*时:会在文件名末尾自动添加随机字符串,例如:test.php→test.php456789
利用这个原理上传.html进行xss外带cookie
| |
得到文件名后让bot访问
http://nginx-proxy/uploads/xxx.html
| |
接着查看找到
| |
/var/run/docker.sock 是 Docker daemon 的 Unix Domain Socket 文件,是 Docker 的核心通信接口。
| |
先获取所有容器id
| |
回显如下
| |
找到存储容器的然后再读flag
| |
得到 Docker API 的 /archive 接口返回的 TAR 压缩包格式数据,太长了我就不放了,然后可以看到有部分数据挨着的转一下就是 flag
| |
A&D
被打爆了,我也只会最简单那个解,简单记录一下赛后的看法,代码真的多🥵。
red-note
通过询问 0ops 大哥们得知 remote 的管理员为 admin\123456,下面的测试皆为本地测试
私密笔记的密码生成逻辑如下
| |
看一下密码校验部分
| |
所以下一步就是获取用户和笔记的 id
| |
以note_id:user_id:secret作为“口令”原文,且 secret 允许使用固定默认值 DEVELOPMENT_DEFAULT_SECRET,导致口令可预测,直接到 flag 笔记。
| |
条件竞争创建笔记获得 flag
| |
只要我们笔记大于 10,就可以获得 flag,但是一个人限制两条,准确来说是一个会话限制两条
| |
主要就是最后的数据库递减,没有进行原子化处理,多个会话同时通过chance > 0的检查,各自创建笔记并分别做递减,数据库层递减与配额判定分离,且递减语句无原子条件WHERE chance > 0与更新结果检查,无法阻止超额创建。exp 如下
| |
SSRF,我相信大家一开始就是看到了这个flask应用,但是需要到5000端口才行,找了一圈没找到
在AdminController.php的 handleConnect 方法:
| |
我们看到有个典型的链接,并且里面黑名单限制了为 ftp,其实tcp也可以,所以就能到 5000 了,而且有个地方直接进行了参数拼接
| |
那既然到了这里,如何绕过呢?Stream Context!
| |
$options参数允许用户完全控制stream context,Stream context可以通过 proxy 选项重定向请求到任意地址和端口
| |
说明已经成功 ssrf, 但是就差最后一步不知道怎么加 http 头
| |
经过测试我发现可以直接在-d里塞请求包,那现在就简单了
| |
exp 如下
| |
文件上传 RCE,看到可以上传 .htaccess,很容易想到文件上传 getshell,但是他过滤的有点死,名称过滤 p 与内容过滤php/?,而且也没有 include,后来询问了 0ops 的大哥,得知了这个点 https://eastjun.top/posts/htaccess_use/

对于题目环境构造出这样的文件(图上的不行)
| |
能够成功回显 HIT,exp 如下
| |
nsl
由于没给 docker,去问了一下 @Jay17大哥,他也记不得 flag 位置了,所以这里测试是本地使用的 /flag 所以并不确保远程可以打通🤗
这个题,有多个洞,越权读取 flag、tar 软连接读取 flag、利用 pulsebus 任务主题可被覆盖的漏洞触发维护快照并从日志中读取 flag。
攻击者可通过legacy模式的中继握手(Relay Handshake)获取一个高权限的 token,并使用该令牌直接访问对外暴露的管理接口/admin/vault/snapshot,从而越权读取 flag。
| |
在app/routes/diagnostics.py中,/admin/vault/snapshot路由被定义,作为一个代理,将收到的请求(包括 Authorization 头)直接转发。
| |
在app/diagnostics_service.py中,内部的/admin/vault/snapshot接口仅验证令牌是否为 maintenance_token 或有效的 RelayToken,exp 如下
| |
还有软连接,在app/routes/support.py的_extract_archive函数中,代码检查了归档成员是否为符号链接,如果是,则在服务器上重新创建它。
| |
使用了 symlink 第一时间想到 tar 软连接
在app/routes/diagnostics.py的 diagnostics_bundle 函数中,当系统根据 manifest.json 将暂存区文件打包成最终的 diagnostics.tar 时,tarfile.open 使用了 dereference=True(默认或显式设置)。这导致 tar 命令会读取符号链接指向的真实文件内容。
| |
exp 如下
| |
还有就是泄漏日志,在调度 PulseBus 任务时,可以覆盖 topic,篡改为内部专用的maintenance.vault.snapshot。这会触发系统执行一个计划外的维护任务,将包含 flag 的敏感快照信息写入到可读的中继日志中。
| |
直接覆盖topic
| |
然后写入日志,exp 如下
| |
小结
第一次参加XCTF final,也是最后一次,很可惜,第二天AD被打爆了,好像就差8分就是二等奖。
第一天CTF打的还行,但是我电脑怎么搭建代理都搭建不好,所以我还专门在博客发了一篇文章,当时洪老师和书鱼哥哥直接远程给我配,😍。见到了不少师傅,unk\qanux\jay17等,unk队真温柔,突然回想起去年想加入VN,无论问什么问题,他都会细心回答我,还有我一起打国际赛的suers(好兄弟)那更不用说,虽然互相压力,但是也互相包容,就是家人的感觉,第一天晚上刚到就开始畅谈~🥰
感谢 0ops 的大哥们 A&D 打了 18w 分,不然我可能就是优秀奖了😭