失之交臂通用型_mtceo SQL注入
下载地址
http://61.155.169.167:81/code/201109/MTCEO_a5.zip
项目背景
MTCEO文库系统可转目前已知所有文档格式,设置监视文件夹和转换后文件夹,即可开始自动转换,无需人工干预,是文库网站必备软件!
1、具有文库基本功能
2、用户可以互为粉丝
3、可以对文档进行评分、收藏和推荐
4、完善了网站积分机制
5、完美整合ucenter,可与discuz论坛互通头像、积分和用户
6、评论、评分更加完善,表情等可后台自定义
7、预览无压力,借用官方开放平台,不占用个人空间!
8、文库模板自由替换机制,详情可见教程区模板相关教程
9、后台在线升级、数据库备份与还原、缓存更新
10、腾讯、新浪微博和淘宝一键登录支持!
11、标签机制,自由更改模板内容
工具使用
1、运行环境 php5.4+mysql5.7
2、IDE phpstorm
3、审计工具 Rips + Burp Suite + sqlmap
影响版本
v2.6
搭建环境效果
前台:
后台:
漏扫扫描结果
代码分析
通过上面的关键词name_exists(
搜索定位,搜索到的含有这个函数的文件还是挺多的,下面笔者只分析一处,思路是相通的嘛。
所以我们分析哪一个呢,那就article_cate这个文件吧,我们来到这个包含这个关键词的方法体。
/**
* 入库数据整理
*/
protected function _before_insert($data = '') {
//检测分类是否存在
if($this->_mod->name_exists($data['name'], $data['pid'])){
$this->ajaxReturn(0, L('article_cate_already_exists'));
}
//生成spid
$data['spid'] = $this->_mod->get_spid($data['pid']);
return $data;
}
那么这里的参数data是调用这个方法时,传的参数,那么是哪里调用的呢,来,接着全局扫搜,我们找到该控制器的父级里面,有这么一个方法,调用了它。
/**
* 添加
*/
public function add() {
$mod = D($this->_name);
if (IS_POST) {
if (false === $data = $mod->create()) {
IS_AJAX && $this->ajaxReturn(0, $mod->getError());
$this->error($mod->getError());
}
if (method_exists($this, '_before_insert')) {
$data = $this->_before_insert($data);
}
if($data['spid']=='maxcate'){
//$data['spid']='';
IS_AJAX && $this->ajaxReturn(0, '分类限制为三级分类,级别太深不利于优化哦');
$this->error('分类限制为三级分类,级别太深不利于优化哦');
}
if($data['spid']=='0|'){
$data['spid']=0;
}
if( $mod->add($data) ){
if( method_exists($this, '_after_insert')){
$id = $mod->getLastInsID();
$this->_after_insert($id);
}
IS_AJAX && $this->ajaxReturn(1, L('operation_success'), '', 'add');
$this->success(L('operation_success'));
} else {
IS_AJAX && $this->ajaxReturn(0, L('operation_failure'));
$this->error(L('operation_failure'));
}
} else {
$this->assign('open_validator', true);
if (IS_AJAX) {
$response = $this->fetch();
$this->ajaxReturn(1, '', $response);
} else {
$this->display();
}
}
}
在这个方法体的十二行,调用了这个方法,并且通过前面的判断,给data参数做了赋值操作,怎么赋值的呢,$mod->create();这个是赋值操作,然而,此处的变量mod是对应article的模型,不巧的是,这个文件不存在这个方法,那它在哪呢,不错,在它的父级里,这是PHP的继承性。于是乎,在它父级找到这个方法,如下:
/**
* 创建数据对象 但不保存到数据库
* @access public
* @param mixed $data 创建数据
* @param string $type 状态
* @return mixed
*/
public function create($data='',$type='') {
// 如果没有传值默认取POST数据
if(empty($data)) {
$data = $_POST;
}elseif(is_object($data)){
$data = get_object_vars($data);
}
// 验证数据
if(empty($data) || !is_array($data)) {
$this->error = L('_DATA_TYPE_INVALID_');
return false;
}
// 检查字段映射
$data = $this->parseFieldsMap($data,0);
// 状态
$type = $type?$type:(!empty($data[$this->getPk()])?self::MODEL_UPDATE:self::MODEL_INSERT);
// 检测提交字段的合法性
if(isset($this->options['field'])) { // $this->field('field1,field2...')->create()
$fields = $this->options['field'];
unset($this->options['field']);
}elseif($type == self::MODEL_INSERT && isset($this->insertFields)) {
$fields = $this->insertFields;
}elseif($type == self::MODEL_UPDATE && isset($this->updateFields)) {
$fields = $this->updateFields;
}
if(isset($fields)) {
if(is_string($fields)) {
$fields = explode(',',$fields);
}
// 判断令牌验证字段
if(C('TOKEN_ON')) $fields[] = C('TOKEN_NAME');
foreach ($data as $key=>$val){
if(!in_array($key,$fields)) {
unset($data[$key]);
}
}
}
// 数据自动验证
if(!$this->autoValidation($data,$type)) return false;
// 表单令牌验证
if(C('TOKEN_ON') && !$this->autoCheckToken($data)) {
$this->error = L('_TOKEN_ERROR_');
return false;
}
// 验证完成生成数据对象
if($this->autoCheckFields) { // 开启字段检测 则过滤非法字段数据
$fields = $this->getDbFields();
foreach ($data as $key=>$val){
if(!in_array($key,$fields)) {
unset($data[$key]);
}elseif(MAGIC_QUOTES_GPC && is_string($val)){
$data[$key] = stripslashes($val);
}
}
}
// 创建完成对数据进行自动处理
$this->autoOperation($data,$type);
// 赋值当前数据对象
$this->data = $data;
// 返回创建的数据以供其他调用
return $data;
}
这里面我们需要注意是哪里啊,没错,就是在第十行的位置,它没有过滤,直接把$_POST这么一个全局变量给了data。如果说这是悲剧的开始,那么悲剧的最后一次防御的机会,它也没有守好。看代码。
/**
* 检测分类是否存在
*
* @param string $name
* @param int $pid
* @param int $id
* @return bool
*/
public function name_exists($name, $pid, $id=0) {
$where = "name='" . $name . "' AND pid='" . $pid . "' AND id<>'" . $id . "'";
$result = $this->where($where)->count('id');
if ($result) {
return true;
} else {
return false;
}
}
在判断存不存在的时候,没有遵循底层框架的书写规范,拿到这个data里面的name值,直接进行了参数的拼接啊,在tp3的版本里,至少要写个数据格式的吧,然而,最后把自己送到了鬼门关。
至此代码分析完。
工具验证阶段
说到验证SQL注入的最好的工具,肯定就是sqlmap啦。啥也不说了,抓个包埋点土吧,看看能长出来个啥。
包内容如下:
将包导入sqlmap里。跑一下,看的能否跑出结果
好,PHP版本跑的很准确,MySQL数据也是可以的。说明确实存在问题。
修复阶段
1、遵循开发手册,按照指定好的原则来,别手动拼接SQL,很危险、很危险、很危险【重要的事说三遍】,如果按照规则来了,还有漏洞,这个锅可以甩给tp啦。
2、做特殊字符的过滤验证【推荐第一种修复】
E
N
D
关
于
我
们
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
我知道你在看哟