Thinkphp <= 5.0.10 緩存getshell復現
0x01 poc
首先看緩存函數的使用場景
然后會生成以下緩存文件
可以看到,字符串abc直接存儲到以php結尾的緩存文件中。嘗試使用\n換行getshell
語法有錯,注釋一下后面的垃圾字符,成功getshell。
0x02 跟蹤源碼
首先跟進18行的Cache::set()函數
跟進self::init()
self::$handler此時為null,進入true塊,由於上面調用的是self::init(),沒有參數,故67行條件不滿足。
69行,查看配置cache.type的值,發現默認為File,
故此條件也不滿足,進入72行。此處以上圖的cache數組作為參數,調用了self::connect()。跟進connect方法
這里通過一系列判斷,根據cache.type的值,找到cache驅動為File,對應44行的think\cache\driver\File類。然后在51行進行實例化,並return。
回溯到上個函數,也直接return
繼續回溯
這里調用了return過來的實例的set方法。跟進think\cache\driver\File的set方法
可以看到,在142行調用了getCacheKey方法。
跟進getCacheKey方法后發現,這里由於options['cache_subdir']默認值是true,所以這里直接用參數md5加密后的結果的前兩位作為目錄名,剩余30位作為緩存文件名。然后通過拼接.php后return。
繼續回來,獲取上面構造的filename之后,在146行將$value進行序列化,然后在149行使用gzcompress對其進行二進制壓縮。接着在151行在data前后拼接php標簽,最后在152行寫文件。
這里存在漏洞的點就是151行把用戶可控的數據放到了php標簽內。
0x03 審計思路
拿到源碼后,找到Cache::set(name, value, expire),其中緩存文件名是跟name相關聯的,因此可以看作是一個已知條件。漏洞的關鍵點就是value是否可控。
0x04 補丁
看一下修復之后的結果(v5.0.15)
這里在data之前加了一個exit()強制退出,基本杜絕了data執行php代碼的可能。