4-关于魔术方法?

什么是魔术方法

一个预定义好的,在特定情况下自动触发的行为方法。

魔术方法的作用

反序列化漏洞的成因:
反序列化过程中,unserialize()接收的值(字符串)可控;
通过更改这个值(字符串),得到所需要的代码;
通过调用方法,触发代码执行。

常见的魔术方法

image.png

魔术方法相关机制

image.png

__construct()

构造函数,在实例化一个对象的时候,首先会去自动执行的一个方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class User {
public $username;
public function __construct($username) {
$this->username = $username;
echo "触发了构造函数1次" ;
}
}
$test = new User("benben"); //实例化对象时触发构造函数_construct()
$ser = serialize($test);
unserialize($ser);

?>

触发时机:
实例化对象功能: 提前清理不必要内容
参数:非必要
返回值:

就是在 new 一个 User 实例化的时候 自动执行了 __construct 方法
然后 去调用了 username 执行了echo
并不是 $ser 去触发的
**验证 : **

image.png
这样他还是去执行了 echo
证明是实例化的时候触发了__construct()方法

__destruct()

析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class User {
public function __destruct()
{
echo "触发了析构函数1次"."<br />" ;
}
}
$test = new User("benben"); //创建不触发 ,但是创建完执行下一个就触发 了 __destruct
$ser = serialize($test);
unserialize($ser); //反序列化之后 会触发

?>

实例化对象结束后,代码运行完会销毁,触发析构函数_destruct()
这个代码会触发两次 __destruct() 方法
image.png

例题 : __destruct()

image.png
这个题了会执行一次 __destruct

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
error_reporting(0);
class User {
var $cmd = "echo 'dazhuang666!!';" ;
public function __destruct()
{
eval ($this->cmd);
}
}
$ser = $_GET["benben"];
unserialize($ser); // 在这里结束了反序列化之后会去调用 __destruct() 方法

?>

构造 payload :
image.png
这个会执行几次 __destruct 呢
答案 是两次
new 的时候不执行 ,但是当new完之后就 执行了一次 __destruct()方法
所以会出现phpinfo();
然后第二执行 是 echo 完 去调用了 __destruct();
image.png
验证 :
当注释了$a 之后还是会去执行 phpinfo()
image.png
例题payload:

O:4:”User”:1:{s:3:”cmd”;s:10:”phpinfo();”;}

image.png

__sleep()

序列化serialize()函数会检查类中是否存在一个魔术方法_sleep()。
如果存在,该方法会先被调用,然后才执行序列化操作。
此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
如果该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误。

触发时机:序列化serialize()之前
功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
参数:成员属性
返回值:需要被序列化存储的成员属性

例子:
image.png
代码分析 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password) { //第一个触发
$this->username = $username; // username = a
$this->nickname = $nickname; // nickname = b
$this->password = $password; // password = c
}
public function __sleep() { //第二次触发
return array('username', 'nickname');
}
}
$user = new User('a', 'b', 'c'); //new User 传入值 a b c 这里会触发 __construct
echo serialize($user); // 然后去输出 一个序列化的$user ,然后回触发一个__sleep()
// 因为如果序列化前,存在__sleep()方法就会先调用__sleep()方法。
?>

先注释掉 序列化的内容 看是否先调用了 __construct 方法 ,成功赋值呢 ?
image.png
可以看出是成功调用了 __construct 方法,成功赋值了的
然后在加上 序列化
image.png
看调用了 __sleep()
只返回了 username 和 nickname 的值

例题

image.png
很简单只需要传入一个值 ,?benben = 需要执行的命令
因为实例化的时候会去调用__construct 去赋值
$cmd 等于你 要执行的命令 , 就等于 username = $cmd ;nickname= b ; password = c
但是在序列化之前会去调用 __sleep() 方法 然后会吧传入的$cmd 赋值给 username ,然后__sleep() 去调用 username 就执行了 system($cmd );
image.png
本地测试 :
image.png
没有序列化前之调用了__construct() 方法
image.png
成功执行了 __sleep()方法

__wakeup()

