Thinkphp5.X的RCE分為兩大版本:
ThinkPHP 5.0-5.0.24
ThinkPHP 5.1.0-5.1.30
tp5.0.x
代碼執行漏洞:
URL:http://tp5019.com/index.php
POST請求
_method=__construct&method=GET&filter[]=system&s=whoami
_method=__construct&method=GET&filter[]=system&get[]=whoami
自己搭建的環境是 thinkphp5.0.5 php 5.4.x
payload:_method=__construct&filter[]=system&method=GET&get[]=whoami
測試,成功執行命令!

漏洞分析:
這條payload不受debug影響,因為觸發了兩次request->param()
首先是來到 Route::RouteCheck方法中

接着進行 Route::check 方法中

繼續跟中method方法中,這里是關鍵,因為這里存在變量覆蓋漏洞,最后導致了命令的執行

這里只是單純的調用了__construct方法,我們去__construct中看看如何進行調用的,發現是一個 foreach循環將每個post的參數進行變量覆蓋

接着回到了App->run()的方法中
調試有一處觸發了param,這里我們不跟,跟另外一處(在self::module中)

此時走到self::module這里,已經是走過了 initCommon 初始化配置參數,routeCheck 路由檢測,來到了如下的地方,此時的post數據有如下


繼續跟進去,這個module方法主要就是進行 相關的控制器 動作 的初始化

來到最后app->module方法中的最后 進行調用了路由所指向的動作

繼續跟進去return self::invokeMethod($call, $vars),來到了參數綁定的方法中,繼續跟進去

參數綁定的方法實質就是接收請求過來的方法的參數,它最后又繼續進去了 request->param方法中 ,繼續跟

開始獲取各種請求方法請求過來的數據

此時我們是post請求,所以進post中


接接着將$_POST的數據帶進了input方法中,在這里$name為空所以直接退出來

然后將數據合並 再一次進去 input方法中

此時的$name不為false 所以往下走,此時來到了array_walk_recursive,這個方法 就是 進filterValue方法中 $data作為第一個參數 $filter作為第二個參數,用$filter函數進行處理$data每個一個值


但是這里觀察,$filter 為什么能被我們控制呢?往上看一下,當$filter不是字符串的時候,則進行轉化為array賦值

再繼續走,處理完之后 $data其中的值 就為如下

最后通過渲染,展現在頁面上!

同樣的為什么會顯示處兩個一樣的值呢?
因為$_POST中有個get[]=whoami,當變量覆蓋的時候$this->get[]=whoami,導致Request類中又多了一個whoami
總結:
第一個重點: $this->method 可控導致可以調用 __contruct() 覆蓋Request類的filter字段

然后App::run()執行判斷 debug 來決定是否執行 $request->param(),
並且還有$dispatch['type'] 等於controller或者 method 時也會執行$request->param()
第二個重點: 最終 $request->param()會進入到input()方法,在這個方法中將被覆蓋的filter回調call_user_func(),造成rce。

關系圖如下:

最后還需要知道的是: 5.0.13版本之后需要開啟debug才能RCE
URL:http://tp5019.com/index.php?s=captcha
POST請求:
payload:_method=__construct&method=GET&filter[]=system&s=whoami
發現沒有響應,但是開啟debug模式之后就可以進行命令執行
但是開啟 captcha 路由之后,在debug關閉了之后也可以進行了

沒開DEBUG,不能RCE的原因:在進入module方法中對$request->filter中的變量進行了清空


開啟debug之后能夠進行RCE的原因:走的就不是module分支,而是method的分支

