PHP对象注入并不是一种十分常见的漏洞,这种漏洞可能会比较难利用,但是这种漏洞可能比较危险。为了学习这种漏洞,首先需要理解基础的PHP代码。
0x01 PHP的类和对象
HP中的类和对象理解起来很简单,举个例子来说,下面的代码中就定义了一个包含一个变量和一个方法的类:
<?php
class TestClass //定义一个TestClass类
{
public $variable = 'This is a string'; // 一个变量
public function PrintVariable() // 一个简单的方法
{
echo $this->variable;
}
}
$object = new TestClass(); // 创建一个对象
$object->PrintVariable(); // 调用类的方法
?>
0x02 PHP自动载入
在PHP中,我们可以定义一些特殊函数,它们可以被自动地调用,所以这些函数不需要函数调用来执行它们里面的代码。考虑到这个特性,这些函数通常被称为魔幻函数或魔幻方法。魔幻方法的名字以双下划线“__”开头PHP魔幻方法名称受限于PHP所支持的部分关键字,例如construct、destruct等等。
例如__construct()方法会在一个对象被创建时自动调用,对应的__destruct则会在一个对象被销毁时调用等等。
下面是PHP中的一些魔幻函数:
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(),
__isset(), __unset(), __sleep(), __wakeup(), __toString(),
__invoke(), __set_state(), __clone(), and __autoload().
0x03 PHP对象的序列化与反序列化
php允许保存一个对象方便以后重用,这个过程被称为序列化,即serialize()函数,对应的反序列化就是unserialize()。
简单的说,序列化就是把PHP对象按照一定规则存成一个字符串,而反序列化就是把序列化之后的字符串恢复成一个PHP对象.
PHP对象的序列化
理解序列化的字符串:
O:4{ 以分号隔开,一共有四段数据
"test":2 test类,一共有2个对象
s:8:"username" String类型, 长度为8, 值为username
s:6:"serial" String类型, 长度为6, 值为serial
s:8:"password" String类型, 长度为8, 值为password
s:6:"serial" String类型, 长度为6, 值为serial
PHP对象的反序列化
经过unserialize()之后,我们的对象又被重建了出来.
0x04 PHP对象注入
有两个比较特别的Magic方法,__sleep 方法会在一个对象被序列化的时候调用。 __wakeup方法会在一个对象被反序列化的时候调用。
那么,考虑这么一个场景,如果一个攻击者的数据是通过unserialize()方法传递的,那么这样就很可能引发“php对象注入”,而很可能那些与该对象有关的Magic方法会执行一些效果,打个比方,如果该对象是一个记录临时文件的对象,当对象创建的时候,即会调用 __construct方法,此时会创建一个文件,而 __destruct则会删除创建的文件。而此时恰好我们可以构造输入通过unserialize()传递,那么我们就可以对我们的输入稍作加工就可以完成输入我们想要输入的。
示例代码:
<?php
class test
{
public $filename = 'temp.txt';
public function LogData($text)
{
file_put_contents($this->filename, $text, FILE_APPEND); //file_put_contents() 函数把一个字符串写入文件中。与依次调用 fopen(),fwrite() 以及 fclose() 功能一样。
}
public function __destruct()
{
unlink(dirname(__FILE__) . '/' . $this->filename); //unlink() 函数删除文件
}
}
class user
{
public $age = 0;
public $name = '';
public function PrintData()
{
echo 'Username:'.$this->name.'<br>'.'age:'.$this->age;
}
}
if(isset($_GET['user']))
$usr = unserialize($_GET['user']);
?>
定义了一个test类生成一个临时文件和一个user类,最后有一个 “unserialize” 值,它的本意是传递user的信息,但是如果我们输入一个字符串最后能够被反序列化为test类,那么问题就关键了,因为一个test类最后会删除它的同名文件夹,如果此时我们通过如下代码构造一串字符串:
<?php
//include 'object_injection.php';
class test{}
$obj = new test();
$obj->filename = '.htaccess';
echo serialize($obj) . '<br />';
?>
运行结果为:
O:4:”test”:1:{s:8:”filename”;s:9:”.htaccess”;}
新建一个.htaccess
的文件,然后将打印出来的结果传入到之前那个php中,那么最终删除的就是这个’.htaccess’文件了。因为脚本结束时 __destruct会被调用,从而触发删除。
这就是该漏洞名字的由来:作为一个攻击者,为了实现执行你的代码或者对你来说其他未预料到的有用的表现,不使用期望的序列化的对象,而是注入其他的PHP对象。
0x05 PHP对象注入防御
在处理由用户提供数据的地方不要使用“unserialize”,你可以使用“json_decode”。