您現在的位置是:首頁 > 藝術

細說php反序列化字元逃逸

由 合天網安實驗室 發表于 藝術2023-01-29
簡介}發現序列化的值他會傳遞給user中的方法update_profile,接著update_profile又將這個值傳遞給了父類方法filter,顯而易見,就是一種過濾,防止sql注入,但是可以發現,他是以替換的方式給返回值,在過濾的字串中

如何利用strlen漏洞

想了解更多精彩內容,快來關注

湖南蟻景

相關實驗推薦——PHP反序列化漏洞實驗 (透過本次實驗,大家將會明白什麼是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和預防此類漏洞。)

前言

php反序列化的字元逃逸算是比較難理解的一個知識點,在最近的好幾場比賽中都出現了相關的題,於是下定決心徹底理解透徹這個知識點,於是便有了這篇文章。

基礎知識理解

字元逃逸在理解之後就能夠明白,這是一種閉合的思想,它類似SQL中的萬能密碼,理解這種原理之後會變得特別容易。

在SQL注入中,我們常用‘、“來對注入點進行一些閉合或者一些試探,從而進行各種姿勢的注入,反序列化時,序列化的值是以;作為欄位的分隔,在結尾是以}結束,我們稍微瞭解一下,

<?php

class people{

public $name = ’Tom‘;

public $sex = ’boy‘;

public $age = ’12‘;

}

$a = new people();

print_r(serialize($a));

O:6:”people“:3:

{s:4:”name“;s:3:”Tom“;s:3:”sex“;s:3:”boy“;s:3:”age“;s:2:”12“;}

反序列化的過程就是碰到;}與最前面的{配對後,便停止反序列化。我們可以將上面的序列化的值稍作改變:

O:6:”people“:3:

{s:4:”name“;s:3:”Tom“;s:3:”sex“;s:3:”boy“;s:3:”age“;s:2:”12“;}123123

細說php反序列化字元逃逸

可以看到,並沒有報錯,而且也順利將這個物件反序列化出來了,恰好說明了我們以上所說的閉合的問題,與此同時,修改一些序列化出來的值可以反序列化出我們所知道的物件中裡沒有的值,在學習繞過__wakeup就能過知道了,這裡可以自己去做一些嘗試,去理解。

接下來就是要說到報錯的時候了,當你的字串長度與所描述的長度不一樣時,便會報錯,比如上圖中s:3:”Tom“變成s:4:”Tom“或s:2:”Tom“便會報錯,為的就是解決這種報錯,所以在字元逃逸中分為兩類:

1。字元變多

2。字元減少

關鍵字元增多

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

細說php反序列化字元逃逸

這裡,我們對序列化後的字串進行了替換,使得後來的反序列化報錯,那我們就需要在Tom這裡面的字串做手腳,在username之後只有一個age,所以在雙引號裡面可以構造我需要的username之後引數的值,這裡修改age的值,我們這裡將Tom替換為Tom”;s:3:“age”;s:2:“35”;}然後進行反序列化,這裡指的是在對username傳參的時候進行修改,也就是我們寫鏈子的時候進行的操作

細說php反序列化字元逃逸

可以看到構造出來的序列化字串長度為25,而在上面的反序列化過程中,他會將一個o變成兩個,oo,那麼得到的應該就是s:25:“Toom”我們要做的就是讓這個雙引號裡面的字串在過濾替換之後真的有描述的這麼長,讓他不要報錯,再配合反序列化的特點,(反序列化的過程就是碰到;}與最前面的{配對後,便停止反序列化)閉合後忽略後面的age:13的字串成功使得age被修改為35。

而age的修改需要前面的字串username的值長度與描述的一樣,這需要我們精確的計算,這裡是將一個o變成兩個,以下就只寫o不寫Tom,效果一致,我們需要知道我們除了雙引號以內的,所構造的字串長度為多少,即“;s:3:”age“;s:2:”35“;}的長度22,那就需要22個o,

總的來說就是22個o加上後面的字串長度22,總長度就為44,在被過濾替換後,光o就有44個,符合描述的字串長度。下面就說明(為什麼叫做逃逸)

細說php反序列化字元逃逸

這裡特意寫了”將一大串o進行與前面的“閉合了,如果直接反序列化,在序列化出來的值中就包含了”;s:3:“age”;s:2:“35”;}。

