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
提供/
,绕过斜杠过滤。 - 分号分隔语句:允许在同一行执行多条命令。
什么文件上传?
访问 robots.txt 发现/class.php 继续访问 进入反序列化
poc:
1 | <?php |
触发逻辑
- 触发
yesterday
的析构函数:当yesterday
对象被销毁时,会调用$this->study->hard()
。 - 调用
today
的__call
方法:将study
属性设置为today
的实例,当调用不存在的方法hard()
时,触发__call
方法。 - 处理
doing
属性:在__call
方法中,$this->doing
被转换为字符串时触发future
的__toString
方法。 - 执行任意命令:
__toString
方法中的system($_POST["wow"])
允许通过POST参数执行任意命令。
在 __call()
方法中,存在以下逻辑:
1 | if (md5(md5($this->doing)) == 666) { // 分支1 |
- 分支1:
$this->doing()
会触发__invoke()
方法(但此处与目标无关)。 - 分支2:
$this->doing->better
会触发__get()
方法(若better
属性不存在)。
3. 关键触发点:字符串隐式转换
若想触发 future::__toString()
,需要让 $this->doing
在被当作字符串处理时自动转换。这可以通过以下两种方式实现:
方式一:通过 md5()
函数
md5(md5($this->doing))
中的md5()
函数要求参数为字符串。- 如果
$this->doing
是future
类的实例,PHP 会尝试将其转换为字符串,从而触发future::__toString()
。
方式二:通过属性访问 ->better
$this->doing->better
中,若future
类没有better
属性,会触发future::__get()
。- 但
__get()
本身不直接触发__toString()
,需配合其他操作。
4. 构造攻击链
为实现代码执行,需要让 $this->doing
是 future
类的实例,并通过 md5()
隐式触发字符串转换:
1 | // 构造对象链 |
当 md5(md5($this->doing))
执行时:
$this->doing
(future
实例)被传入md5()
。- PHP 隐式调用
future::__toString()
将其转为字符串。 __toString()
中的system($_POST["wow"])
执行任意命令。
5. 为什么需要 md5()
?
md5()
强制将对象转为字符串,是触发__toString()
的最佳跳板。- 如果绕过
md5(md5(...)) == 666
的条件(例如通过哈希碰撞),甚至可以进入$this->doing()
分支触发__invoke()
,但此处直接利用字符串转换更直接。
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 个冗余字符(如填充符号),截断后不影响解码逻辑。这一特性使得攻击者无需强制添加后缀即可完成利用。实际攻击中需确保编码后的字符串结构适配截断操作。
火眼辩魑魅
访问robots.txt
利用的是通过tgshell.php的漏洞
因为他写了post shell吗 然后试着传参发现有waf
这里可以直接通过antsword连接 然后在根目录里找到flag
直面天命
查看源代码发现hint 路由
这里的路由 应该可以爆破出来(我看的hint 没有爆破www)访问 /aazz
然后这里通过抓包可以发现一个filename参数 这个是可以用来进行文件读取的 直接传?filename=flag 就能读取