1. 程式人生 > 其它 >php反序列化之Session反序列化

php反序列化之Session反序列化

Session反序列化

Session是一次瀏覽器和伺服器的互動的會話,在ctf中,Session往往有妙用,可以實現反序列化和檔案包含,接下來我們先來看看Session具體是啥,然後如何利用Session實現反序列化:

1、Session到底是啥

前面我們說到,Session是瀏覽器和伺服器之間互動的會話,會話是啥呢?就是我問候你好嗎?你回答說很好。就是一次會話,那麼對話完成後,這次會話相當於就結束了,但為什麼會出現Session會話呢?因為我們用瀏覽器訪問網站用的是http協議,http協議是一種無狀態的協議,就是說它不會儲存任何東西,每一次的請求都是沒有關聯的,無狀態的協議好處就是快速;但它也有不方便的地方,比如說我們在login.php

登入了,我們肯定希望在index.php中也是登入的狀態,否則我們登入還有什麼意義呢?但前面說到了http協議是無狀態的協議,那訪問兩個頁面就是發起兩個http請求,他們倆之間是無關聯的,所以無法單純的在index.php中讀取到它在login.php中已經登陸了的;為了解決這個問題,cookie就誕生了,cookie是把少量資料存在客戶端,它在一個域名下是全域性的,相當於php可以在這個域名下的任何頁面讀取cookie資訊,那隻要我們訪問的兩個頁面在同一個域名下,那就可以通過cookie獲取到登入資訊了;但這裡就存在安全問題了,因為cookie是存在於客戶端的,那使用者就是可見的,並且可以隨意修改的;那如何又要安全,又可以全域性讀取資訊呢?這時候Session就出現了,其實它的本質和cookie
是一樣的,只不過它是存在於伺服器端的

2、session的產生和儲存

上面講了Session產生的原因,那它具體長啥樣子呢?這裡我們用php中的Session機制,因為後面講的反序列化也是基於php的嘛

首先,當我們需要使用Session時,我們要首先開啟Session,開啟Session的語句是session_start();,這個函式沒有任何返回值,既不會成功也不會報錯,它的作用是開啟Session,並且隨機生成一個32位的session_id,session的全部機制也是基於這個session_id,伺服器就是通過這個唯一的session_id來區分出這是哪個使用者訪問的:

<?php
highlight_file(__FILE__); session_start(); echo "session_id(): ".session_id()."<br>"; echo "COOKIE: ".$_COOKIE["PHPSESSID"];

這裡可以看出session_id()這個系統方法是輸出了本次生成的session_id,並且存入了COOKIE中,引數名為PHPSESSID,這兩個值是相同的,而且只要瀏覽器一直不關,無論重新整理多少次它的值都是不變的,但當你關掉瀏覽器之後它就消失了,重新開啟之後會生成一個新的session_idsession_id就是用來標識一個使用者的,就像是一個人的身份證一樣,接下來就來看看session它是怎麼儲存的:

它是儲存在伺服器中的臨時目錄下的,儲存的路徑需要看php.ini的配置,我的是儲存在D:\phpStudy\PHPTutorial\tmp\tmp這個路徑下的,我們可以開啟來看看:

可以看到它的儲存形式是檔名為sess+_+session_id,那我們能不能通過修改COOKIEPHPSESSID的值來修改session_id呢?

然後重新整理頁面,可以發現成功了,成功修改了session_id的值,並且去儲存的路徑下去看發現也成功寫進去了:

但由上圖可知,它的檔案內容是為空的,裡面什麼都沒有,那我們能不能嘗試往裡面寫入東西呢?依然在a.php中操作,給它賦個值:

現成功寫進去了,它的內容就是將鍵值對序列化之後的結果

我們把大致過程總結一下:

就是HTTP請求一個頁面後,如果用到開啟session,會去讀COOKIE中的PHPSESSID是否有,如果沒有,則會新生成一個session_id,先存入COOKIE中的PHPSESSID中,再生成一個sess_字首檔案。當有寫入$_SESSION的時候,就會往sess_檔案裡序列化寫入資料。當讀取session變數的時候,先會讀取COOKIE中的PHPSESSID,獲得session_id,然後再去找這個sess_session_id檔案,來獲取對應的資料。由於預設的PHPSESSID是臨時的會話,在瀏覽器關閉後就會消失,所以,當我們開啟瀏覽器重新訪問的時候,就會新生成session_idsess_session_id這個檔案。

3、有關的配置

好了,上面鋪墊了這麼多,應該明白Session是什麼以及Session的機制了,下面就開始正式進入正題,來看看Session反序列化

首先,我們先去php.ini中去看幾項與session有關的配置:

1.session.save_path:這個是session的儲存路徑,也就是上文中sess_session_id那個檔案儲存的路徑

2.session.auto_start:這個開關是指定是否在請求開始時就自動啟動一個會話,預設為Off;如果它為On的話,相當於就先執行了一個session_start(),會生成一個session_id,一般來說這個開關是不會開啟的

