突破某場景下tp 5.0.x 路由代碼執行漏洞


0x00 前言
源於一個師傅在博客短消息問的問題,感謝某師傅提供的案例
 
0x01 限制條件
配置修改:
// 應用調試模式
'app_debug'              => false, (默認true)
 // URL參數方式 0 按名稱成對解析 1 按順序解析
'url_param_type'         => 1, // (默認0)
// 是否開啟路由
'url_route_on'           => false, //(默認true)
thinkphp 版本 :5.0.5
其他修改:網站根目錄不在public,在public上一級目錄,並綁定了模塊為index
目標操作系統:linux
 
0x02 分析
由於關閉了調試,我們沒法獲得報錯信息
url_param_type 
url_route_on
這兩個默認配置的修改,也導致了很多問題
我們都知道tp5有兩個非常出名的任意代碼執行,一個是路由,一個是method變量覆蓋
其中
路由任意代碼執行的影響版本為:
  • ThinkPHP 5.0.5-5.0.22
  • ThinkPHP 5.1.0-5.1.30
method 變量覆蓋漏洞影響版本為:  
  • Thinkphp 5.0.0 rc4-5.0.23   
但在這個場景下
 
1,由於配置url_route_on 和url_param_type的問題,method變量覆蓋漏洞執行不了
method變量覆蓋漏洞需要在程序運行過程中,間接或直接調用到Request的method方法
跟到tp的運行過程,在App::run方法中由於$dispatch為空,會進入路由檢測代碼
在路由檢測方法中,url_route_on配置值控制了是否進入到if代碼塊中,當url_route_on=true,程序才能進入if代碼塊

而在if代碼塊中Route::check函數間接調用了Request->method()方法,從而進行覆蓋變量,但是這里還不是執行

要執行還得繼續往下走,再觸發一次Request::instance()->input()方法

看到最后執行控制器方法時,App.php中的bindParams(),會將外部獲得的參數進行綁定,當url_param_type 為0時

 

由Request::instance()-->param()間接調用Request::instance()->input()方法,用覆蓋的filter來對輸入參數進行過濾,從而觸發代碼執行

2,路由漏洞也因為url_param_type配置的修改,導致只能通過pathinfo的方式獲取變量
最終在給方法綁定變量的時候出錯,也就是說現有的payload沒辦法直接執行

 

0x03 突破

先來講講一些坑點:

因為只能通過pathinfo的方式傳值,我們沒辦法指定鍵,所以只能按順序傳參數,能夠利用的方法也十分有限
 
還有pathinfo切割參數時根據/ 符號來切割,也導致了我們傳入的內容不能包括它
 
還有一個坑點,由於5.0 Loader.php自動加載類方法中

win環境中嚴格區分大小寫,導致了很多類其實是沒辦法加載的,這個我在之前的文章也分析過:https://www.cnblogs.com/r00tuser/p/10103329.html

但是目標是linux,經過搭建環境分析,發現linux下也是不行的,雖然系統是linux,IS_WIN的值必為false,不會直接返回false,但是會包含不到文件
 
主要是因為配置
// 是否自動轉換URL中的控制器和操作名
    'url_convert'            => true,(默認值)
 
將控制器轉換成了小寫,而實際的文件名是大寫的,比如
payload:
http://127.0.0.1/index.php/?s=think\Process/start

可以看到實際找到的class為think/process,對應找到的文件為think/process.php,而文件實際的文件名為think/Process.php

(本地環境是win,僅證明linux的情況)

最后勢必會因為包含不到文件,而觸發錯誤,簡單用個測試文件test.php
在centos下php7環境測試

而測試過程中也發現了個有趣的點,在Mac下,php文件包含是不會區分大小寫的

同樣是測試文件test.php

 所以無論在win還是linux下能夠利用的類就只有一開始程序已經加載的類:

think\Loader
think\Route
think\Config
think\Validate
think\Console
think\Error
think\App
think\Request
think\Hook
think\Env
think\Lang

 

