【Bugku CTF】 Web —— welcome to bugkuctf
0x00 前言
此题运用了许多 PHP 技巧,解题思路惊艳,特此想分享一下思考过程。与其说此题是 Web 题,不如说是道 PHP 技巧题,难度中等,要求有扎实的 PHP 基础。
涉及到的技巧有 PHP 伪协议、魔术方法、对象的序列化与反序列化等,相关链接如下:
0x01 PHP 伪协议获取源码
点开链接,依然按常规套路只有一句话:you are not the number of bugku ! ,继续查看源码发现线索:
这里需要满足两点:
- 构造合适的 URL 查询变量
txt
使得 PHP 变量$user
满足 if 语句的条件 - 令变量
$file
通过文件包含函数include()
获取指定页面的源码
注意,此处还未涉及到查询变量 password
。
php://input
在 if 语句中,通过 file_get_contents()
函数读取变量 $user
的值后,再与字符串 welcome to the bugkuctf
比较。
因此可借助伪协议 php:// 中的 php://input
访问原始请求数据中的只读流。这里令 $user = "php://input"
,并在请求主体中提交字符串 welcome to the bugkuctf
。
php://filter
在 include($file); //hint.php
语句中,相当于把变量 $file
的值在该语句的位置读入,结合后面的提示,应该是要把 hint.php 页面的源码以某种形式包含在当前页面内。
因此可利用 php://filter
以某种方式筛选过滤特定的数据流。这里令 $file = php://filter/read=convert.base64-encode/resource=hint.php
,意思是以 Base64 编码的方式过滤出 hint.php 页面的源码,也可以把它理解为一个具有 URI 形式的特殊函数,该函数能把 hint.php 源码以 Base64 编码的方式读入。
根据以上两种 PHP 伪协议的分析,构造出以下 payload 即得 hint.php 源码的 Base64 编码:
解码后得到 hint.php 源码:
1 |
|
根据注释发现了藏 flag 的页面,再次照葫芦画瓢去偷看 flag.php 源码:
…果然没那么简单,唔,卡住了,怎么办?
别忘了还有主页面啊!利用主页面的功能也能读取自身的源码,这一点很容易被遗忘,所以这次很顺利就获得了 index.php 的源码(注意,路径末尾不带 php 文件的 URL http://120.24.86.145:8006/test1/
,一般默认就是访问 http://120.24.86.145:8006/test1/index.php
):
1 |
|
注意到第 8 行将 $file
变量中的 flag
字符串过滤了,印证了前面的结果。到此,我们顺利地将此题变成一道 PHP 白盒审计题了,现在才是此题最有意思的地方,如何利用已知的 hint.php 与 index.php 的源码去获取 flag.php 的内容?请继续往下看。
0x02 序列化构造 payload
要理清 hint.php 与 index.php 之间的关系,首先要理解 hint.php 中的 __tostring()
函数是何方神圣。
魔术方法 __toString()
「写在前面」:PHP 中变量与方法的命名一般遵循小驼峰式命名法(Lower Camel Case),类的命名一般遵循大驼峰式命名法(Upper Camel Case),也称帕斯卡命名法(Pascal Case),并且普通变量、超级全局变量、常量、数组索引等区分大小写,而函数名、方法名、类名、魔术变量等不区分大小写,但最好使用与定义一样的大小写名字。详情可参考:
因此看到此魔术方法定义为 __toString()
,在源码中却是 __tostring()
就不足为奇了。
魔术方法 __toString() 定义在类中,在该类的对象被当成字符串打印时执行,并且必须返回一个字符串,否则出现报错。
因此在 hint.php 中,当 Flag
类的对象被打印时,将获取 $file
变量(注意此处的 $file
与 index.php 中的不同)中文件的内容并输出,最后返回字符串 good
。
对象的序列化
再来看看 index.php 的 11-15 行,满足了上面所有 if 语句的条件后,先包含变量 $file
,再反序列化变量 $password
后输出。此段代码是本题的核心,理解了就能找到 index.php 与 hint.php 之间的联系,从而构造出 payload。
核心思想如下:先用 include()
函数包含 hint.php,从而引入 Flag
类;再将该类对象的序列化字符串赋值给 $passsword
,且令对象的成员变量 $file = flag.php
, 所以在反序列化后得到上述对象;最后用 echo
打印该对象,从而触发 __toString()
函数,输出 flag.php 的值。
通过以下代码可获得成员变量 $file = flag.php
的 Flag
对象的序列化字符串:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
1 |
|
根据以上所有的分析,可以将 payload 总结如下:
http://120.24.86.145:8006/test1/?txt=php://input&file=hint.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
最后别忘了在请求主体中提交字符串 welcome to the bugkuctf
喔,提交后得到 flag:
若有不足或错误之处劳烦指出,欢迎有疑问的朋友前来留言讨论。