反序列的過程中,所描述的字串長度(這裡為44),而後面雙引號包裹的字串長度(這裡為22)不夠所描述的長度,那麼他將會向後吞噬,他會將後雙引號吞噬,直至足夠所描述的長度,在一切吞噬結束之後,序列化出來的字串如果不滿足反序列化的字串的格式,就會報錯。我們這裡是他吞噬結束後,還滿足這個格式,所以不報錯。

在這個例子中,我們利用他對序列化後的值,進行增加字串長度的過濾,讓他填充雙引號內的字串達到所描述的44這麼長,使得後面的s:3:“age”;s:2:“35”;不被吞噬,讓這部分程式碼逃逸出吞噬,又讓他提前遇到}忽略後面的一些不需要的字串,結束反序列化。

細說php反序列化字元逃逸

可以看到,我們構造的payload是成功修改了age,這裡是陣列,在對物件操作時也是一樣的。

剛剛說到吞噬,在增加字串的題目中,我們是利用題中的增加操作,阻止他進行向後吞噬我們構造的程式碼,而在字元減少的過程中,我們也是利用這個操作。

關鍵字元減少

有了前面”吞噬“的一種解釋,那麼字串減少就很好說了 ,同樣的也是因為替換的問題,使得引數可以讓我們構造payload

細說php反序列化字元逃逸

這裡的錯誤是因為s:5:“zddo”長度不夠,他向後吞噬了一個雙引號,導致反序列化格式錯誤,從而報錯,我們要做的就是讓他往後去吞噬一些我們構造的一些程式碼。以下講具體實施。

同樣的,我們這裡以修改age為例,不同的是與增加字串傳值的地方有些許不同,我們構造的值是有一部分讓他吞噬的

先正常傳遞值序列化出我們需要修改的值,我們需要的是將age:13改為35

細說php反序列化字元逃逸

取出“;s:3:”age“;s:2:”35“;}這就是我們需要構造的,接著繼續將這部分內容重新傳值,序列化出來,得到下面的結果

細說php反序列化字元逃逸

選中部分就是我們構造出來,他需要吞噬的程式碼,s:22:”“這個雙引號裡面我們還有操作的空間,用來補齊字串長度,接著就是計算我們自己所需要吃掉的字串長度為18,根據過濾,他是將兩個o變成一個,也就是每吃掉一個字元,就需要有一個oo,那我們需要吃掉的是18個長度,那麼我們就需要18個oo,在吞噬結束之後我們的格式又恢復正確,使得真正的字元s:3:”age“;s:2:”35“;逃逸出來,成功加入反序列化

細說php反序列化字元逃逸

這就是我們最終的payload,可以看到下圖成功修改了

細說php反序列化字元逃逸

例題

有了以上基礎,就可以做題了,簡單的開始入手

安恆四月(字元減少)

<?php

show_source(”index。php“);

function write($data) {

return str_replace(chr(0) 。 ’*‘ 。 chr(0), ’\0\0\0‘, $data);

}

function read($data) {

return str_replace(’\0\0\0‘, chr(0) 。 ’*‘ 。 chr(0), $data);

}

class A{

public $username;

public $password;

function __construct($a, $b){

$this->username = $a;

$this->password = $b;

}

}

class B{

public $b = ’gqy‘;

function __destruct(){

$c = ’a‘。$this->b;

echo $c;

}

}

class C{

public $c;

function __toString(){

echo file_get_contents($this->c);

return ’nice‘;

}

}

$a = new A($_GET[’a‘],$_GET[’b‘]);

//省略了儲存序列化資料的過程,下面是取出來並反序列化的操作

$b = unserialize(read(write(serialize($a))));

看到上面的程式碼,很明顯,我們需要利用file_get_contents();讀取檔案,將flag讀取出來,但是他是個__toString()方法,我們就要讓他觸發這個方法,當反序列化出物件後,被當作字串使用時,就可以觸發,那我們就需要寫一個鏈,與此同時我們也要知道字串被刪減了幾個字元

function write($data) {

return str_replace(chr(0) 。 ’*‘ 。 chr(0), ’\0\0\0‘, $data);

}

function read($data) {

return str_replace(’\0\0\0‘, chr(0) 。 ’*‘ 。 chr(0), $data);

}

看這一部分即可瞭解到,如果發現不可見字元*不可見字元,字串就會增多,接著又將\0\0\0的6個字元變成3個字元不可見字元*不可見字元,我們自己是不會去寫入不可見字元的在這道題中,相反可以故意寫入\0\0\0使得字串減少,透過計算逃逸字元,讀取flag檔案

題目中序列化的是物件$a,裡面有兩個引數,username和password,我們要傳入的也是這兩個引數的值,所以我們構造的payload,應該是往password傳我們構造好的字元,而在username傳入的為計算好的\0\0\0個數,

