
1. 從一道CTF題看SESSION的“另一面”最近在復盤一些經典的Web安全CTF題目時遇到了一道關于文件包含漏洞的題它的切入點不是常規的include($_GET[‘file’])包含日志或者上傳文件而是巧妙地利用了PHP的SESSION機制。這道題讓我重新審視了SESSION——這個我們日常開發中用于維持用戶狀態、再熟悉不過的組件在攻擊者眼中可能是一個隱藏的“文件上傳點”和“代碼執行跳板”。很多開發者和初學安全的同學對SESSION的理解可能停留在“服務器存儲的鍵值對”卻忽略了它在服務器上本質也是以文件形式存在的。這就為一種特殊的文件包含漏洞利用方式打開了大門包含SESSION文件本身。傳統的文件包含漏洞利用往往需要攻擊者想方設法在服務器上留下一個包含惡意代碼的文件比如通過文件上傳功能、寫入日志、利用php://input等偽協議。但如果服務器開啟了SESSION并且session.save_path目錄已知或可猜測同時存在文件包含漏洞那么攻擊者可能完全不需要“上傳”這個動作。他只需要讓服務器“主動”為他生成一個包含惡意代碼的SESSION文件即可。這聽起來有點繞但原理其實很直接SESSION文件的內容部分來源于用戶可控的$_SESSION超全局變量。如果我們能通過某種方式比如反序列化漏洞、參數污染等向$_SESSION中注入惡意代碼再通過文件包含漏洞去包含這個SESSION文件就能實現遠程代碼執行。這道CTF題正是考察了這個知識點。它模擬了一個看似只有文件包含功能卻沒有直接文件上傳入口的場景。解題的關鍵就在于意識到SESSION文件可以被包含并找到控制SESSION文件內容的方法。接下來我將詳細拆解這類漏洞的原理、利用條件、具體利用步驟并分享在實戰和CTF中挖掘此類漏洞的心得與技巧。2. SESSION機制與文件包含漏洞的交叉點要理解這個漏洞我們必須先拋開“鍵值對”的抽象概念深入到PHP中SESSION的底層實現。當session_start()被調用時PHP會根據session_id通常通過Cookie中的PHPSESSID傳遞來唯一標識一個用戶會話。會話數據需要持久化存儲默認的存儲方式就是文件。2.1 SESSION文件的存儲與命名PHP的session.save_handler配置決定了存儲方式默認為files。此時會話數據會被序列化后保存到session.save_path指定的目錄中。文件的命名規則通常是sess_加上session_id。例如如果session_id是abc123那么對應的SESSION文件就是/tmp/sess_abc123假設session.save_path為/tmp。這個文件的內容是經過序列化的字符串。默認的序列化處理器session.serialize_handler通常是php或php_binary。例如當我們設置$_SESSION[‘user’] ‘admin’;后文件內容可能就是user|s:5:“admin”;這樣的格式。這里的關鍵在于$_SESSION數組中的鍵和值都會以明文形式出現在這個文件里。2.2 漏洞形成的核心鏈條文件包含漏洞如include($_GET[‘file’])與SESSION文件的交叉形成了如下的攻擊鏈條存在文件包含點應用程序存在未經過濾或過濾不嚴的文件包含漏洞可以包含服務器上的任意文件如include(‘/tmp/’ . $_GET[‘file’])。SESSION存儲路徑已知或可猜攻擊者需要知道session.save_path的值。這在很多環境下是默認的如/tmp或可以通過信息泄露如phpinfo()獲取。能控制SESSION文件內容攻擊者需要有能力向$_SESSION中寫入可控的數據。這是整個鏈條中最關鍵、也最具技巧性的一環。控制的方式可能多樣直接賦值極少數情況下可能存在代碼直接操作$_SESSION[‘key’] $_GET[‘input’];且未過濾。反序列化入口更常見的是存在一個session反序列化漏洞。例如PHP在讀取SESSION數據時會對其進行反序列化。如果攻擊者能控制session上傳進度session.upload_progress或通過其他方式如php_binary格式處理差異注入序列化數據就可能將惡意對象注入$_SESSION。但在文件包含的語境下我們更關注的是直接寫入文件的內容而非反序列化觸發所以通常是將惡意代碼作為字符串值寫入。利用SESSION初始化在某些框架或自定義SessionHandler中可能存在從用戶輸入初始化SESSION的邏輯。能獲取或預測SESSION_ID攻擊者需要知道自己的SESSION文件名即sess_后面的部分。這通常就是當前會話的PHPSESSID瀏覽器會自動攜帶。在攻擊中攻擊者就是利用自己的會話。當這四個條件滿足時攻擊者可以先訪問一個能向$_SESSION寫入數據的頁面或利用相關漏洞將PHP代碼如作為值寫入。然后再訪問文件包含點嘗試包含自己的SESSION文件如/tmp/sess_abc123。如果包含成功寫入的PHP代碼就會被服務器解析執行。注意這里有一個重要的細節。直接包含原始的SESSION文件可能會因為文件開頭包含序列化格式字符如user|s:18:“”而導致PHP解析錯誤。因此攻擊時往往需要結合php://filter偽協議進行編碼轉換先讀取文件內容然后解碼執行。這是此類利用的一個標準技巧。3. 實戰利用從理論到Getshell我們通過一個高度簡化的模擬場景來還原整個利用過程。假設目標環境如下PHP應用session.save_path “/tmp”。存在一個頁面write.php不安全地將用戶輸入存入SESSION。存在一個頁面include.php存在文件包含漏洞。3.1 漏洞代碼模擬write.php (存在可控SESSION寫入點)?php session_start(); // 危險操作未經過濾直接將GET參數存入SESSION if(isset($_GET[‘data’])){ $_SESSION[‘payload’] $_GET[‘data’]; echo “Data written to session.”; } ?include.php (存在文件包含漏洞)?php $file $_GET[‘file’]; include($file); // 危險未做任何過濾 ?3.2 分步攻擊利用第一步注入惡意代碼到SESSION文件攻擊者訪問write.php并通過參數將PHP代碼寫入SESSION。為了防止引號等字符破壞序列化結構通常需要對Payload進行編碼或者確保它作為整個字符串值的一部分。一個簡單的方法是使用Base64編碼后再寫入包含時再解碼。但更直接的方式是利用PHP的短標簽和避免破壞序列化格式。例如訪問http://target.com/write.php?data?php system(‘id’);?此時攻擊者瀏覽器中的PHPSESSID假設為abc123。那么在服務器的/tmp目錄下就會生成一個文件sess_abc123其內容大致為payload|s:23:“?php system(‘id’);?“;注意這里的“和;是序列化格式的一部分不是Payload的內容。Payload字符串?php system(‘id’);?被完整地存儲了。第二步利用文件包含漏洞執行SESSION中的代碼直接包含/tmp/sess_abc123會失敗因為PHP解釋器會試圖解析整個文件內容開頭的payload|s:23:“會導致語法錯誤。這時就需要php://filter偽協議出場。攻擊者構造如下請求http://target.com/include.php?filephp://filter/convert.base64-decode/resource/tmp/sess_abc123這個Payload的意圖是先讀取/tmp/sess_abc123文件的內容然后對其進行Base64解碼最后將解碼后的內容傳遞給include。但是我們寫入的內容并不是Base64編碼的所以解碼會亂執行不會成功。這是新手常犯的錯誤。正確的思路是我們需要讓SESSION文件中的某一部分在經過php://filter鏈式處理后變成可執行的PHP代碼。一個經典的方法是使用convert.iconv.*過濾器進行字符集轉換或者利用string.rot13過濾器。string.rot13是一個簡單的編碼PHP在執行include時會先對經過過濾器處理后的流進行解碼。更可靠的利用鏈如下寫入一個經過php://filter編碼的Payload到SESSION。包含時使用對應的解碼過濾器使得最終被包含的內容是純正的PHP代碼。例如我們可以寫入http://target.com/write.php?data?cuc flfgrz(‘vq’);?這是?php system(‘id’);?經過rot13編碼后的結果。然后攻擊者訪問http://target.com/include.php?filephp://filter/readstring.rot13/resource/tmp/sess_abc123php://filter會讀取sess_abc123的內容并對整個內容進行rot13解碼。解碼后文件內容變成payload|s:23:“?php system(‘id’);?“;雖然序列化格式部分payload|s:23:“和結尾的“;也被解碼了但解碼后可能變成無意義的字符但重要的是?php system(‘id’);?這段代碼被正確還原了。當PHP引擎解釋這個文件時它會尋找?php ... ?標簽并執行其中的代碼。序列化格式的亂碼部分位于PHP標簽之外會被當作普通文本忽略或導致一個警告但不會阻止執行。這樣system(‘id’)命令就被成功執行了。在實際的CTF題目中條件可能更苛刻。例如write.php可能不存在需要尋找其他控制SESSION的途徑比如session.upload_progress這是一個PHP特性在上傳文件時可以在$_SESSION中創建一個包含上傳進度的數組。攻擊者可以通過構造特殊的上傳表單和POST數據將惡意代碼寫入這個數組。這是此類題目非常常見的考點。反序列化漏洞觸發__wakeup或__destruct如果存在反序列化點并且可以觸發魔術方法向$_SESSION寫數據。3.3 利用php://filter的鏈式操作在更復雜的情況下可能需要組合多個過濾器。例如如果目標服務器對包含的文件后綴有檢查比如要求包含.php文件我們可以利用php://filter的convert.base64-decode和write特性先解碼Payload再將其“寫入”一個虛擬的.php文件中被包含。一個高級的Payload構造示例假設需要繞過.php后綴限制include.php?filephp://filter/writeconvert.base64-decode/resourcephp://temp然后通過POST數據向這個請求體發送Base64編碼后的PHP代碼。write過濾器會將解碼后的內容寫入resource指定的流這里是php://temp內存流然后include會包含這個流。由于php://temp沒有后綴限制且內容已經是解碼后的純PHP代碼從而繞過檢查。但這需要能控制POST體數據在文件包含場景中通常與php://input結合屬于另一個技巧。4. CTF解題中的常見陷阱與繞過技巧在CTF比賽中這類題目不會直接給出所有條件需要選手主動挖掘和組合。以下是一些常見的陷阱和對應的技巧陷阱1SESSION路徑未知技巧嘗試常見的默認路徑如/tmp,/var/lib/php/sessions,/var/tmp。利用phpinfo()信息泄露是首選。如果沒有可以嘗試目錄遍歷漏洞配合包含或者利用報錯信息回顯路徑。陷阱2無法直接控制$_SESSION變量技巧重點檢查session.upload_progress。這是PHP的一個功能當文件上傳時可以在$_SESSION[‘upload_progress_xxx’]中跟蹤進度。通過構造一個文件上傳表單并在POST數據中插入惡意字段名有可能將數據寫入SESSION。例如一個名為PHP_SESSION_UPLOAD_PROGRESS的字段其值可能會被處理。這是此類題目的高頻考點。技巧尋找反序列化漏洞。全局搜索unserialize,session_start()之前的session相關操作。有時題目會提供一個反序列化入口反序列化后的對象會在其魔術方法如__wakeup,__destruct中執行$_SESSION[‘key’] $this-data;這樣的操作。陷阱3文件包含點有后綴限制技巧使用php://filter時resource部分可以指向SESSION文件但最終包含的是經過過濾器處理的流而不是原文件。因此后綴限制通常對php://filter無效。如果限制是黑名單過濾了php:等字符串可以嘗試大小寫、雙寫、添加多余字符php://filter等方式繞過。陷阱4寫入SESSION的代碼對特殊字符進行了過濾或轉義技巧如果過濾了和可以嘗試使用PHP短標簽?需要開啟short_open_tag或者利用php://filter的編碼特性寫入編碼后的Payload。例如如果代碼對輸入進行了htmlspecialchars轉義那么會變成lt;無法形成PHP標簽。這時就需要尋找其他不依賴?php標簽的執行方式比如利用.htaccess的php_value指令如果包含的是.htaccess文件且Apache支持但這在SESSION包含中不常見。更可能的是題目本意就是讓你使用php://filter的編碼來繞過轉義。陷阱5SESSION文件內容包含序列化前綴導致語法錯誤技巧這是此類利用的標準解法即前面提到的使用string.rot13或convert.iconv.*過濾器。string.rot13是最常用的因為它是一種對稱編碼且PHP支持在包含時解碼。構造?cuc ... ?的Payload寫入包含時用string.rot13解碼即可。有時也會使用convert.iconv.UTF-8.UTF-7等轉換原理類似。一個典型的CTF解題流程可能是信息收集找到文件包含點嘗試讀取/proc/self/environ、/etc/passwd或源碼確認session.save_path。尋找SESSION寫入點檢查是否有明顯的$_SESSION賦值或嘗試利用session.upload_progress。構造Payload將?php system(‘cat /flag’);?進行rot13編碼得到?cuc flfgrz(‘pngt /synt’);?。寫入SESSION通過找到的寫入點如上傳表單的PHP_SESSION_UPLOAD_PROGRESS字段將編碼后的Payload寫入。包含執行使用php://filter/readstring.rot13/resource/tmp/sess_yoursessionid去包含獲取命令執行結果。5. 防御之道如何避免SESSION被包含從開發和安全加固的角度我們需要多層面布防切斷這個攻擊鏈條1. 杜絕文件包含漏洞這是根本。永遠不要將用戶輸入直接傳遞給文件包含函數include,require,include_once,require_once。如果必須動態包含請使用白名單機制只允許包含預設的幾個安全文件。2. 安全配置SESSION修改默認存儲路徑不要使用/tmp這類全局可讀的目錄作為session.save_path。將其設置為一個僅Web服務器用戶有讀寫權限的專用目錄。使用安全的存儲方式將session.save_handler改為redis或memcached將SESSION數據存儲在內存數據庫中徹底避免文件落地。這是最推薦的方案。嚴格設置目錄權限確保session.save_path目錄的權限為700所有者讀寫執行所有者是Web服務用戶如www-data, nginx其他用戶無任何權限。3. 對SESSION數據進行嚴格過濾和校驗不要將任何用戶可控的、未經驗證和過濾的數據直接存入$_SESSION。對待$_SESSION要和對待數據庫輸入一樣進行嚴格的類型檢查、長度限制和內容過濾。特別是對于從反序列化、session.upload_progress等潛在入口進入$_SESSION的數據要有清晰的校驗邏輯。4. 關閉危險特性如果應用不需要文件上傳進度跟蹤功能可以在php.ini中關閉session.upload_progress.enabled從根本上杜絕通過此途徑污染SESSION。確保allow_url_include設置為Off默認值防止包含遠程URL。5. 使用Web應用防火墻WAF部署WAF規則檢測異常的文件包含請求路徑特別是包含/tmp/sess_、php://filter等特征的請求。6. 代碼審計與安全意識在代碼審計中將文件包含漏洞和SESSION操作點尤其是用戶輸入直接操作$_SESSION的點作為重點檢查對象。讓開發團隊理解SESSION不是“安全區”它同樣需要防范注入。這道關于包含SESSION的CTF題目從一個精巧的角度揭示了安全風險的關聯性。它告訴我們一個普通的特性SESSION文件存儲在遇到另一個漏洞文件包含時會產生意想不到的化學反應。作為防御者我們的思維不能是孤立的需要建立起“攻擊面關聯”的意識通過安全的默認配置、最小權限原則和輸入輸出的嚴格校驗來構建縱深防御體系讓攻擊者即便找到一個突破口也難以串聯形成完整的攻擊鏈。