0%

PHP反序列化基础

春招的时候公司也会问,就顺便多学一些好了

  • 引入- json 编码

    即使 json 的序列化与 php 关系不大,但以其引入更加清晰。对于函数 json_encode() ; json_decode()

    1
    2
    3
    4
    5
    <?php
    $qwq=array('name1'=>'vme50','name2'=>'111');
    $json=json_encode($qwq);
    echo $json;
    ?>

    json_encode 将数组 qwq 转化为了便于传送储存的字符串,其中以键对值存储

    那么扩展到一个 class 上,为了方便存储与传递,php 可以将其转化为一个字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
    class qwq
    {
    public $name="neko";
    public $age="18";
    public $tip="cat";
    }
    $test=new qwq();
    echo serialize($test);
    ?>

    O 代表 Object ,后面的 3 代表对象名称 qwq 占三个字符;再后面的 3 代表对象里有三个变量。s 代表数据类型为 string ,还可能为 i ,即 int ;紧跟的数字即为后面变量名的长度

    如此就达到了 php 的序列化

    即关键函数为:

    1
    2
    serialize()
    unserialize()
  • 漏洞产生

    • 魔法函数

      常常以 __ 开头,通常通过条件触发而非手动调用。若进行反序列化时碰见以下函数就要仔细研究

      1
      2
      3
      4
      5
      __construct()  当一个对象创建时被调用
      __destruct() 当一个对象销毁时被调用
      __toString() 当一个对象被当作一个字符串使用
      __sleep() 在对象在被序列化之前运行
      __wakeup() 将在序列化之后立即被调用

      如果服务器接受我们所上传的反序列化字符串,并将未经过滤的变量名直接放进这些函数中时,会造成严重漏洞,例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <?php
      class exp{
      var $word = "testtt";
      function __construct(){
      echo "start exp construct<br />";
      }
      function __destruct(){
      echo $this->word;
      echo "<br />exp destruct<br />";
      }
      }

      echo "initing<br />";
      $b=new exp();
      echo "init b end<br />";
      $a = $_GET['test'];
      echo "receive a<br />";
      $a_unser = unserialize($a);
      echo "init a end<br />";
      ?>

      payload?test=O:3:"exp":1:{s:4:"word";s:5:"hello";}

      笔者在这里写了一份更清晰的代码来更好的理解反序列化如何实现

      首先新建了名为 b 的对象,所以自动执行了 __construct() ,发送出第二句;而接受 a 的过程中并未发生对象的新建或删除,因而没有执行语句;在最后结束部分,依次删除了 a,b 故执行两次 __destruct()

      而由于用户的提交,对象 a$word 值已经被更改且对其并无过滤,所以出现了反序列化漏洞

    • 看一道例子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <?php
      //flag is in pctf.php
      class Shield {
      public $file;
      function __construct($filename = '') {
      $this -> file = $filename;
      }
      function readfile() {
      if (!empty($this->file) && stripos($this->file,'..')===FALSE
      && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
      return @file_get_contents($this->file);
      }
      }
      }
      require_once('shield.php');
      $x = new Shield();
      isset($_GET['class']) && $g = $_GET['class'];
      if (!empty($g)) {
      $x = unserialize($g);
      }
      echo $x->readfile();
      ?>

      唯一的输出是 shield 对象中的 readfile() 函数,函数的判断只有非空判断及一个简单的 \\

      而新建对象 x 的过程会激活 __construct() 函数,但其并不对我们对·$file 的操作

      因此只需要输入序列化后的字符串使变量 file 值为 pctf.php 即可

      payload 即为 ?class=O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}