按照流程來,先寫一個鏈子,某些引數先隨便寫

細說php反序列化字元逃逸

”;s:8:“password”;O:1:“B”:1:{s:1:“b”;O:1:“C”:1:{s:1:“c”;s:5:“/flag”;}}}

選中部分就是我們需要重新傳參的地方,我們傳進password,再次序列化看看效果

細說php反序列化字元逃逸

我所選擇的地方就是需要被吞噬的部分,然後才能使得後邊的程式碼全部逃逸,長度為23,計算後發現需要8個\0\0\0,但8個這樣的符號會吞噬24個字元,因此我們可以在s:70:“”雙引號裡面可以隨意補一個字元讓它吞噬。

因此我們最終在password裡面傳參的應該是:

i“;s:8:”password“;O:1:”B“:1:{s:1:”b“;O:1:”C“:1:{s:1:”c“;s:5:”/flag“;}}}

而在username中傳的就是8個\0\0\0:

\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0

最終payload:

?a=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&b=i”;s:8:“password”;O:1:“B”:1:{s:1:“b”;O:1:“C”:1:{s:1:“c”;s:5:“/flag”;}}}

細說php反序列化字元逃逸

0CTF piapiapia(字元增加)

掃描目錄發現www。zip,下載後開始,審計原始碼

我們根據他的網頁一步步看原始碼,首先他要我們登陸,我們先註冊一個賬號進行登陸

細說php反序列化字元逃逸

看到上圖的介面,這時我們看到update。php,都是一些對引數的白名單按要求寫即可,圖片也隨便傳一個符合大小的即可,但是注意nickname是我們要操作的地方,稍後講解,然後有個序列化陣列的過程

$user->update_profile($username, serialize($profile));

在上傳成功後,他會到profile。php,我們看到profile。php,有這麼一段東西

$profile = unserialize($profile);

$phone = $profile[’phone‘];

$email = $profile[’email‘];

$nickname = $profile[’nickname‘];

$photo = base64_encode(file_get_contents($profile[’photo‘]));

這裡告訴我們,他反序列化的是profile這個陣列序列化後的值,讀取的是鍵名為photo裡面的檔名,而flag在config。php也就是說我們需要構造的就是陣列中$profile[’photo‘]=’config。php‘

那麼怎樣才能讓他讀這個config。php呢

我們看到class。php,看到父類mysql中:

public function filter($string) {

$escape = array(’\‘’, ‘\\\\’);

$escape = ‘/’ 。 implode(‘|’, $escape) 。 ‘/’;

$string = preg_replace($escape, ‘_’, $string);

$safe = array(‘select’, ‘insert’, ‘update’, ‘delete’, ‘where’);

$safe = ‘/’ 。 implode(‘|’, $safe) 。 ‘/i’;

return preg_replace($safe, ‘hacker’, $string);

}

發現序列化的值他會傳遞給user中的方法update_profile,接著update_profile又將這個值傳遞給了父類方法filter,顯而易見,就是一種過濾,防止sql注入,但是可以發現,他是以替換的方式給返回值,在過濾的字串中,只有where變成hacker,由5個字元變成6個,所以是字元增加的逃逸方式,接著開始構造

細說php反序列化字元逃逸

選擇部分是需要傳入的,在phone和email都是白名單,傳入的格式受限制,只有nickname是黑名單,所以我們要繞過這個黑名單,bp抓包

細說php反序列化字元逃逸

他使用的是strlen()所以這裡有個方式,這個函式如果引數是字串,那麼數出來的就是字串個數,如果是陣列,那麼數出來的就是陣列元素的個數

將nickname改成nickname[]然後傳參

傳的引數我們需要構造,需要計算,上面分析了我們需要構造的字串“;}s:5:”photo“;s:10:”config。php“;},但是他是字元增多,我們就需要知道需要逃逸的字元有多少個,計算了一下為34個,(

這裡因為改為了陣列,而陣列的序列化格式結束後是一個大括號,所以我們在一開始的”;後面增加了一個花括號

)所以需要34個where幫助我們逃逸這部分程式碼,上傳成功,根據這個修改放包即可

細說php反序列化字元逃逸

返回網頁點選

細說php反序列化字元逃逸

之後可以看到一張顯示不出來的圖片,因為使用base64編碼了,右擊檢視圖片資訊

細說php反序列化字元逃逸

解碼即可看到flag

細說php反序列化字元逃逸

推薦文章