unserialize()会检查是否存在一个_wakeup()方法。
如果存在,则会先调用_wakeup()方法,预先准备对象需要的资源。预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。

触发时机:反序列化unserialize()之前
功能:
参数:
返回值:

___wakeup()在反序列化unserialize()之前
__destruct()在反序列化unserialize()之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
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";}';
//字符串里面并没有password
//但是还是输出了password
var_dump(unserialize($user_ser));
?>

输出结果:

object(User)#1 (4) { [“username”]=> string(1) “a” [“nickname”]=> string(1) “b” [“password”:”User”:private]=> string(1) “a” [“order”:”User”:private]=> NULL }

在反序列化之前触发了__wakeup 给 password赋值

例题

image.png
很简单 运行反序列化之前会去调用 __wakeup() 方法
image.png
把其他几个都去掉只留一个 username 因为本来也就只需要这一个

O:4:”User”:1:{s:8:”username”;s:2:”id”;}

image.png
成功执行id 命令

__toStrings()

表达方式错误误导魔术方法触发

触发时机:把对象被当成字符串调用
功能:
参数:
返回值:

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __toString()
{
return '格式不对,输出不了!';
}
}
$test = new User() ;
print_r($test);
echo "<br />";
echo $test;
?>

new 一个 user对象的时候
print_r($test)
是不会去调用 __toString方法的
但是去echo 的时候 就会去调用__toString方法
输出了 格式不对 ,输出不了

__invoke()

格式表达错误导致魔术方法触发

触发时机:把对象当成函数调用
功能:
参数:
返回值:

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __invoke()
{
echo '它不是个函数!';
}
}
$test = new User() ;
echo $test ->benben;
echo "<br />";
//$test(); //这里就当成一个函数来执行了
?>

把类User实体化并赋值给$test为对象正常输出对象里的值benben
加()是把test当成函数test()来调用,此时触发invoke()

image.png

错误调用相关魔术方法

__call()

触发时机:调用一个不存在的方法
功能:
参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数

image.png

调用的方法abc()不存在,触发魔术方法call()
触发call(),传参$arg1,$arg2 ( abc,b)
$arg1,调用的不存在的方法的名称;
$arg2,调用的不存在的方法的参数;

__callStatic()

触发时机:静态调用或调用成员常量时使用的方法不存在
功能:
参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
class User {
public function __callStatic($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test::callxxx('a');
?>

image.png

静态调用::时的方法callxxx()不存在
触发callStatic(),
传参$arg1,$arg2 (callxxx,a)

__get()

触发时机:调用的成员属性不存在
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __get($arg1)
{
echo $arg1;
}
}
$test = new User() ;
$test ->var2;
?>

这里调用的var2 但是成员属性里面没有var2 ,只有var1 所以触发了 __get()方法
image.png
在尝试不触发
image.png
给 var1 赋值
然后去调用 var1
就会输出 没有触发,

__set()

触发时机:给不存在的成员属性赋值
功能:
参数:传参$arg1,$arg2
返回值:不存在的成员属性的名称和赋的值

image.png
就会输出 var2 ,1
和__get 差不多的意思 ,
测试存在成员变量 会成功赋值并输出
image.png

__isset()

触发时机:对不可访问属性使用isset()或empty()时,_isset()会被调用。
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
class User {
private $var;
public function __isset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
isset($test->var);
?>

isset()调用的成员属性var不可访问或不存在
这里是存在的 ,但是变量是不可访问的 所以还是会返回他的var 参数值
触发 __isset()
返回$arg1 不存在成员属性的名称

__unset()

触发时机:对不可访问属性使用unset()时
功能:
参数:传参$arg1
返回值:不存在的成员属性的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
class User {
private $var;
public function __unset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
unset($test->var);
?>

var

和 __isset差不多的

使用的时候是unset 去调用 的时候不存在 或不可访问 就会触发 unset()

__clone()

触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法_clone()
功能:
参数:
返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __clone( )
{
echo "__clone test";
}
}
$test = new User() ;
$newclass = clone($test)
?>

使用clone()克隆对象完成后,触发魔术方法_clone()
image.png
—————————————————————————————————————–
image.png
image.png
——————————————————————————————————————-