免责声明:本文为个人技术学习与工程实践笔记,所涉操作仅应在获得授权的环境中进行。因不当使用造成的后果由使用者自行承担。
本次比赛没有队友,所有题目均由本人独立完成。
Dreamer
本题以一套开源 Java CMS(dreamer_cms)为审计对象,其后台登录与标签库实现具有典型的审计价值。
进入管理员登录页面后,使用题目环境提供的管理员凭据即可登录。该 CMS 在当时存在多处已知风险点,本文只讨论与本题相关的一处。

问题位于标签库中负责内容包含的组件 IncludeTag:该组件对 file 参数的值未做任何校验,直接将其用于读取并渲染目标路径的内容。

由于 file 值未经过滤,模板渲染到该标签时便会把参数所指向的文件内容直接包含进来,最终形成任意文件读取。核心问题并不在标签语法本身,而在于把用户可控的路径直接交给了文件读取接口。




Hacker
分析流量包,发现其中存在被上传的 Web 脚本(webshell),将其提取出来

发现经过 urlencode 编码的脚本,解码后如下

得到

脚本逻辑较为简单:以 admin 的密码作为密钥,将两个字符串转为十六进制后逐位异或,再拼接到 DNS 日志域名上。也就是说,解题需要全部 DNS 查询记录以及 admin 的密码,密码位于第 58 条记录。

对 dns 记录进行过滤后逐一提取即可

提取后的 dns 记录如下
1 2 3 4 5 6 7 8
| $a[0] = "59115a4b465044695a5a56015c4252065e501c130e416f5c5647556b510044"; $a[1] = "05b0e5d4b5f5b5b69505c57074f18430c423f5b0c0852105a521d4409476b5"; $a[2] = "4a32135c07594c474d4d4a47684453501657411c171e456f4c5f5659043d19"; $a[3] = "0c495011391d4e40054d495a4368"; $a[4] = "79227024716c7522787370254c777230667673222570247b76677322632671"; $a[5] = "d7b357226771575227a7372237677702573611f372570317b7672772076206"; $a[6] = "1479207024777b60247e6674231a626727666171372570317f766773207620"; $a[7] = "067879226731756c60206d75703670754e";
|
解码脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function decode($a){ for ($i=0; $i < strlen($a); $i++) { global $hex_password, $t, $flag, $len_password; $t = $a[$i]; $p = $hex_password[$i % $len_password]; $x = dechex(hexdec($t)^hexdec($p)); $flag.=$x; } return hex2bin($flag); } echo decode($a); // for ($i=0; $i < sizeof($a); $i++) { // echo decode($a[$i]) . "<br>"; // }
|
解密时只有第一个和第五个能够成功解出,解开后的数据如下
1 2
| api.php checktable.php data fav ACCAGTAAAACG{AATTCAACAACATGCTGC
|
第二个明显是 DNA 编码,编写脚本运行即可

由此可以推测剩下的 flag 都是由 DNA 进行编码的。回顾上面那串加密脚本,我忽略了这一段代码

在 A 开头处补位即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| $servername="127.0.0.1"; $username="root"; $password="8a3e684c923b763d252cf1e8734a7a29"; $dbname="zentao"; $hex_password=bin2hex($password); $len_output=strlen($hex_output); $len_password=strlen($hex_password); $max_subdomain_length=62; $subdomain_base="yafgcy.ceye.io"; $hex_xor=""; for($i=0;$i<$len_output;$i++){ $char_output=$hex_output[$i]; $char_password=$hex_password[$i%$len_password]; $char_xor=dechex(hexdec($char_output)^hexdec($char_password)); $hex_xor.=$char_xor; } $a = "59115a4b465044695a5a56015c4252065e501c130e416f5c5647556b510044"; $b = "05b0e5d4b5f5b5b69505c57074f18430c423f5b0c0852105a521d4409476b5"; $c = "4a32135c07594c474d4d4a47684453501657411c171e456f4c5f5659043d19"; $d = "0c495011391d4e40054d495a4368"; $e = "79227024716c7522787370254c777230667673222570247b76677322632671"; $f = "d7b357226771575227a7372237677702573611f372570317b7672772076206"; $g = "1479207024777b60247e6674231a626727666171372570317f766773207620"; $h = "067879226731756c60206d75703670754e"; $m = "79227024716c7522787370254c777230667673222570247b766773226326711d7b357226771575227a7372237677702573611f372570317b767277207620611479207024777b60247e6674231a626727666171372570317f766773207620067879226731756c60206d75703670754e"; function decode($l){ $flag = ""; for ($i=0; $i < strlen($l); $i++) { global $hex_password, $t, $len_password; $t = $l[$i]; $p = $hex_password[$i % $len_password]; $x = dechex(hexdec($t)^hexdec($p)); $flag.=$x; } return hex2bin($flag); } echo decode($m) . "<br>";
|
得到
1
| ACCAGTAAAACG{AATTCAACAACATGCTGC$CTACA-AACAAAAACAAT-TCATCAACAAAS-AACAACTGGTGA-TTCTTCTCATGATGAAA4AACTTCTTCTGCTGC}
|
逐段拼接
ACCAGTAAAACG->flag
AATTCAACAACATGCTGCTCTACA->d1ee664e
TCTACAAACAAAAACAAT->4ebabd
逐一人工比对确实较为费时

解码后
1 2 3 4 5 6 7 8 9 10 11 12 13
| print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAAA")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAAC")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAAG")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAAT")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAATAACAACTGGTGA")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAATAACAACTGGTGATTCTTCTCATGATGAAAA")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAATAACAACTGGTGATTCTTCTCATGATGAAAC")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAATAACAACTGGTGATTCTTCTCATGATGAAAG")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAATAACAACTGGTGATTCTTCTCATGATGAAAT")) print(translate("ACCAGTAAAACGAATTCAACAACATGCTGCTCTACAAACAAAAACAATTCATCAACAAATAACAACTGGTGATTCTTCTCATGATGAAATAACTTCTTCTGCTGC")) # 621438bc-1208-42e1-86eb-f04e80203ef8 # d1ee664e-babd-11ed-bb75-00155db0066 print("flag{d1ee664e-babd-11ed-bb75-00155db0066}")
|
阿尼亚
查看图片 IEND 末尾,发现可疑字符串,直接使用 Chef 爆破

