PHP反序列化---魔术方法
# PHP反序列化---魔术方法
PHP是世界上最好的语言
入门反序列化,最近刚做题很容易忘了魔术方法的触发时机和作用,记录下魔术方法,附带示例方面后续查阅,篇幅可能较长
推荐
这篇文章写的比较水,推荐一下陈腾老师和包师傅博客php反序列化\pop (opens new window)
B站up:橙子科技 陈腾老师 (opens new window)
# 0. 触发时机
__construct(): 构造函数,在实例化一个对象的时候,首先会去自动执行的一个方法;
__destruct(): 析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法。
__toString(): echo或者print只能调用字符串的方式去调用对象,即把对象当成字符串使用,此时自动触发toString()
__invoke(): 把test对象当成函数test()来调用,此时触发invoke()
__call($method, $arguments): 调用的不存在的方法的名称和参数
__sleep():在调用 serialize() 函数序列化对象之前被调用
__wakeup():调用unserialize()反序列化函数时反序列化之后调用
__get($property): 调用的成员属性不存在,用于获取对象的属性值。
__set($property, $value): 给不存在的成员属性赋值。用于设置对象的属性值。
__isset($property): 对不可访问属性使用 isset() 或 empty() 时,__isset() 会被调用。用于检测属性是否被设置。
__unset($property): 在使用 unset() 删除一个不可访问属性时自动调用的方法。用于删除对象的属性。
__callStatic($method, $arguments): 静态调用或调用成员常量时使用的方法不存在。用于处理静态方法的调用。
注意:部分题目正则过滤O:数字,在数字前加+号绕过时注意+号需要url编码,cookie里添加payload需要url编码
类名,方法名不区分大小写,属性/变量名区分大小写
# 过滤
if (preg_match('/[oc]:\d+:/i',$cmd))
# 替换
str_replace('O:','O:+',$cmd)
2
3
4
# 1. 示例
# 1.1 __construct()构造函数
触发时机:在实例化对象时会触发__construct()
<?php
class test{
public $var1;
public $var2;
public function __construct($x,$y){
$this->var1 = $x;
$this->var2 = $y;
}
}
$test =new test('hello','world');
echo $test->var1;
echo $test->var2;
# 运行结果:
# helloworld
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1.2 析构函数
触发时机:在程序结束或显示销毁对象时触发destruct()
显示销毁:通过显式地将对象引用设置为 null 来间接触发对象的销毁
<?php
class test{
public function __destruct(){
echo '析构函数执行';
}
}
$test0 =new test();
$test1 = new test();
$test0 = null;
echo "\n程序继续\n";
# 运行结果:
# 析构函数执行
# 程序继续
# 析构函数执行
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.3 __toString()方法
触发时机:尝试echo打印对象时触发,返回一个字符串
<?php
class test{
public function __toString(){
return 'toString方法触发';
}
}
$test = new test();
echo $test;
# 运行结果:
# toString方法触发
2
3
4
5
6
7
8
9
10
# 1.4 __invoke()方法
__invoke(): 把test对象当成函数test()来调用,此时触发invoke()
<?php
class test{
public function __invoke(){
echo 'invoke方法触发';
}
}
$test = new test();
$test();
# 运行结果:
# invoke方法触发
2
3
4
5
6
7
8
9
10
# 1.5 __call方法
触发时机:调用类不存在的方法
<?php
class test{
public function __call($name,$arguments){
echo "$name 方法不存在 带着你的参数有夺远爬多远";
echo "\n";
print_r($arguments);
}
}
$test = new test();
$test -> hello('world');
# 运行结果:
# hello 方法不存在 带着你的参数有夺远爬多远
# Array
# (
# [0] => world
# )
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1.6 __sleep()方法
触发时机:在调用 serialize() 函数序列化对象之前被调用
<?php
class test{
public function __sleep(){
echo "sleeeep触发\n";
}
}
$test = new test();
echo "序列化结果".serialize($test);
# 运行结果
# sleeeep触发
# 序列化结果 N;
2
3
4
5
6
7
8
9
10
11
# 1.7 __wakeup()方法
CVE-2016-7124,当成员属性大于实际存在的属性个数时,会绕过wakeup方法
版本:5-5.6.25 7-7.0.10
例如:payload: O:4:"test":2:{s:3:"var";s:1:"2";}
提示
已知存在多种对于其他版本的wakeup绕过姿势,自行查阅
触发时机:调用unserialize()反序列化函数时反序列化之后调用
示例中,先反序列化把hello给$evil,再调用wakeup,打印hello
<?php
class test{
public $evil;
public function __wakeup(){
echo $this->evil;
}
}
$test = new test();
$test->evil = 'hello';
unserialize(serialize($test));
# 运行结果:
# hello
2
3
4
5
6
7
8
9
10
11
12
# 1.8 __get($argu)方法
触发时机:对象调用不存在的成员属性
<?php
class test{
public function __get($argu){
echo "$argu 不存在,整点别的?";
}
}
$test =new test();
echo $test-> hello;
# 运行结果:
# hello 不存在,整点别的?
2
3
4
5
6
7
8
9
10
# 1.9 __set($param,$value)方法
触发时机:在给不存在或没有访问权限的属性赋值时触发
搭配get方法,使用一个数组储存键值对
<?php
class test{
public $data=[];
public function __set($param,$value){
$this->data[$param] = $value;
}
public function __get($param){
return $this->data[$param];
}
}
$test =new test();
$test->hello = 'world';
echo $test->hello;
# 运行结果:
# world
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1.10 __isset()方法
触发时机:对属性使用 isset() 或 empty() 时,__isset() 会被调用。用于检测属性是否被设置。
这个暂时不太清楚魔术方法怎么定义...报一丝,直接用 来演示了,后续补充
<?php
class test{
public $var1=11;
}
$test =new test();
var_dump(isset($test->var1));
var_dump(isset($test->var2));
# 运行结果:
# bool(true)
# bool(false)
2
3
4
5
6
7
8
9
10