3.session.save_handler:這個是設定使用者自定義session儲存的選項,預設是files,也就是以檔案的形式來儲存的,當然你也可以選擇其它的形式,比如說資料庫啥的

4.session.serialize_handler:這個是最為重要的一個,用來定義session序列化儲存所用的處理器的名稱,不同的處理器序列化以及讀取出來會產生不同的結果;預設的處理器為php,常見的還有php_binaryphp_serialize,接下來來一個一個的看它們:

首先是php,因為它預設就是php,所以說用的應該是最多的,它處理之後的格式是鍵名+豎線|+經過serialize()序列化處理後的值

然後我們來看php_binary,首先我們把處理器換成php_binary需要用語句ini_set('session.serialize_handler','php_binary');這個處理器的格式是鍵名的長度對應的 ASCII 字元 + 鍵名 + 經過 serialize() 函式序列化處理後的值;注意這個鍵名的長度所所對應的ASCII字元,就比如說鍵名長度為4,那它對應的就是ASCII碼為4的字元,是個不可見字元EOT,具體可見下表,從1到31都是不可見字元

所以說它最後的結果如下,框框代表的就是不可見字元:

最後我們來看php_serialize,這個處理器需要php版本>5.5.4才能使用,首先我們還是得先用ini_set進行設定,語句如下:ini_set('session.serialize_handler','php_serialize');這個的格式是直接進行序列化,把session中的鍵和值都會被進行序列化操作,然後把它當成一個數組返回回來:

4、session反序列化原理

講了這麼多,相信很多人還是一頭霧水,那為什麼會產生Session反序列化漏洞呢?這個問題其實也困擾了我很久,以前我也是隻知道操作但不清楚原理,知道前面加個|就可以成功但至於為什麼就一臉懵逼,因為我們都知道Session反序列化是不需要unserialize()函式就可以實現的,那這具體是怎麼實現的呢?今天就來把它徹底搞懂:

首先我們再來看看session_start()函式,前面我們看到的是沒有開啟Session的情況下它是開啟Session並且返回一個session_id,但假如我們前面就已經打開了Session呢?這裡我們再來看看官方文件:

這裡重點看我框了的內容,尤其我箭頭指向的地方,它會自動反序列化資料,那就很漂亮啊!這裡就解決了沒有unserialize()的問題,那我們可不可以考慮先把序列化後的資料寫入sess_session_id檔案中,然後在有反序列化漏洞頁面重新整理頁面,由於這個頁面依然有session_start(),那它就去讀取那個檔案的內容,然後自動進行反序列化操作,這樣就會觸發反序列化漏洞,完美!!

這個思路理論上是可以成功的,但這裡還有一個核心問題沒有解決,就是說我們怎麼讓它反序列化的是我們傳入的序列化的內容,因為我們傳入的是鍵值對,那麼session序列化儲存所用的處理器肯定也是將這個鍵值對寫了進去,那我們怎麼讓它正好反序列化到我們傳入的內容呢?這裡就需要介紹出兩種處理器的差別了,php處理器寫入時的格式為鍵名+豎線|+經過serialize()序列化處理後的值那它讀取時,肯定就會以豎線|作為一個分隔符,前面的為鍵名,後面的為鍵值,然後將鍵值進行反序列化操作;而php_serialize處理器是直接進行序列化,然後返回序列化後的陣列,那我們能不能在我們傳入的序列化內容前加一個分隔符|,從而正好序列化我們傳入的內容呢

這肯定是可以的,而這正是我們Session反序列化的原理,如果看到這有點發暈的話,沒關係,咱接著往下看,接下來咱來分析一個例子

5、案例分析

首先我們來寫一個存在反序列化漏洞的頁面:

<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php');
session_start();
class Test{
    public $code;
    function __wakeup(){
    eval($this->code);
    }
}
?>

這應該是很簡單的一個反序列化,反序列化後會先直接進入__wakeup(),然後就eval執行任意程式碼了,我們先寫個exp:

<?php
class Test{
    public $code='phpinfo();';
}
$a = new Test();
echo serialize($a);
?>

然後我們再寫一個頁面,因為這裡既沒有傳參的點也沒有反序列化的點,相對於有漏洞利用不了,那我們就寫一個利用它的頁面sess.php

<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
if(isset($_GET['test'])){
    $_SESSION['test']=$_GET['test'];
}
?>

有了這個頁面我們就可以把想要的內容寫入到Session中了,然後就可以在有漏洞的頁面中執行反序列化了,接下來開始操作,首先執行exp.php

然後我們通過sess.php將執行結果寫入Session中,記得在前面加上|

然後我們去看它成功寫入Session沒有,並且看看寫入的內容是什麼:

可以看到它已經成功寫入進去了,並且內容也是我們想要的內容,按照php處理器的處理方法,會以|為分隔符,左邊為鍵,右邊為值,然後將值進行反序列化操作,那我們就去有漏洞的頁面去重新整理,看看它有沒有反序列化之後觸發反序列化漏洞: