主要目的
借著本次機(jī)會(huì)系統(tǒng)的學(xué)習(xí)反序列化漏洞劫扒,和PHP的一些語句的具體用法
問題原因:
漏洞的根源在于unserialize()函數(shù)的參數(shù)可控适揉。如果反序列化對象中存在魔術(shù)方法,而且魔術(shù)方法中的代碼或變量用戶可控徐钠,就可能產(chǎn)生反序列化漏洞呜魄,根據(jù)反序列化后不同的代碼可以導(dǎo)致各種攻擊灌旧,如代碼注入、SQL注入悴晰、目錄遍歷等等棵逊。
魔術(shù)方法:PHP的類中可能會(huì)包含一些特殊的函數(shù)叫魔術(shù)函數(shù)甫何,魔術(shù)函數(shù)命名是以符號__開頭的; 有以下的魔術(shù)方法: __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set(), _state(), __clone(), __debugInfo()
__sleep 和__wakeup
序列化serialize可以把變量包括對象,轉(zhuǎn)化成連續(xù)bytes數(shù)據(jù). 你可以將序列化后的變量存在一個(gè)文件里或在網(wǎng)絡(luò)上傳輸. 然后再反序列化還原為原來的數(shù)據(jù). 你在反序列化類的對象之前定義的類,PHP可以成功地存儲(chǔ)其對象的屬性和方法. 有時(shí)你可能需要一個(gè)對象在反序列化后立即執(zhí)行. 為了這樣的目的,PHP會(huì)自動(dòng)尋找__sleep和__wakeup方法.
當(dāng)一個(gè)對象被序列化,PHP會(huì)調(diào)用__sleep方法(如果存在的話). 在序列行化一個(gè)對象后,PHP 會(huì)調(diào)用__wakeup方法. 這兩個(gè)方法都不接受參數(shù). __sleep方法必須返回一個(gè)數(shù)組,包含需要序列化化的屬性.PHP會(huì)拋棄其它屬性的值. 如果沒有__sleep方法,PHP將保存所有屬性.
在程序執(zhí)行前,serialize() 函數(shù)會(huì)首先檢查是否存在一個(gè)魔術(shù)方法 __sleep.如果存在,__sleep()方法會(huì)先被調(diào)用旁涤, 然后才執(zhí)行串行化(序列化)操作。這個(gè)功能可以用于清理對象搓萧,并返回一個(gè)包含對象中所有變量名稱的數(shù)組讳窟。如果該方法不返回任何內(nèi)容,則NULL被序列化饲帅,導(dǎo)致 一個(gè)E_NOTICE錯(cuò)誤复凳。與之相反,unserialize()會(huì)檢查是否存在一個(gè)__wakeup方法灶泵。如果存在育八,則會(huì)先調(diào)用 __wakeup方法,預(yù)先準(zhǔn)備對象數(shù)據(jù)赦邻。
__sleep() 方法常用于提交未提交的數(shù)據(jù)髓棋,或類似的清理操作。同時(shí)惶洲,如果有一些很大的對象按声,但不需要全部保存,這個(gè)功能就很好用恬吕。
與之相反签则,unserialize() 會(huì)檢查是否存在一個(gè) __wakeup() 方法。如果存在铐料,則會(huì)先調(diào)用 __wakeup 方法渐裂,預(yù)先準(zhǔn)備對象需要的資源豺旬。
__wakeup() 經(jīng)常用在反序列化操作中,例如重新建立數(shù)據(jù)庫連接柒凉,或執(zhí)行其它初始化操作族阅。
<?php
class Connection
{
protected $link;
private $server, $username, $password, $db;
public function __construct($server, $username, $password, $db)
{
$this->server = $server;
$this->username = $username;
$this->password = $password;
$this->db = $db;
$this->connect();
}
private function connect()
{
$this->link = mysql_connect($this->server, $this->username, $this->password);
mysql_select_db($this->db, $this->link);
}
public function __sleep()
{
return array('server', 'username', 'password', 'db');
}
public function __wakeup()
{
$this->connect();
}
}
?>
下面例子顯示了如何用__sleep和 __wakeup方法來序列化一個(gè)對象. Id屬性是一個(gè)不打算保留在對象中的臨時(shí)屬性. __sleep方法保證在串行化的對象中不包含id屬性. 當(dāng)反串行化一個(gè)User對象,__wakeup方法建立id屬性的新值. 這個(gè)例子被設(shè)計(jì)成自我保持. 在實(shí)際開發(fā)中膝捞,你可能發(fā)現(xiàn)包含資源(如圖像或數(shù)據(jù)流)的對象需要這些方法坦刀。
<?php
class user {
public $name;
public $id;
function __construct() { // 給id成員賦一個(gè)uniq id
$this->id = uniqid();
}
function __sleep() { //此處不串行化id成員
return(array('name'));
}
function __wakeup() {
$this->id = uniqid();
}
}
$u = new user();
$u->name = "Leo";
$s = serialize($u); //serialize串行化對象u,此處不串行化id屬性蔬咬,id值被拋棄
$u2 = unserialize($s); //unserialize反串行化鲤遥,id值被重新賦值
//對象u和u2有不同的id賦值
print_r($u);
print_r($u2);
?>
user Object
(
[name] => Leo
[id] => 5d0d9c66b9a03
)
user Object
(
[name] => Leo
[id] => 5d0d9c66b9a45
)
<?php
class Person
{
private $name, $age, $sex, $info;
public function __construct( $name, $age, $sex )
{
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
$this->info = sprintf("prepared by construct magic functionname: %s age: %d sex: %s",
$this->name, $this->age, $this->sex);
}
public function getInfo()
{
echo $this->info . PHP_EOL;
}
/**
* serialize前調(diào)用 用于刪選需要被序列化存儲(chǔ)的成員變量
* @return array [description]
*/
public function __sleep()
{
echo __METHOD__ . PHP_EOL;
//序列化時(shí)只會(huì)存儲(chǔ) name age sex, info 不會(huì)被序列化
return ['name', 'age', 'sex'];
}
/**
* unserialize前調(diào)用 用于預(yù)先準(zhǔn)備對象資源
*/
public function __wakeup()
{
echo __METHOD__ . PHP_EOL;
$this->info = sprintf("prepared by wakeup magic function name: %s age: %d sex: %s",
$this->name, $this->age, $this->sex);
}
}
$boy = new Person( 'sallency', 25, 'male' );
//構(gòu)造函數(shù)組裝的 $info
$boy->getInfo();
echo "<hr>";
//序列化時(shí)并不會(huì)存儲(chǔ) $info 屬性
$temp = serialize($boy);
echo $temp . PHP_EOL;
echo "<hr>";
//反序列化時(shí)會(huì)調(diào)用 __wakeup() 函數(shù)
$boy = unserialize($temp);
//__wakeup() 組裝的 $info
$boy->getInfo();
echo "<hr>";
?>
prepared by construct magic functionname: sallency age: 25 sex: male
<hr>Person::__sleep
O:6:"Person":3:{s:12:"Personname";s:8:"sallency";s:11:"Personage";i:25;s:11:"Personsex";s:4:"male";}
<hr>Person::__wakeup
prepared by wakeup magic function name: sallency age: 25 sex: male
<hr>
先寫一段代碼
class myClass{
public $myContent;
function outMycontent(){
//dosomething
}
}
$content = new myClass();
echo serialize($content);
輸出的結(jié)果是O:7:”myClass”:1:{s:9:”myContent”;N;}
它竟然把一個(gè)類的給序列化了,也就是把一個(gè)類轉(zhuǎn)換成了一個(gè)字符串计盒,可以傳輸或者保存下來渴频。
下面我修改一下上面的代碼
class myClass{
public $myContent;
function __construct($string){
$this->myContent = $string;
}
}
$content = new myClass('my china');
echo serialize($content);
輸出的結(jié)果是O:7:”myClass”:1:{s:9:”myContent”;s:8:”my china”;}
序列化后也對應(yīng)了相應(yīng)的值,但是現(xiàn)在有個(gè)問題北启,比如我這個(gè)變量是個(gè)秘密呢卜朗?而且我又得把這個(gè)類序列化傳給別的地方呢?
看下面的代碼
class myClass{
public $myContent;
function __construct($string){
$this->myContent = $string;
}
}
$content = new myClass('我愛宋祖英咕村,這是一個(gè)秘密');
echo serialize($content);
輸出的結(jié)果是O:7:”myClass”:1:{s:9:”myContent”;s:36:”我愛宋祖英场钉,這是一個(gè)秘密”;}
我的秘密序列化后還是存在的,可是我不想我的心里話被別人看到懈涛。這個(gè)時(shí)候PHP很貼心逛万,她知道你的問題,所以設(shè)置了魔術(shù)方法批钠。
__sleep() 就表示當(dāng)你執(zhí)行serialize()這個(gè)序列化函數(shù)之前時(shí)的事情宇植,就像一個(gè)回調(diào)函數(shù),所以在這個(gè)回調(diào)函數(shù)里面我們就可以做點(diǎn)事情埋心,來隱藏我的秘密指郁。
class myClass{
public $myContent;
function __construct($string){
$this->myContent = $string;
}
public function __sleep(){
$this->myContent = '這是我的秘密';
return array('myContent');
}
}
$content = new myClass('我愛宋祖英,這是一個(gè)秘密');
echo serialize($content);
輸出的結(jié)果是:O:7:”myClass”:1:{s:9:”myContent”;s:18:”這是我的秘密”;}
我的心里話被加密了拷呆,這個(gè)就是__sleep()的作用闲坎。至于__wakeup()和__sleep()大同小異,只不過是反序列化之前進(jìn)行的回調(diào)函數(shù)茬斧。我不詳細(xì)說了腰懂,大家看下下面的代碼就明白了。
class myClass{
public $myContent;
function __construct($string){
$this->myContent = $string;
}
public function __sleep(){
$this->myContent = '這是我的秘密';
return array('myContent');
}
public function __wakeup(){
$this->myContent = '我的秘密又回來了';
//反序列化就不用返回?cái)?shù)組了项秉,就是對應(yīng)的字符串的解密绣溜,字符串已經(jīng)有了就不用其他的了
}
}
$content = new myClass('我愛宋祖英,這是一個(gè)秘密');
print_r(unserialize(serialize($content)));
輸出的內(nèi)容為:myClass Object ( [myContent] => 我的秘密有回來了 )
__toString
__toString() 方法用于定義一個(gè)類被當(dāng)成字符串時(shí)該如何處理伙狐。
__toString是在直接輸出對象引用時(shí)自動(dòng)調(diào)用的涮毫, 前面我們講過對象引用是一個(gè)指針瞬欧,比如說:“p就是一個(gè)引用罢防,我們不能使用echo 直接輸出$p, 這樣會(huì)輸出”Catchable fatal error: Object of class Person could not be converted to string“這樣的錯(cuò)誤,如果你在類里面定義了“__toString()”方法唉侄,在直接輸出對象引用的時(shí)候咒吐,就不會(huì)產(chǎn)生錯(cuò)誤,而是自動(dòng)調(diào)用了”__toString()”方法, 輸出“__toString()”方法中返回的字符属划,所以“__toString()”方法一定要有個(gè)返回值(return 語句).
<?php
class Person{
private $name = "";
function __construct($name = ""){
$this->name = $name;
}
function say(){
echo "Hello,".$this->name."!<br/>";
}
function __tostring(){//在類中定義一個(gè)__toString方法
return "Hello,".$this->name."!<br/>";
}
}
$WBlog = new Person('WBlog');
echo $WBlog;//直接輸出對象引用則自動(dòng)調(diào)用了對象中的__toString()方法
$WBlog->say();//試比較一下和上面的自動(dòng)調(diào)用有什么不同
?>
程序輸出:
Hello,WBlog!
Hello,WBlog!
如果不定義“__tostring()”方法會(huì)怎么樣呢恬叹?例如在上面代碼的基礎(chǔ)上,把“ __tostring()”方法屏蔽掉同眯,再看一下程序輸出結(jié)果:
Catchable fatal error: Object of class Person could not be converted to string
由此可知如果在類中沒有定義“__tostring()”方法绽昼,則直接輸出以象的引用時(shí)就會(huì)產(chǎn)生誤法錯(cuò)誤,另外__tostring()方法體中需要有一個(gè)返回值须蜗。
__invoke
當(dāng)嘗試以調(diào)用函數(shù)的方式調(diào)用一個(gè)對象時(shí)硅确,__invoke() 方法會(huì)被自動(dòng)調(diào)用。(本特性只在 PHP 5.3.0 及以上版本有效明肮。)
<?php
class Demo{
public function __invoke(){
echo "測試";
}
}
$demo = new Demo;
$demo();
?>
這樣的話,直接用對象名就當(dāng)函數(shù)使用了,調(diào)用的是_invoke的方法;
輸出
測試
__construct 構(gòu)造方法 __destruct 析構(gòu)方法
__construct()在每次創(chuàng)建新對象時(shí)先調(diào)用此方法
__destruct() 允許在銷毀一個(gè)類之前執(zhí)行執(zhí)行析構(gòu)方法菱农。
<?php
/**
* 清晰的認(rèn)識__construct() __destruct()
*/
class Example {
public static $link;
//在類實(shí)例化的時(shí)候自動(dòng)加載__construct這個(gè)方法
public function __construct($localhost, $username, $password, $db) {
self::$link = mysql_connect($localhost, $username, $password);
if (mysql_errno()) {
die('錯(cuò)誤:' . mysql_error());
}
mysql_set_charset('utf8');
mysql_select_db($db);
}
/**
* 通過__construct鏈接好數(shù)據(jù)庫然后執(zhí)行sql語句......
*/
//當(dāng)類需要被刪除或者銷毀這個(gè)類的時(shí)候自動(dòng)加載__destruct這個(gè)方法
public function __destruct() {
echo '<pre>';
var_dump(self::$link);
mysql_close(self::$link);
var_dump(self::$link);
}
}
$mysql = new Example('localhost', 'root', 'root', 'test');
結(jié)果:
resource(2) of type (mysql link)
resource(2) of type (Unknown)
__set __get
__get()方法:這個(gè)方法用來獲取私有成員屬性值的,有一個(gè)參數(shù),參數(shù)傳入你要獲取的成員屬性的名稱柿估,返回獲取的屬性值循未。如果成員屬性不封裝成私有的,對象本身就不會(huì)去自動(dòng)調(diào)用這個(gè)方法秫舌。
__set()方法:這個(gè)方法用來為私有成員屬性設(shè)置值的的妖,有兩個(gè)參數(shù),第一個(gè)參數(shù)為你要為設(shè)置值的屬性名足陨,第二個(gè)參數(shù)是要給屬性設(shè)置的值嫂粟,沒有返回值。(key=>value)
__set() 方法用于設(shè)置私有屬性值:
function __set($property_name, $value)
{
$this->$property_name = $value;
}
在類里面使用了 __set() 方法后钠右,當(dāng)使用 $p1->name = "張三"; 這樣的方式去設(shè)置對象私有屬性的值時(shí)赋元,就會(huì)自動(dòng)調(diào)用 __set() 方法來設(shè)置私有屬性的值。
__get()
__get() 方法用于獲取私有屬性值:
function __set($property_name, $value)
{
return isset($this->$property_name) ? $this->$property_name : null;
}
例子:
<?php
class Person {
private $name;
private $sex;
private $age;
//__set()方法用來設(shè)置私有屬性
function __set($property_name, $value) {
echo "在直接設(shè)置私有屬性值的時(shí)候飒房,自動(dòng)調(diào)用了這個(gè) __set() 方法為私有屬性賦值<br />";
$this->$property_name = $value;
}
//__get()方法用來獲取私有屬性
function __get($property_name) {
echo "在直接獲取私有屬性值的時(shí)候搁凸,自動(dòng)調(diào)用了這個(gè) __get() 方法<br />";
return isset($this->$property_name) ? $this->$property_name : null;
}
}
$p1=new Person();
//直接為私有屬性賦值的操作, 會(huì)自動(dòng)調(diào)用 __set() 方法進(jìn)行賦值
$p1->name = "張三";
//直接獲取私有屬性的值狠毯, 會(huì)自動(dòng)調(diào)用 __get() 方法护糖,返回成員屬性的值
echo "我的名字叫:".$p1->name;
?>
運(yùn)行該例子,輸出:
在直接設(shè)置私有屬性值的時(shí)候嚼松,自動(dòng)調(diào)用了這個(gè) __set() 方法為私有屬性賦值
在直接獲取私有屬性值的時(shí)候嫡良,自動(dòng)調(diào)用了這個(gè) __get() 方法
我的名字叫:張三
__isset() __unset()
__isset()
__isset() 方法用于檢測私有屬性值是否被設(shè)定锰扶。
如果對象里面成員是公有的,可以直接使用 isset() 函數(shù)寝受。如果是私有的成員屬性坷牛,那就需要在類里面加上一個(gè) __isset() 方法:
private function __isset($property_name)
{
return isset($this->$property_name);
}
這樣當(dāng)在類外部使用 isset() 函數(shù)來測定對象里面的私有成員是否被設(shè)定時(shí),就會(huì)自動(dòng)調(diào)用 __isset() 方法來檢測很澄。
__unset()
__unset() 方法用于刪除私有屬性京闰。
同 isset() 函數(shù)一樣,unset() 函數(shù)只能刪除對象的公有成員屬性甩苛,當(dāng)要?jiǎng)h除對象內(nèi)部的私有成員屬性時(shí)蹂楣,需要使用__unset() 方法:
private function __unset($property_name)
{
unset($this->$property_name);
}
### __call __callStatic
1.__call()方法。當(dāng)調(diào)用一個(gè)沒有在類中聲明的方法時(shí)讯蒲,可以調(diào)用__call()方法代替聲明一個(gè)方法痊土。接受方法名和數(shù)組作為參數(shù)。
代碼實(shí)例:
<?php
class test{
//魔術(shù)方法__call
/*
$method 獲得方法名
$arg 獲得方法的參數(shù)集合
*/
public function __call($method,$arg){
echo '你想調(diào)用我不存在的方法',$method,'方法<br/>';
echo '還傳了一個(gè)參數(shù)<br/>';
echo print_r($arg),'<br/>';
}
$list=new test();
$list->say(1,2,3);
?>
執(zhí)行結(jié)果:
你想調(diào)用我不存在的方法say方法
還傳了一個(gè)參數(shù)
Array ( [0] => 1 [1] => 2 [2] => 3 )
2.__callStatic()方法墨林。從PHP5.3開始出現(xiàn)此方法赁酝,當(dāng)創(chuàng)建一個(gè)靜態(tài)方法以調(diào)用該類中不存在的一個(gè)方法時(shí)使用此函數(shù)。與__call()方法相同萌丈,接受方法名和數(shù)組作為參數(shù)赞哗。
代碼實(shí)例:
<?php
class test{
//魔術(shù)方法__callStatic
/*
$method 獲得方法名
$arg 獲得方法的參數(shù)集合
*/
//魔術(shù)方法__callStatic
public static function __callStatic($method,$arg){
echo '你想調(diào)用我不存在的',$method,'靜態(tài)方法<br/>';
echo '還傳了一個(gè)參數(shù)<br/>';
echo print_r($arg),'<br/>';
}
}
test::cry('痛哭','鬼哭','號哭');
?>
執(zhí)行結(jié)果:
你想調(diào)用我不存在的cry靜態(tài)方法
還傳了一個(gè)參數(shù)
Array ( [0] => 痛哭 [1] => 鬼哭 [2] => 號哭 )
參考:
https://www.cnblogs.com/uduemc/p/4122156.html
https://blog.csdn.net/guiyecheng/article/details/60590646
https://blog.csdn.net/wong_gilbert/article/details/76679108
http://www.5idev.com/p-php_member_overloading.shtml
https://blog.csdn.net/sunyinggang/article/details/78906048