前言
3 月 22 日,第十九届 CISCN & 长城杯的线下半决赛。这次半决赛我们被分到重庆赛区,我负责 web 方向。一天就打完:上午 4 小时 AWDP 攻防对抗,下午 4 小时 ISW 综合渗透。
这篇文章只复盘属于我的部分——web 题和内网渗透。pwn 和逆向归队友管,我也不懂,就不献丑了。
先说结果:最终排在赛区前 25,晋级总决赛。至于过程嘛,有惊喜也有遗憾,往下看。
上午 AWDP:MediaDrive 的攻与修
AWDP 的玩法一句话说清:同一套服务,既能 break(拿到 flag 换攻击分),也要 fix 自己的(提交补丁、通过 check 换防御分),平台上攻击和防御是两个入口。check 的脾气可以总结成两句话:完全没修好,报”漏洞利用成功”;补丁把服务改坏、或者修复方式不符合要求,报”服务异常”。所以修复这事特别熬人——你不知道出题人的检测规则,只能换着姿势多试。
本届 web 一共四道题:easy_time、wso2、IntraBadge、MediaDrive。我只做出来了 MediaDrive,只拿到了fix防守分
题目背景
MediaDrive 是一个企业内部的媒体管理后台,支持文件上传、预览、管理和文件编码兼容转换。出题人在题目描述里其实把攻击面提示得很客气了——”系统在处理用户偏好、文件路径转换以及文件预览时存在潜在安全风险”。这三个关键词翻译一下:
- 用户偏好 → Cookie
- 文件路径转换 → iconv
- 文件预览 → preview.php
三个词串起来,审计入口基本就锁定了。审计 preview.php,关键源码就这几行:
$user = null;
if (isset($_COOKIE['user'])) {
$user = @unserialize($_COOKIE['user']); // ① Cookie 直接反序列化
}
if (!$user instanceof User) {
$user = new User("guest");
setcookie("user", serialize($user), time() + 86400, "/");
}
$f = (string)($_GET['f'] ?? "");
$rawPath = $user->basePath . $f; // ② basePath 来自 Cookie,可控
if (preg_match('/flag|\/flag|\.\.|php:|data:|expect:/i', $rawPath)) { // ③ 黑名单
http_response_code(403);
exit;
}
$convertedPath = @iconv($user->encoding, "UTF-8//IGNORE", $rawPath); // ④ 编码转换
$content = @file_get_contents($convertedPath); // ⑤ 直接读文件
漏洞分析
preview.php 的反序列化:系统把用户偏好直接存在 Cookie 里,服务端取出来不加分筛就丢给了 unserialize(),还原出一个 $user 对象,其中的 basePath 属性还会参与后面文件路径的拼接。Cookie 可控 + 反序列化 + 路径拼接,这尼玛经典的 PHP 反序列化利用场景。
download.php 的路径限制问题:文件上传部分的路径处理不严,上传位置可以被操纵,意味着能把文件写到预期目录之外——和预览功能串起来就是一条完整的链。
修复(Fix)
我们的修法两个点分别下刀:
- preview.php:反序列化是根,两种思路——PHP 的
unserialize()第二个参数给allowed_classes白名单,把能还原的类卡死;或者更干脆,偏好存储改用 json,不反序列化。$user->basePath再补一层 realpath 校验,必须落在设定的工作目录内,路径穿越直接掐死。 - download.php:上传路径由服务端指定,用户输入的文件名过
basename(),不允许自定义相对路径。
赛后翻各家公开复盘,能过 check 的修法其实五花八门,列两个开阔一下思路:
路径写死。把拼接这一行直接改掉,basePath 彻底出局,反序列化当场失去意义:
// $rawPath = $user->basePath . $f;
$rawPath = "/var/www/html/uploads/" . $f;
Cookie 先过滤再反序列化。有的队伍把 download.php、index.php、preview.php、profile.php 四个文件开头的反序列化统一改造成:先过一道关键词黑名单 + strlen($user) > 50 的长度卡死,命中就 header("Location: /") 弹走,不命中才放行给 unserialize():
if (isset($_COOKIE['user'])) {
$user = $_COOKIE['user'];
// 正则过长有删节,原版把 flag/select/union/引号/空字节/百分号/数字等全 ban 了
if (preg_match("/flag|select|union|\\$|'|\"|--|#|\\0|into|file|\=|or|
\x09|\x0a|\x0b|\x0c|\xa0|\%|\<|\>|\^|\x00|[0-9]|\x7c|and|where|\x26|
sleep|benchmark|regexp|from|count|ascii|substr|load_file|join|show|
update|concat|delete|alter|insert|create|drop|not|between|like|user|
greatest|mid|char|hex|ord|limit|conv|table|rpad|\&|\*|\.|/is", $user)
|| strlen($user) > 50) {
header("Location: /");
die();
}
$user = @unserialize($user);
}
粗暴,但对这种无签名的反序列化是真管用。
修复的心得就一条:防御分按轮次发,改完先本地把正常功能跑通、确认漏洞真的堵死了再提交;另外修复代码里有语法错误等于白给,线下高压环境下这种低级失误并不少见,提交前多看一眼。
上午收工,MediaDrive 的fix修补第二轮就完成了,上午光靠这个题一直吃分
其他 web 题一览
- easy_time:一道”双服务”题,Dockerfile 里起了两个 web 服务——对外是 python 服务,内部还藏着一个 php。审计点是加载远程头像处的 SSRF,配合插件上传的 zip 路径穿越写马,flag 据说还藏在
/tmp里,不少队找半天。修复方向是把远程头像加载和上传功能禁掉,还有个坑:上传新代码服务并不会自动重启,得想办法让进程重新拉起。 - wso2:基于真实企业级中间件 WSO2 改编的 Java 题。审计点是系统做数据库连接测试时对用户提交的 SQL 不做校验,攻击者可以利用 H2 数据库的特性执行恶意语句——官方描述还贴心提醒”记得考虑无回显情况”;防御侧只允许改一个 jar 文件,改多了容器启动超时直接判负。我们队那道 Java 完全没看懂是在干嘛,白白准备了很久。这题难到什么程度呢——赛后听说有队伍靠
rm /flag过了 check,修不动漏洞就把 flag 物理消灭,也是个人才。 - IntraBadge:企业内部”徽章卡片系统”,员工可以自定义展示模板、填写头像 URL,系统会自动去抓取头像、缓存之后渲染页面——
avatar_refresh接口没限制 URL 协议,连redis://都能打内网,典型的 SSRF 面,还叠了一个从 Cookie 直接取用户身份的认证缺陷。
(pwn 方向另有四道题,队友fix成功了一道,帮我们稳固了排名)
下午 ISW:从 shiro 然后PK提权
下午的 ISW 综合渗透是另一种完全不同的体验:只做了一台靶机ISW-1
入口是一台双网卡机器:对外 8080 端口跑着 Shiro
flag1:Shiro 反序列化
按惯例 fscan 先扫一遍入口机,直接扫出了shiro反序列化

直接用ShiroAttack2工具一把梭拿到flag

拿到的只是普通用户权限,
find / -perm -4000 -type f 2>/dev/null
suid发现有pkexec,这是个CVE-2021-4034(PwnKit)
上传pwnkit的exp,直接提权到root
./pwnkit


结果
八小时打完,最终排在赛区前 25,拿到总决赛门票,4 月福州见。