2023 Third Hongminggu Cup Cybersecurity Competition Retrospective

免责声明:本文为个人技术学习与工程实践笔记,所涉操作仅应在获得授权的环境中进行。因不当使用造成的后果由使用者自行承担。

本次比赛没有队友,所有题目均由本人独立完成。

Dreamer

本题以一套开源 Java CMS(dreamer_cms)为审计对象,其后台登录与标签库实现具有典型的审计价值。

进入管理员登录页面后,使用题目环境提供的管理员凭据即可登录。该 CMS 在当时存在多处已知风险点,本文只讨论与本题相关的一处。

img

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

img

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

img

img

img

img

Hacker

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

img

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

img

得到

img

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

img

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

img

提取后的 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 编码,编写脚本运行即可

img

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

img

在 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

逐一人工比对确实较为费时

img

解码后

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 爆破

img

解码得到

img

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

img

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

img

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

img

flag{386baeaa-e35a-47b6-905d-5e184cab25ea}

签到

js 文件中有一个 decrypt 函数,直接运行即可

Dreamer_revenge

与上面那题思路相近,读取 redis 日志即可获取 flag

防御启示

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

Support via Solana

Solana

Solana

Solana Pay

Solana Pay

WeChat

WeChat