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來區分出這是哪個使用者訪問的:
<?phphighlight_file(__FILE__); session_start(); echo "session_id(): ".session_id()."<br>"; echo "COOKIE: ".$_COOKIE["PHPSESSID"];
這裡可以看出session_id()
這個系統方法是輸出了本次生成的session_id
,並且存入了COOKIE
中,引數名為PHPSESSID
,這兩個值是相同的,而且只要瀏覽器一直不關,無論重新整理多少次它的值都是不變的,但當你關掉瀏覽器之後它就消失了,重新開啟之後會生成一個新的session_id
,session_id
就是用來標識一個使用者的,就像是一個人的身份證一樣,接下來就來看看session
它是怎麼儲存的:
它是儲存在伺服器中的臨時目錄下的,儲存的路徑需要看php.ini
的配置,我的是儲存在D:\phpStudy\PHPTutorial\tmp\tmp
這個路徑下的,我們可以開啟來看看:
可以看到它的儲存形式是檔名為sess
+_
+session_id
,那我們能不能通過修改COOKIE
中PHPSESSID
的值來修改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_id
和sess_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_binary
和php_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
處理器的處理方法,會以|
為分隔符,左邊為鍵,右邊為值,然後將值進行反序列化操作,那我們就去有漏洞的頁面去重新整理,看看它有沒有反序列化之後觸發反序列化漏洞: