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