目录

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) 
1
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
1
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";
# 运行结果:
# 析构函数执行
# 程序继续
# 析构函数执行
1
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方法触发
1
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方法触发
1
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
# )
1
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;
1
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
1
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 不存在,整点别的?
1
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
1
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)
1
2
3
4
5
6
7
8
9
10
最后一次更新于: 2024/10/21, 01:15:20