有的一些思路:
1,寫馬到日志,寫馬到session,文件包含之
2,從tp中自有的類中找到可控的方法
 
思路1:
第一步就是要寫馬到日志文件,因為php代碼需要<? 包裹,包含?符號,而在url中傳該符號的時候會被截斷,在日志里面只能看到<
payload:(當然urlencode兩次也不行)
http://127.0.0.1/index.php/?s=think\Error/appError/abc/%3C?php%20phpinfo();?%3E/

用<script language="php">(php<7)的時候,也因為</script>結束符有/ 符號而被切割無法正常寫入日志

payload:
http://127.0.0.1/index.php/?s=think\Error/appError/abc/%3Cscript%20language=%22php%22%3Ephpinfo();%3C/script%3E

而嘗試去掉閉合標簽,也會因為后面的日志導致報錯

寫入:
http://127.0.0.1/index.php/?s=think\Error/appError/abc/%3Cscript%20language=%22php%22%3Ephpinfo();

包含:

http://127.0.0.1/index.php?s=think\Lang/load/runtime\log\202009\08.log

並且在linux實際環境下還有一個問題,日志的路徑或者說文件的路徑是/xx/xxx/的樣式,是沒法通過pathinfo的方式正常傳入進去的
 
所以思路1失敗。
 

思路2:

前面說到由於沒法自動加載類的原因,導致能用的類少之又少,所以我們必須在下面的列表中找到能利用的

think\Loader
think\Route
think\Config
think\Validate
think\Console
think\Error
think\App
think\Request
think\Hook
think\Env
think\Lang

在一遍遍分析之后,終於在think\Loader類中找到了一個完美的方法action,看到代碼:

代碼對$url進行pathinfo取值,獲取pathinfo數組basename索引的值作為$action,而$module從數組dirname索引中獲取,如果數組dirname索引的值取不到則取當前控制器名(也就是think\Loader)

之后傳入到controller方法進行實例class,之后判斷了$vars變量是否為標量,如果為標量並且包含"=",則用parse_str解析參數為變量存儲在$vars中
 
再之后用App::invokeMethod方法來進行反射調用,調用$class的實例$action方法,並傳$vars的值到方法中
 
pathinfo傳參的方式,我們是能夠控制$url,$vars的值的,也就是說類,方法,參數我們都可以控制,十分完美的任意類任意方法調用
 
一般情況下tp 5.0的代碼執行payload 如下:
http://127.0.0.1/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()

這里,我們就可以通過think\Loader的action方法間接調用起think\app 的invokefunction

http://127.0.0.1/index.php?s=think\Loader/action/think\APP\invokefunction/function=call_user_func_array%26vars[0]=assert%26vars[1][]=phpinfo()

最終實現代碼執行

 

但是這個payload只能在win下執行,原因是

pathinfo()是根據系統的類型來選擇路徑分割符分割的
 
在win下可以是
\ /

但在linux下只能是

/

也就是dirname索引的值會是點號,從而執行不到我們想要的think\App類

那么該怎么辦呢?
 
其實我們可以套多一次action方法,也就是調用action方法去調用action方法之后再調用think\App\invokefunction,即think\Loader::action->think\Loder::action->think\App::invokefunction
 
由於第二次的pathinfo()取值的$url來源於第一次action方法parse_str解析的變量,我們可以對正斜杠進行雙urlencode再傳進去(parse_str函數會對值進行一次urldecode),從而避免了正斜杠與pathinfo分隔符沖突而不能正確傳值的問題
 
最后基於正常的代碼執行payload,構造出payload如下(win/linux通用):
 
http://127.0.0.1/index.php?s=think\Loader/action/action/url=think\APP%252finvokefunction%26vars[function]=call_user_func_array%26vars[vars][0]=assert%26vars[vars][1][]=phpinfo()

win:

 

 linux:

 

 

0x04 總結
算是把之前沒有踩的坑重新踩了,在實際測試中可以多留意一下,版本是在漏洞版本內,但是代碼執行卻不行,可能就是這種場景


免責聲明!

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



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