序列化:把工具转换为字节序列的过程称为工具的序列化,相称于游戏中的存档。
PHP中的序列化函数serialize()
serialize()函数用于序列化工具或数组,并返回一个字符串。serialize()函数序列化工具后,可以很方便的将它通报给其他须要它的地方,且其类型和构造不会改变。
语法
string serialize ( mixed $value )
参数解释
$value: 要序列化的工具或数组。
返回值
返回一个字符串。
示例:
<?phphighlight_file(__FILE__);$sites=array('I', 'Like', 'PHP');echo'<br/>';var_dump(serialize($sites)); //把这个工具进行序列化echo'<br/>';classman{public$name=34;xiaocui";public$sex="man";private$age=26;}$M=newman();//创建一个工具var_dump(serialize($M)); //把这个工具进行序列化?>
输出结果为:
string(47) "a:3:{i:0;s:1:"I";i:1;s:4:"Like";i:2;s:3:"PHP";}"string(79) "O:3:"man":3:{s:4:"name";s:7:"xiaocui";s:3:"sex";s:3:"man";s:8:"manage";i:26;}"
参数解释:
数组的序列化:a 代表一数组 3 代表数组中有3个元素i 代表数组的下标0 代表I元素的下标值s 代表元素I的数据类型为字符型1 代表元素I的长度为1工具的序列化:O 代表是一个工具3 代表类名man的长度3 代表类中的字段数s 代表属性name的类型为字符型4 代表属性name的长度//后面的以此类推,序列化字符串中字段内容以{开始,;}结束
反序列化:把字节序列规复为工具的过程称为工具的反序列化,相称于游戏中的读档。
【逐一帮助安全学习,所有资源免费获取关注我,私信回答“资料”即可逐一】①网络安全学习路线②20份渗透测试电子书③安全攻防357页条记④50份安全攻防口试指南⑤安全红队渗透工具包⑥信息网络80条搜索语法⑦100个漏洞实战案例⑧安全大厂内部视频资源⑨历年CTF夺旗赛题解析
PHP中的反序列化函数unserialize()
unserialize()函数用于将序列化后的字符串在还原为原来的数组或工具的过程。unserialize()函数可以快速将序列化的字符串还原出来,用来完成它本来的功能。
语法
mixed unserialize ( string $str )
参数解释
$str: 序列化后的字符串。
返回值
返回的是转换之后的值,可为 integer、float、string、array 或 object。不可反序列化时返回FALSE,并抛出提醒。
示例代码:
<?phphighlight_file(__FILE__);$sites=array('I', 'Like', 'PHP');echo'<br/>';echo$ser=serialize($sites).'<br/>'; //把这个工具进行序列化var_dump(unserialize($ser)); //把序列化的字符串进行反序列化echo'<br/>';classman{public$name="xiaocui";public$sex="man";private$age=26;}$M=newman();//创建一个工具echo$ser=serialize($M).'<br/>'; //把这个工具进行序列化var_dump(unserialize($ser)); //把序列化的字符串进行反序列化?>
输出结果为:
a:3:{i:0;s:1:"I";i:1;s:4:"Like";i:2;s:3:"PHP";}array(3) { [0]=>string(1) "I"[1]=>string(4) "Like"[2]=>string(3) "PHP"}O:3:"man":3:{s:4:"name";s:7:"xiaocui";s:3:"sex";s:3:"man";s:8:"manage";i:26;}object(man)#2 (3) { ["name"]=> string(7) "xiaocui" ["sex"]=> string(3) "man" ["age":"man":private]=> int(26) }
可以看出上述反序列化后的数组或工具,与原数据没有任何变革。
序列化与反序列化在系统中的浸染
①把工具的字节序列永久放在磁盘中,须要时可以随时调用,大大节省磁盘占用空间。
②在传输过程中可以直接传输字节序列,而不是工具,这可以大大提高传输速率。
在业务系统中,须要对一些工具进行序列化存储,让他们离开内存空间,存放在一个文件中,以便持久化保存。例如:在用户注册并登录系统时,会将用户信息如用户名,密码,cookie等信息先通过序列化存储起来,当用户再次登录时将序列化后的字节序列通过反序列化成原工具到内存进行利用,可以大大节省内存开销。
2.魔术方法PHP将所有以__(两个下划线)开头的类方法保留为魔术方法。以是在定义类方法时,除了上述魔术方法,建议不要以__为前缀。
PHP中常见魔术方法__construct()具有 __construct函数的类会在每次创建新工具时先调用此方法,适宜在利用工具之前做一些初始化事情。
代码示例:
<?phphighlight_file(__FILE__);classdemo{public$name="xiaocui";public$sex="man";private$age=26;publicfunction__construct(){echo"<br/>"."类被实例化时调用我!
";}}$D=newdemo(); //实例化工具?>
输出结果:
类被实例化时调用我!
__destruct()
该函数会在到某个工具的所有引用都被删除或者当工具被销毁时实行
代码示例:
<?phphighlight_file(__FILE__);class demo{public $name="xiaocui";public $sex="man";private $age=26;public function __construct(){echo "<br/>"."类被实例化时调用我!
"."<br/>";}public function num($a,$b){echo $c = $a + $b.'<br/>';return $c;}public function __destruct(){echo "当类中的所有方法都被销毁时调用我!
";}public function person($per){echo "We are $per !!!".'<br/>';}}$D=new demo(); //实例化工具$D->num(5,6); //调用num()方法$D->person(man); //调用person()方法?>
输出结果:
类被实例化时调用我!
11Weareman!!!当类中的所有方法都被销毁时调用我!
上述输出结果可以很直不雅观的看到代码的实行顺序:__construct()=>num(5,6)=>person(nanren)=>__destruct()
实例化工具时首先要实行布局方法__construct(),然后接着实行类中的实例num()、person(),当所有方法都实行完成销毁时,末了调用析构方法__destruct()。
__wakeup()在利用unserialize()时,会检讨是否存在一个__wakeup()魔术方法。如果存在,则该方法会先被调用,预先准备工具须要的资源。
代码示例:
<?phphighlight_file(__FILE__);classdemo{public$name="xiaocui";protected$sex="man";private$age=26;publicfunction__construct(){echo"<br/>"."类被实例化时调用我!
"."<br/>";}publicfunction__destruct(){echo"<br/>"."当类中的所有方法都被销毁时调用我!
"."<br/>";}publicfunction__wakeup(){echo"<br/>"."当反序列时首先调用我 !
".'<br/>';}}$D=newdemo(); //实例化工具echo$ser=serialize($D); //序列化工具$Dvar_dump(unserialize($ser)); //反序列化字符串$ser?>
输出结果:
由于$age为私有属性,在序列化时会在前后加空缺字符%00,原来的字符长度为7,而在两侧加入空缺字符则字符长度就会变为9
类被实例化时调用我!
O:4:"demo":3:{s:4:"name";s:7:"xiaocui";s:6:"sex";s:3:"man";s:9:"demoage";i:26;}当反序列时调用我 !
object(demo)#2 (3) { ["name"]=> string(7) "xiaocui" ["sex":protected]=> string(3) "man" ["age":"demo":private]=> int(26) }当类中的所有方法都被销毁时调用我!
当类中的所有方法都被销毁时调用我!
上述输出结果可以很直不雅观的看到代码的实行顺序:__construct()=>serialize($D)=>__wakeup()=>unserialize($ser)=>__destruct()=>__destruct()
实例化工具时首先要实行布局方法__construct(),然后实行序列化serialize($D),然后再反序列化之前要实行__wakeup()方法,然后实行反序列化unserialize($ser),当所有方法都实行完成销毁时末了实行__destruct()析构方法,由于反序列化后将原来的序列化字符串还原又实行一次__destruct()。
__toString()__toString()方法用于定义一个类被当成字符串时该如何处理。
示例代码:
<?phphighlight_file(__FILE__);class demo{ public $name="xiaocui"; protected $sex="man"; private $age=26; public function __construct() { echo "<br/>"."类被实例化时调用我!
"."<br/>"; } public function __destruct(){ echo "<br/>"."当类中的所有方法都被销毁时调用我!
"."<br/>"; } public function __wakeup() { echo "<br/>"."当反序列时调用我 !
".'<br/>'; } public function __toString(){ return "<br/>"."类被当成字符串处理时调用我!
"."<br/>"; }}$D=new demo(); //实例化工具echo $D; //类被当成字符串输出?>
输出结果:
类被实例化时调用我!
类被当成字符串处理时调用我!
当类中的所有方法都被销毁时调用我!
上述输出结果可以看到,当类被当成字符串echo时,__toString()方法被调用并实行。
如果该类中没有__toString()方法,进行echo输出时,PHP会抛出致命性缺点,缺点如下:
Catchablefatalerror: ObjectofclassdemocouldnotbeconvertedtostringinD:\XXXX\phpstudy_pro\WWW\two\demo.phponline30
__sleep()
在利用serialize()函数时,程序会检讨类中是否存在一个__sleep()魔术方法。如果存在,则该方法会先被调用,然后再实行序列化操作。
__sleep()方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的工具,但不须要全部保存,该函数就起到了很好的清理浸染。
示例代码:
<?phphighlight_file(__FILE__);class demo{ public $name="xiaocui"; protected $sex="man"; private $age=26; public function __construct() { echo "<br/>"."类被实例化时调用我!
"."<br/>"; } public function __destruct(){ echo "<br/>"."当类中的所有方法都被销毁时调用我!
"."<br/>"; } public function __wakeup() { echo "<br/>"."当反序列时调用我 !
".'<br/>'; } public function __sleep(){ echo "<br/>"."当序列时调用我 !
".'<br/>'; return array("name","sex","age"); //这里必须返回一个数值,里边的元素表示返回的属性名称 }}$D=new demo(); //实例化工具echo $ser = serialize($D); //序列化工具?>
输出结果:
类被实例化时调用我!
当序列时调用我!
O:4:"demo":3:{s:4:"name";s:7:"xiaocui";s:6:"sex";s:3:"man";s:9:"demoage";i:26;}当类中的所有方法都被销毁时调用我!
上述输出结果可以看到,当类被序列化时首先调用了__sleep()方法,该函数必须返回一个数值。如果该函数没有返回属性的话,序列化时会将属性清空。
__invoke()当考试测验以调用函数的办法调用一个工具时,__invoke方法会被自动调用。(本特性只在 PHP 5.3.0 及以上版本有效。)
代码示例:
<?phphighlight_file(__FILE__);class demo{ public $name="xiaocui"; protected $sex="man"; private $age=26; public function __construct() { echo "<br/>"."类被实例化时调用我!
"."<br/>"; } public function __destruct(){ echo "<br/>"."当类中的所有方法都被销毁时调用我!
"."<br/>"; } public function __wakeup() { echo "<br/>"."当反序列化时调用我 !
".'<br/>'; } public function __sleep(){ echo "<br/>"."当序列化时调用我 !
".'<br/>'; return array("name","sex","age"); } public function __invoke() {echo "<br/>"."当以函数的办法调用工具时会调用我!
".'<br/>'; }}$D=new demo(); //实例化工具$D(); //以函数的办法调用工具?>
结果输出:
类被实例化时调用我!
当以函数的办法调用工具时会调用我!
当类中的所有方法都被销毁时调用我!
上述结果可以看到当利用函数的方法调用工具时,就会调用__invoke()方法.
如果没有类中没有该方法,那么PHP会报出致命性缺点,如下:
Fatalerror: UncaughtError: FunctionnamemustbeastringinD:\xxxxx\phpstudy_pro\WWW\two\demo.php:42Stacktrace: #0 {main} thrown in D:\xxxxx\phpstudy_pro\WWW\two\demo.php on line 42
__call()
在工具中调用一个不存在或者不可访问方法时,__call会被调用。
代码示例:
<?phphighlight_file(__FILE__);class demo{ public $name="xiaocui"; protected $sex="man"; private $age=26; public function __construct() { echo "<br/>"."类被实例化时调用我!
"."<br/>"; } public function num($a,$b){ echo "<br/>".$c = $a + $b.'<br/>'; return $c; } public function __destruct(){ echo "<br/>"."当类中的所有方法都被销毁时调用我!
"."<br/>"; } public function person($per){ echo "<br/>"."We are $per !!!".'<br/>'; } public function __wakeup() { echo "<br/>"."当反序列化时调用我 !
".'<br/>'; } public function __sleep(){ echo "<br/>"."当序列时调用我 !
".'<br/>'; return array("name","sex","age"); } public function __call($arg1,$arg2){ echo "<br/>"."当工具调用一个不存在或者不可访问的方法时调用我!
".'<br/>'; }}$D=new demo(); //实例化工具$D->num1(1,2); //调用一个不存在的方法?>
结果输出:
类被实例化时调用我!
__set()
当工具调用一个不存在或者不可访问的方法时调用我!
当类中的所有方法都被销毁时调用我!
给不可访问属性赋值时,__set会被调用。
代码示例:
<?phphighlight_file(__FILE__);class demo extends demo1{ public $name="xiaocui"; protected $sex="man"; private $age=26; public function __construct() { echo "<br/>"."类被实例化时调用我!
"."<br/>"; } public function __destruct(){ echo "<br/>"."当类中的所有方法都被销毁时调用我!
"."<br/>"; } public function person($per){ echo "<br/>"."We are $per !!!".'<br/>'; } public function __set($arg1,$arg2){ echo "<br/>"."当给不存在或者不可访问的属性赋值时调用我!
"."<br/>"; }}class demo1{ private $weight; public $height; public function people(){ echo $this->weight; echo $this->height; }}$D=new demo(); //实例化工具$D->weight=74; //给不可访问的属性赋值?>
输出结果:
类被实例化时调用我!
__isset()
当给不存在或者不可访问的属性赋值时调用我!
当类中的所有方法都被销毁时调用我!
对不可访问属性调用isset()或empty()时,__iset()会被调用。
__unset()对不可访问属性调用unset()时,__unset()会被调用。
__get()读取不可访问属性的值时,__get会被调用。
代码示例:
<?phphighlight_file(__FILE__);class demo extends demo1{ public $name="xiaocui"; protected $sex="man"; private $age=26; public function __construct() { echo "<br/>"."类被实例化时调用我!
"."<br/>"; } public function __destruct(){ echo "<br/>"."当类中的所有方法都被销毁时调用我!
"."<br/>"; } public function __get($arg1){ echo "<br/>"."读取不存在或不可访问的属性时调用我!
"; }}class demo1{ private $weight = 0; public $height; public function people(){ echo $this->weight; echo $this->height; }}$D=new demo(); //实例化工具$D->weight; //读取父类中不可访问的属性?>
输出结果:
类被实例化时调用我!
3.反序列化漏洞3.1 反序列化漏洞利用条件
读取不存在或不可访问的属性时调用我!
当类中的所有方法都被销毁时调用我!
① unserialize()函数中参数可控② 存在可利用的类,且类中有魔术方法
代码示例1:
<?phphighlight_file(__FILE__);class demo{ public $arg1 = "0"; public function __destruct() { echo $this->arg1; //输出用户通报的arg1值 }}$a=$_GET['arg']; //吸收前端通报的arg1变量$unser = unserialize($a); //反序列化通报的arg1?>
上述的代码知足反序列化漏洞的两个条件,arg参数可控,也便是unserialize()函数的参数可控;存在可利用的类demo,且demo类中有可利用的魔术方法__destruct(),然而__destruct()魔术方法中利用echo输出变量了arg。
通过arg参数,布局序列化字符串,通过unserialize()函数进行反序列化,末了通过__destruct()魔术方法输出变量,造成XSS。
示例代码2
<?phphighlight_file(__FILE__);class demo{ public $arg1 = "0"; public function __destruct() { eval($this->arg1); //eval()去实行用户通报的arg1值 }}$a=$_GET['arg']; //吸收前端通报的arg1变量$unser = unserialize($a); //反序列化通报的arg1var_dump($unser);?>
如果魔术方法中存在eval(),且参数可控,那么通过布局序列化字符串,通过反序列化漏洞造成RCE。
3.2 __wakeup()函数绕过
在序列化时通报工具的属性大于实际工具的属性时,__wakeup()魔术方法将不会被实行,从而导致绕过。
PHP版本<=5.6.25或者PHP版本<=7.0.11
示例代码:
<?phphighlight_file(__FILE__);class demo{ public $arg1 = "0"; public function __destruct() { eval($this->arg1); //eval()去实行用户通报的arg1值 } public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = ''; //将传入的参数遍历,全部赋值为空 } }}$a=$_GET['arg']; //吸收前端通报的arg1变量$unser = unserialize($a); //反序列化通报的arg1var_dump($unser);?>
如果属性值与工具实际属性值相同,则会在反序列化时实行__wakeup()函数将传入的变量值更换为空。
如果属性值设置大于实际工具属性值则会绕过__wakeup()函数。