### 简要描述: 74CMS 最新版二次SQL注入可越权操作两处 ### 详细说明: 74CMS最新版:74cms_v3.4.20140820 官方8.20号更新 文件:/user/personal/personal_resume.php ``` //创建简历 -保存基本信息 elseif ($act=='make1_save') { $captcha=get_cache('captcha'); $postcaptcha = trim($_POST['postcaptcha']); if($captcha['verify_resume']=='1' && empty($postcaptcha) && intval($_REQUEST['pid'])===0) { showmsg("请填写验证码",1); } if ($captcha['verify_resume']=='1' && intval($_REQUEST['pid'])===0 && strcasecmp($_SESSION['imageCaptcha_content'],$postcaptcha)!=0) { showmsg("验证码错误",1); } $setsqlarr['uid']=intval($_SESSION['uid']); $setsqlarr['title']=trim($_POST['title'])?trim($_POST['title']):showmsg('请填写简历名称!',1); $setsqlarr['fullname']=trim($_POST['fullname'])?trim($_POST['fullname']):showmsg('请填写姓名!',1); $setsqlarr['sex']=trim($_POST['sex'])?intval($_POST['sex']):showmsg('请选择性别!',1); $setsqlarr['sex_cn']=trim($_POST['sex_cn']); $setsqlarr['birthdate']=intval($_POST['birthdate'])>1945?intval($_POST['birthdate']):showmsg('请正确填写出生年份',1);...
### 简要描述: 74CMS 最新版二次SQL注入可越权操作两处 ### 详细说明: 74CMS最新版:74cms_v3.4.20140820 官方8.20号更新 文件:/user/personal/personal_resume.php ``` //创建简历 -保存基本信息 elseif ($act=='make1_save') { $captcha=get_cache('captcha'); $postcaptcha = trim($_POST['postcaptcha']); if($captcha['verify_resume']=='1' && empty($postcaptcha) && intval($_REQUEST['pid'])===0) { showmsg("请填写验证码",1); } if ($captcha['verify_resume']=='1' && intval($_REQUEST['pid'])===0 && strcasecmp($_SESSION['imageCaptcha_content'],$postcaptcha)!=0) { showmsg("验证码错误",1); } $setsqlarr['uid']=intval($_SESSION['uid']); $setsqlarr['title']=trim($_POST['title'])?trim($_POST['title']):showmsg('请填写简历名称!',1); $setsqlarr['fullname']=trim($_POST['fullname'])?trim($_POST['fullname']):showmsg('请填写姓名!',1); $setsqlarr['sex']=trim($_POST['sex'])?intval($_POST['sex']):showmsg('请选择性别!',1); $setsqlarr['sex_cn']=trim($_POST['sex_cn']); $setsqlarr['birthdate']=intval($_POST['birthdate'])>1945?intval($_POST['birthdate']):showmsg('请正确填写出生年份',1); $setsqlarr['height']=intval($_POST['height']); $setsqlarr['marriage']=intval($_POST['marriage']); $setsqlarr['marriage_cn']=trim($_POST['marriage_cn']); $setsqlarr['experience']=intval($_POST['experience']); $setsqlarr['experience_cn']=trim($_POST['experience_cn']); $setsqlarr['householdaddress']=trim($_POST['householdaddress'])?trim($_POST['householdaddress']):showmsg('请填写户口所在地!',1); $setsqlarr['education']=intval($_POST['education']); $setsqlarr['education_cn']=trim($_POST['education_cn']); $setsqlarr['tag']=trim($_POST['tag']); $setsqlarr['telephone']=trim($_POST['telephone'])?trim($_POST['telephone']):showmsg('请填写联系电话!',1); $setsqlarr['email']=$user['email']; $setsqlarr['email_notify']=$_POST['email_notify']=="1"?1:0; $setsqlarr['address']=trim($_POST['address'])?trim($_POST['address']):showmsg('请填写通讯地址!',1); $setsqlarr['website']=trim($_POST['website']); $setsqlarr['qq']=trim($_POST['qq']); $setsqlarr['refreshtime']=$timestamp; $setsqlarr['subsite_id']=intval($_CFG['subsite_id']); $setsqlarr['display_name']=intval($_CFG['resume_privacy']); if (intval($_REQUEST['pid'])===0) { $setsqlarr['audit']=intval($_CFG['audit_resume']); $total[0]=$db->get_total("SELECT COUNT(*) AS num FROM ".table('resume')." WHERE uid='{$_SESSION['uid']}'"); $total[1]=$db->get_total("SELECT COUNT(*) AS num FROM ".table('resume_tmp')." WHERE uid='{$_SESSION['uid']}'"); $total[2]=$total[0]+$total[1]; if ($total[2]>=intval($_CFG['resume_max'])) { showmsg("您最多可以创建{$_CFG['resume_max']} 份简历,已经超出了最大限制!",1); } else { $setsqlarr['addtime']=$timestamp; $pid=inserttable(table('resume'),$setsqlarr,1); if (empty($pid))showmsg("保存失败!",0); check_resume($_SESSION['uid'],$pid); write_memberslog($_SESSION['uid'],2,1101,$_SESSION['username'],"创建了简历"); header("Location: ?act=make2&pid=".$pid); } } else { $_CFG['audit_edit_resume']!="-1"?$setsqlarr['audit']=intval($_CFG['audit_edit_resume']):""; updatetable(table('resume'),$setsqlarr," id='".intval($_REQUEST['pid'])."' AND uid='{$setsqlarr['uid']}'"); updatetable(table('resume_tmp'),$setsqlarr," id='".intval($_REQUEST['pid'])."' AND uid='{$setsqlarr['uid']}'"); check_resume($_SESSION['uid'],intval($_REQUEST['pid'])); write_memberslog($_SESSION['uid'],2,1105,$_SESSION['username'],"修改了简历({$_POST['title']})"); if ($_POST['go_resume_show']) { header("Location: ?act=resume_show&pid={$_REQUEST['pid']}"); } else { header("Location: ?act=make2&pid={$_REQUEST['pid']}"); } } } ``` 在创建建立第一步时: 将fullname,education_cn等信息通过函数inserttable插入数据库 在进入数据库时,通过转义,但是进入数据库存储时,依然可带入单引号等而已SQL语句 在inserttable进入数据库后 通过check_resume函数检测简历完成程度: ``` //检查简历的完成程度 function check_resume($uid,$pid) { global $db,$timestamp,$_CFG; $uid=intval($uid); $pid=intval($pid); $percent=0; $resume_basic=get_resume_basic($uid,$pid); $resume_intention=$resume_basic['intention_jobs']; $resume_specialty=$resume_basic['specialty']; $resume_education=get_resume_education($uid,$pid); if (!empty($resume_basic))$percent=$percent+15; if (!empty($resume_intention))$percent=$percent+15; if (!empty($resume_specialty))$percent=$percent+15; if (!empty($resume_education))$percent=$percent+15; if ($resume_basic['photo_img'] && $resume_basic['photo_audit']=="1" && $resume_basic['photo_display']=="1") { $setsqlarr['photo']=1; } else { $setsqlarr['photo']=0; } if ($percent<60) { $setsqlarr['complete_percent']=$percent; $setsqlarr['complete']=2; } else { $resume_work=get_resume_work($uid,$pid); $resume_training=get_resume_training($uid,$pid); $resume_photo=$resume_basic['photo_img']; if (!empty($resume_work))$percent=$percent+13; if (!empty($resume_training))$percent=$percent+13; if (!empty($resume_photo))$percent=$percent+14; $setsqlarr['complete']=1; $setsqlarr['complete_percent']=$percent; require_once(QISHI_ROOT_PATH.'include/splitword.class.php'); $sp = new SPWord(); $setsqlarr['key']=$resume_basic['intention_jobs'].$resume_basic['recentjobs'].$resume_basic['specialty']; $setsqlarr['key']="{$resume_basic['fullname']} ".$sp->extracttag($setsqlarr['key']); $setsqlarr['key']=str_replace(","," ",$resume_basic['intention_jobs'])." {$setsqlarr['key']} {$resume_basic['education_cn']}"; $setsqlarr['key']=$sp->pad($setsqlarr['key']); if (!empty($resume_education)) { foreach($resume_education as $li) { $setsqlarr['key']="{$li['school']} {$setsqlarr['key']} {$li['speciality']}"; } } $setsqlarr['refreshtime']=$timestamp; } ``` 通过: ``` $resume_basic=get_resume_basic($uid,$pid); ``` 取出已经进入简历中的信息 看这里: ``` $setsqlarr['key']="{$resume_basic['fullname']} ".$sp->extracttag($setsqlarr['key']); $setsqlarr['key']=str_replace(","," ",$resume_basic['intention_jobs'])." {$setsqlarr['key']} {$resume_basic['education_cn']}"; ``` fullname,education_cn两个参数被取出来后进入了setsqlarr变量 最后进入了updatetable函数: ``` updatetable(table('resume'),$setsqlarr,"uid='{$uid}' AND id='{$pid}'"); ``` 进入updatetable函数: ``` function updatetable($tablename, $setsqlarr, $wheresqlarr, $silent=0) { global $db; $setsql = $comma = ''; foreach ($setsqlarr as $set_key => $set_value) { if(is_array($set_value)) { $setsql .= $comma.'`'.$set_key.'`'.'='.$set_value[0]; } else { $setsql .= $comma.'`'.$set_key.'`'.'=\''.$set_value.'\''; } $comma = ', '; } $where = $comma = ''; if(empty($wheresqlarr)) { $where = '1'; } elseif(is_array($wheresqlarr)) { foreach ($wheresqlarr as $key => $value) { $where .= $comma.'`'.$key.'`'.'=\''.$value.'\''; $comma = ' AND '; } } else { $where = $wheresqlarr; } return $db->query("UPDATE ".($tablename)." SET ".$setsql." WHERE ".$where, $silent?"SILENT":""); } ``` 在updatetable中,没有对数据进行任何的处理专业过滤等 到这里我们第一步进入数据库的恶意SQL,在这里再次进去了数据库,导致SQL注入 因为在update中,所以我们想更新简历中那个信息都可以 ================================================================================ 我们来总结下: 前面发了很多二次注入的漏洞,都是在建立简历这里,基本都在updatetable这个函数里面产生的 1、首先74cms在获取内容后通过转义过滤处理,进入了数据库 2、进入书库时使用inserttable,这里没有任何过滤处理,导致单引号进入数据库 3、在其他地方取出简历数据,通过get_resume_***函数,也没有处理,此时单引号已经产生了 4、在其他地方进行修复内容时,再次使用inserttable和updatetable,由于没有过滤,导致二次SQL注入 ``` foreach ($setsqlarr as $set_key => $set_value) { if(is_array($set_value)) { $setsql .= $comma.'`'.$set_key.'`'.'='.$set_value[0]; } else { $setsql .= $comma.'`'.$set_key.'`'.'=\''.$set_value.'\''; } $comma = ', '; } ``` 当进入updatetable是数组时,这里直接去数组的元素,连引号都没有,直接注入,如: [WooYun: 骑士人才系统注入(20140805)注入一枚](http://www.wooyun.org/bugs/wooyun-2014-071571) 当非数组是,使用单引号保护,这里产生了二次注入,如: [WooYun: 74CMS 最新版二次SQL注入多出可越权操作](http://www.wooyun.org/bugs/wooyun-2014-074521) [WooYun: 74cms (20140709) 二枚二次注入](http://www.wooyun.org/bugs/wooyun-2014-068362) 等,所以这里修复已经很简单了 当然还有inserttable函数里面也需要处理 ### 漏洞证明: 这里存在两个参数fullname,education_cn 由于fullname在数据库中限制长度为15,太短了 这里我们使用education_cn为测试 因为在update中,所以我们想更新简历中那个信息都可以,这里选择最短的QQ 在第一步,创建基本信息时: 在学历处填写:',qq=concat(user(),version())# [<img src="https://images.seebug.org/upload/201409/031456086609a915af44548fc8162256dca345f7.png" alt="111.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/031456086609a915af44548fc8162256dca345f7.png) 然后继续完善简历信息,直到第四步,填写完教育信息时,返回预览自己的建立 qq联系方式即被修改为user和version的值 [<img src="https://images.seebug.org/upload/201409/0314582994cbb6a1882f7550764065a0f8c7718c.png" alt="222.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/0314582994cbb6a1882f7550764065a0f8c7718c.png) 由于这里可以任意截断,所以可以修改任意用户的联系方式等简历信息 这里我们没有添加where条件,那么即可修改全部用户的QQ联系方式