天天看點

PHP反序列化字元串逃逸PHP反序列化字元串逃逸

PHP反序列化字元串逃逸

​ PHP反序列化字元串逃逸這個知識點稍微的有點難懂,以前其實是會的但是前幾天遇到過這樣的題目有點迷糊是以還是要形成文章比較好。

基礎思想

​ 在了解了php反序列化後你就能知道字元串逃逸本質上是閉合,類似于sql注入中利用分号或者引号來閉合語句。反序列化字元串逃逸也是利用php對序列化字元串解析的特性來進行攻擊。

​ 在反序列化時,序列化的值是以分号作為分隔,在結尾以}為結束。我們看一個例子:

<?php 

class people{
public $name = 'Tom';
public $sex = 'boy';
public $age = '12';
}
$a = new people();
print_r(serialize($a));

 ?>
           

運作結果:

反序列化的過程就是當碰到與{最接近的;}時完成比對并停止解析,我們将上列反序列化結果的結尾加上一些無意義的字元串并反序列化。

經過反序列化後:

PHP反序列化字元串逃逸PHP反序列化字元串逃逸

可以看到我們在結尾添加的123123并沒有影響反序列化,程式沒有報錯解析結果也沒有問題。這說明上述的原理是正确的。

但是當我修改字元串長度的數值時,我們來看看反序列化還能否正常解析,例如我将

s:2:"12"

改為

s:3:"12"

php報錯:

實際上,在解析字元串的過程中,當字元串長度與反序列化描述不同時php将報錯,換一種說法:php将按照反序列化字元串定義的長度讀取字元串,當引号内的定義長度的字元串在讀取時未讀取至末尾引号之前的字元、或超越了末尾引号進行讀取時就會報錯。

因為這種報錯的存在,字元串逃逸分為兩種:

  1. 關鍵字增多
  2. 關鍵字減少

關鍵字增多

​ 在題目中,往往對一些關鍵字會進行一些過濾,使用的手段通常替換關鍵字,使得一些關鍵字增多,簡單認識一下,正常序列化檢視結果。

<?php
  show_source(__FILE__);
  $a=array("username"=>"Tom","age"=>"13");
	$b=serialize($a);
	echo $b;
	echo "<br/>";
	$c=preg_replace('/o/',"oo",$b);
	echo $c."<br/>";
	print_r(unserialize($c));
?>
           

輸出:

a:2:{s:8:"username";s:3:"Tom";s:3:"age";s:2:"13";}
a:2:{s:8:"username";s:3:"Toom";s:3:"age";s:2:"13";}
PHP Notice:  unserialize(): Error at offset 28 of 51 bytes in /Users/oriole/WorkSpace/www/html/index.php on line 11
           

​ 在上述代碼中,未反序列化的序列化字元串中的所有o将被替換為oo,這必然破壞了序列化字元串原有的規則,因而必然引起報錯,而我們的目的就是構造字元串使其不要引起報錯,為了更易了解,我們增加一個需求:age字段修改為35。

PHP反序列化字元串逃逸PHP反序列化字元串逃逸

Payload:

原理:在經過preg_replace以後,每個o都會被替換為oo,比如說我們傳入

s:3:"Tom"

會被替換為

s:3:"Toom"

,這樣必然報錯。我們要做的就是讓這個字元串在增長以後真的有它描述的那麼長,同時達到我們将age字段修改為35的目的。

​ 而實作這一目的的方法就是使反序列化結構包含在這一字元串内,當字元串經過替換後,使其長度等于原來包含反序列化結構的字元串長度,進而使之正常被作為字元串解析的同時使包含在字元串内的反序列化結構被php識别為反序列化結構進行解析,同時備援的反序列化結構(也就是完整{;}結構以外的)被遺棄。 可能乍一看難以了解,可以選擇再仔細了解一下基礎思想或繼續向下看構造payload的每一個過程。

​ 我們還以上述代碼為例,我們需要在字元串中構造一個反序列化結構同時還要達到在替換後閉合前側字元串的目的:

";s:3:"age";s:2:"35";}

,而這一反序列化結構的長度是22:

PHP反序列化字元串逃逸PHP反序列化字元串逃逸

​ 也就是說我們需要22個字元來代替其位置,每一個o會被替換為oo也就是增加一個字元,也就是說我們需要22個o來增加22個字元的長度。

​ 是以我們将22個字元長度的

oooooooooooooooooooooo

";s:3:"age";s:2:"35";}

組成username變量的值,我們将其序列化後得到:

在被preg_replace替換後,長度為22的

oooooooooooooooooooooo

變為長度為44的

oooooooooooooooooooooooooooooooooooooooooooo

,整個反序列化的payload也變為:

當PHP反序列化這個payload時

s:8:"username";s:44:"oooooooooooooooooooooooooooooooooooooooooooo";

将被作為整體解析,而

";s:3:"age";s:2:"35";}

在最前面的引号與分号閉合了前面的變量後的

s:3:"age";s:2:"35";}

也會被正常解析。而因為已經滿足了完整的

{;}

結構

";s:3:"age";s:2:"13";}

将被遺棄而不被解析。

因而可以看到如下的反序列化結果:

這就完成了一次成功的‘逃逸’。

關鍵字減少

​ 如果上述内容了解的還可以,那麼關鍵字減少的部分了解起來不會很難。和關鍵字增多的差別在于通常會把需要反序列化的結構放在關鍵字增多中備援抛棄的那一部分,而關鍵字增多這一部分中我們希望通過關鍵字元串增多而解析的反序列化結構在關鍵字減少中需要因字元串長度變化而成為關鍵字元串的一部分進而不被解析。

​ 和關鍵字增多的例子相似:

<?php
  show_source(__FILE__);
  $a=array("username"=>"Zoo","age"=>"15");
	$b=serialize($a);
	echo $b;
	echo "<br/>";
	$c=preg_replace('/oo/',"o",$b);
	echo $c."<br/>";
	print_r(unserialize($c));
?>
           

在這一個例子裡oo會被替換為o,假設我們的目的仍然是将age的值改為35。

payload:

PHP反序列化字元串逃逸PHP反序列化字元串逃逸

可以看到age被改為了35。

實作這一目的的原理和關鍵字增多類似:

;s:3:"age";s:2:"15";}

長度是22,我們構造44個o來作為username的值,然後将我們需要更改的反序列化結構

;s:3:"age";s:2:"35";}

放在最後面,經過preg_replace替換後o的數量變為22,而username所定義的長度為44,這時php會将

;s:3:"age";s:2:"15";}

這22個字元一并作為username的一部分進行讀取,而後我們後面部分隻要閉合的好,就會被作為正常的反序列化結構進行解析,進而達到逃逸的目的