解码得到

根据文件名 net PixelJihad,检索到一个在线解码工具

将图片上传至该网站解码即可

该密码为压缩包密码,解压得到 decabit 编码,解码即可

flag{386baeaa-e35a-47b6-905d-5e184cab25ea}
签到
js 文件中有一个 decrypt 函数,直接运行即可
Dreamer_revenge
与上面那题思路相近,读取 redis 日志即可获取 flag
防御启示
- 默认凭据必须在部署时强制更换:开源 CMS 的初始管理员账号与口令是公开信息,任何部署都应强制修改初始口令、启用多因素认证,并对后台登录做来源限制与失败次数限制,避免后台直接暴露在公网。
- 模板标签的文件参数必须做路径校验:自定义标签或模板引擎处理文件路径时,应先规范化路径再与允许的根目录做前缀比对,禁止把用户输入直接交给文件读取接口;模板编辑权限本身也应收敛到最小范围。
- 图片与附加数据需要检测:本题的载荷藏在图片 IEND 标记之后,另有一题使用像素级编码与多重编码。上传审核应对文件做重编码、剥离尾部附加数据,并对二维码、像素编码等非常规载体做内容检测。
- 外传通道要按行为特征检测:数据被异或后分段拼接为子域发出,检测重点不是编码方式,而是行为特征——异常的子域长度、同一父域下大量随机标签、非工作时间的高频查询。边界 DNS 应禁止终端直连外部递归解析。
- 中间件日志与备份中的敏感数据要治理:本题通过 redis 日志取得 flag,说明日志常包含业务敏感内容。应对日志字段做脱敏、限制日志文件权限与留存周期,并集中收集到只读存储,避免日志成为数据泄露的侧门。