实战 | 记一次较为详细的代码审计过程_集群智慧网络安全云
全国客户服务热线:4006-054-001 疑难解答:159-9855-7370(7X24受理投诉、建议、合作、售前咨询),173-0411-9111(售前),155-4267-2990(售前),座机/传真:0411-83767788(售后),微信咨询:543646
企业服务导航

实战 | 记一次较为详细的代码审计过程

发布日期:2024-05-19 浏览次数: 专利申请、商标注册、软件著作权、资质办理快速响应热线:4006-054-001 微信:15998557370


实战 | 记一次较为详细的代码审计过程

前言 本次审计的话是Seay+昆仑镜进行漏洞扫描 Seay的话它可以很方便的查看各个文件,而昆仑镜可以很快且扫出更多的漏洞点,将这两者进行结合起来,就可以发挥更好的效果。 昆仑镜官方地址 https://github.com/LoRexxar/Kunlun-M 环境 KKCMS环境搭建 KKCMS链接如下 https://github.com/liumengxiang/kkcms 安装的话正常步骤就好,即 1、解压至phpstudy目录下 2、访问install 3、新建kkcms数据库,然后在安装的时候用这个数据库 4、安装完成,开始审计 目录结构 常见的目录结构,简单了解一下其作用 admin 后台管理目录 css CSS样式表目录 data 系统处理数据相关目录 install 网页安装目录 images 系统图片存放目录 template 模板 system 管理目录 代码审计 对扫描出的开始进行审计 验证码重用 admin/cms_login.php 源码如下 验证码的校验代码 if ($_SESSION['verifycode'] != $_POST['verifycode']) { alert_href('验证码错误','cms_login.php'); } 不难发现这里是将$_SESSION['verifycode']与POST上传的verifycode相比较,如果不相等就会刷新跳转,重新回到登录处,此时验证码也会被更新。 我们进入前端界面看一下 发现验证码js对应处存在文件,跟进查看一下 该文件的含义是用0-9中的任意四个数字作为验证码,也就是说js引用该文件来产生验证码。这里学习过其他师傅的思路后,了解到 Burpsuite默认不解析js 因此我们这里就可以借助bp抓包,摒弃js,对用户名和密码进行爆破 抓包后发送到instruct模块,在密码处添加变量 而后添加一些常用的弱口令密码 开始爆破 成功爆破出密码 XSS wap/shang.php 使用昆仑镜进行扫描,得到结果 结合Seay,查看该文件代码 可以看到直接输出了$_GET['fee'],因此我们这里直接传入一个xss语句尝试触发xss payload fee= wap/seacher.php 昆仑镜扫描 利用seay查看源码 //这只是一部分,具体的师傅们可自行查看此文件 搜索<?php echo $q?>-<?php echo $xtcms_seoname;?> 可以发现这里这个变量$q直接被输出了,这个$q是POST上传的wd参数,因此我们这里POST上传wd参数,给它赋值一个xss语句的话,应该是可以进行XSS的,我们试着去构造一下 wp= 成功触发XSS wap/movie.php //部分源码 $b=(strpos($_GET['m'],'rank=')); $ye=substr($_GET['m'],$b+5); ?> 存在可控参数$_GET['m']和$_GET['page'],开头引用了inc.php,试着找一下输出语句。 发现输出语句 发现被函数getPageHtml包裹了,跟进查看 function getPageHtml($_var_60, $_var_61, $_var_62) { $_var_63 = 5; $_var_60 = $_var_60 < 1 ? 1 : $_var_60; $_var_60 = $_var_60 > $_var_61 ? $_var_61 : $_var_60; $_var_61 = $_var_61 < $_var_60 ? $_var_60 : $_var_61; $_var_64 = $_var_60 - floor($_var_63 / 2); $_var_64 = $_var_64 < 1 ? 1 : $_var_64; $_var_65 = $_var_60 + floor($_var_63 / 2); $_var_65 = $_var_65 > $_var_61 ? $_var_61 : $_var_65; $_var_66 = $_var_65 - $_var_64 + 1; if ($_var_66 < $_var_63 && $_var_64 > 1) { $_var_64 = $_var_64 - ($_var_63 - $_var_66); $_var_64 = $_var_64 < 1 ? 1 : $_var_64; $_var_66 = $_var_65 - $_var_64 + 1; } if ($_var_66 < $_var_63 && $_var_65 < $_var_61) { $_var_65 = $_var_65 + ($_var_63 - $_var_66); $_var_65 = $_var_65 > $_var_61 ? $_var_61 : $_var_65; } if ($_var_60 > 1) { $_var_67 .= '

  • '; } for ($_var_68 = $_var_64; $_var_68 <= $_var_65; $_var_68++) { if ($_var_68 == $_var_60) { $_var_67 .= '
  • '; } else { $_var_67 .= '
  • '; } } if ($_var_60 < $_var_65) { $_var_67 .= '
  • '; } return $_var_67; } 跟进查看后也没有发现输出点,结果网页端js代码再看看 传参 http://127.0.0.1:8080/kkcms-kkcms/wap/movie.php?m=111 查看源代码,Ctrl+f搜?m=111查找对应js代码 找到js代码
  • 尝试直接闭合a标签执行xss语句,构造payload如下 ?m="> 成功触发xss 同类XSS文件如下 wap/tv.php 其对应输出代码如下 wap/zongyi.php 其对应输出代码如下 wap/dongman.php 其对应输出代码如下 system/pcon.php(失败) 发现这里有个echo 变量的,利用Seay跟进一下这个文件 0){ $tiaourl=$cc.$_GET['play']; } else{ $tiaourl=$dd.$_GET['play']; } } } ?> 此时发现可控变量play,如果让他变为xss恶意语句,就可能会实现xss,但我们这个时候看一下最上面,发现有一个if语句 if ($xtcms_pc==1){ 它这个条件为true后执行的语句,不仔细看的话甚至都找不到结尾处在哪,经过仔细查看后发现在最后 这里的话也就是说,我们只有满足了$xtcms_pc==1这个条件,才能够成功的往下执行,进而利用play参数构造xss语句,因此我们此时就需要跟进这个$xtcms_pc变量,全局搜索一下 发现变量赋值点,跟进查看 简单看一下这里的代码,发现这个结果是从SQL查询处的结果取出的,而SQL语句不存在变量,因此这里的话我们是不可控的,所以这里的话应该是不存在XSS的,G admin/cms_ad.php 登录后台后发现有个广告管理界面 发现这里可以设置名称和广告内容,尝试在名称处插入xss语句 发现此时成功触发了xss语句,那么这里的话应该是直接将广告名称进行了输出,我们查看后端代码,验证一下 请输入广告名称 请输入广告的内容<a href="网址" target="_blank"><img src="图片地址" style="width:100%"></a> 可以发现这里只是限制了长度为60,其他没有什么限制,输出广告内容的代码是 $result = mysql_query('select * from xtcms_ad order by id desc'); while($row = mysql_fetch_array($result)){ ?> 这里的话是取出结果,然后将结果赋值给$row,最后输出了$row['id']和$row['name'],正如同所说的一样,不存在过滤点,因而导致了XSS的出现 而你此时大概看一下代码的话,它的内容也是如此,内容是在加载页面的时候出现的,这个时候我们可以用img来构造一个xss恶意语句 此时随便访问首页的一个视频 成功触发XSS youlian.php "/> 关注 value="" 发现这里参数id没有什么防护,虽然开头涉及了inc.php,但那个是防护SQL注入的,不影响xss。我们这里如果能够闭合语句的话,似乎就可以触发XSS了。payload id="> //此时的语句就是 value=" ?>" 结果如下 wx_api.php class wechatCallbackapiTest { public function valid() { $echoStr = $_GET["echostr"]; if($this->checkSignature()){ echo $echoStr; exit; } } 可以发现这里的参$_GET['echostr']不存在防护,在传入后经过一个if语句直接进行了输出,我们跟进一下这个if语句了的checkSignature函数查看一下 private function checkSignature() { // you must define TOKEN by yourself if (!defined("TOKEN")) { throw new Exception('TOKEN is not defined!'); } $signature = $_GET["signature"]; $timestamp = $_GET["timestamp"]; $nonce = $_GET["nonce"]; $token = TOKEN; $tmpArr = array($token, $timestamp, $nonce); // use SORT_STRING rule sort($tmpArr, SORT_STRING); $tmpStr = implode( $tmpArr ); $tmpStr = sha1( $tmpStr ); if( $tmpStr == $signature ){ return true; }else{ return false; } } 发现这里大概是个检验token的,传个空对应的md5值应该就可以,尝试xss payload ?echostr=&signature=da39a3ee5e6b4b0d3255bfef95601890afd80709 SQL bplay.php 0){ if(!isset($_SESSION['user_name'])){ alert_href('请注册会员登录后观看',''.$xtcms_domain.'ucenter'); }; $result = mysql_query('select * from xtcms_user where u_name="'.$_SESSION['user_name'].'"');//查询会员积分 if($row = mysql_fetch_array($result)){ $u_group=$row['u_group'];//到期时间 } if($u_group<=1){//如果会员组 alert_href('对不起,您不能观看会员视频,请升级会员!',''.$xtcms_domain.'ucenter/mingxi.php'); } } include('system/shoufei.php'); if($d_jifen>0){//积分大于0,普通会员收费 if(!isset($_SESSION['user_name'])){ alert_href('请注册会员登录后观看',''.$xtcms_domain.'ucenter'); }; $result = mysql_query('select * from xtcms_user where u_name="'.$_SESSION['user_name'].'"');//查询会员积分 if($row = mysql_fetch_array($result)){ $u_points=$row['u_points'];//会员积分 $u_plays=$row['u_plays'];//会员观看记录 $u_end=$row['u_end'];//到期时间 $u_group=$row['u_group'];//到期时间 } if($u_group<=1){//如果会员组 if($d_jifen>$u_points){ alert_href('对不起,您的积分不够,无法观看收费数据,请推荐本站给您的好友、赚取更多积分',''.$xtcms_domain.'ucenter/yaoqing.php'); } else{ if (strpos(",".$u_plays,$d_id) > 0){ } //有观看记录不扣点 else{ $uplays = ",".$u_plays.$d_id; $uplays = str_replace(",,",",",$uplays); $_data['u_points'] =$u_points-$d_jifen; $_data['u_plays'] =$uplays; $sql = 'update xtcms_user set '.arrtoupdate($_data).' where u_name="'.$_SESSION['user_name'].'"'; if (mysql_query($sql)) { alert_href('您成功支付'.$d_jifen.'积分,请重新打开视频观看!',''.$xtcms_domain.'bplay.php?play='.$d_id.''); } } } } } if($d_user>0){ if(!isset($_SESSION['user_name'])){ alert_href('请注册会员登录后观看',''.$xtcms_domain.'ucenter'); }; $result = mysql_query('select * from xtcms_user where u_name="'.$_SESSION['user_name'].'"');//查询会员积分 if($row = mysql_fetch_array($result)){ $u_points=$row['u_points'];//会员积分 $u_plays=$row['u_plays'];//会员观看记录 $u_end=$row['u_end'];//到期时间 $u_group=$row['u_group'];//到期时间 } if($u_group<$d_user){ alert_href('您的会员组不支持观看此视频!',''.$xtcms_domain.'ucenter/mingxi.php'); } } function get_play($t0){ $result = mysql_query('select * from xtcms_player where id ='.$t0.''); if (!!$row = mysql_fetch_array($result)){ return $row['n_url']; }else{ return $t0; }; } $result = mysql_query('select * from xtcms_vod where d_id ='.$d_id.''); if (!!$row = mysql_fetch_array($result)){ $d_scontent=explode("rn",$row['d_scontent']); //print_r($d_scontent); for($i=0;$i,$d_scontent[$i]); } $playdizhi=get_play($row['d_player']).$d_scontent[0][1]; }else{ return ''; }; include('template/'.$xtcms_bdyun.'/bplay.php'); ?> 这里的话可以看出主要的SQL语句是这句话 $result = mysql_query('select * from xtcms_vod where d_id = '.$_GET['play'].' '); 然后这个play参数是GET传参的,同时看这里的代码可以看出它是没有单引号或者双引号包裹的,此时我们跟进一下include的文件,也就是system/inc.php,查看一下这个文件 跟进这个library.php $userid){ alert_href('您现在所属会员组的权限制大于等于目标会员组权限值,不需要升级!','mingxi.php'); } 看这一处代码 $card= mysql_query('select * from xtcms_userka where id="'.$_POST['cardid'].'"'); 不难发现这里的Select语句中的参数被双引号包裹了,而开头包含了inc.php文件,之前就已经查看过,这个文件包含了四个文件,其中一个文件中有addslashes_deep函数,对传入的参数中的特殊字符(如',",)进行了转义,因此我们这里的话无法通过闭合双引号达到SQL注入的目的,同文件的其他SQL注入处也是如此,这里不再展示 wap/login.php 扫出login.php中存在多个可控变量,我们使用Seay来看一下具体代码 //展示的仅为一部分 $u_end){ $_data['u_flag'] =="0"; $_data['u_start'] ==""; $_data['u_end'] ==""; $_data['u_group'] =1; }else{ $_data['u_flag'] ==$row["u_flag"]; $_data['u_start'] ==$row["u_start"]; $_data['u_end'] ==$row["u_end"]; $_data['u_group'] =$row["u_group"]; } mysql_query('update xtcms_user set '.arrtoupdate($_data).' where u_id ="'.$row['u_id'].'"'); $_SESSION['user_name']=$row['u_name']; $_SESSION['user_group']=$row['u_group']; if($_POST['brand1']){ setcookie('user_name',$row['u_name'],time()+3600 * 24 * 365); setcookie('user_password',$row['u_password'],time()+3600 * 24 * 365); } header('location:user.php'); }else{ alert_href('用户名或密码错误或者尚未激活','login.php?op=login'); } } if(isset($_POST['reg'])){ $username = stripslashes(trim($_POST['name'])); // 检测用户名是否存在 $query = mysql_query("select u_id from xtcms_user where u_name='$username'"); if(mysql_fetch_array($query)){ echo ''; exit; } $result = mysql_query('select * from xtcms_user where u_email = "'.$_POST['email'].'"'); if(mysql_fetch_array($result)){ echo ''; exit; } $password = md5(trim($_POST['password'])); $email = trim($_POST['email']); $regtime = time(); $token = md5($username.$password.$regtime); //创建用于激活识别码 $token_exptime = time()+60*60*24;//过期时间为24小时后 $data['u_name'] = $username; $data['u_password'] =$password; $data['u_email'] = $email; $data['u_regtime'] =$regtime; if($xtcms_mail==1){ $data['u_status'] =0; }else{ $data['u_status'] =1; } $data['u_group'] =1; $data['u_fav'] =0; $data['u_plays'] =0; $data['u_downs'] =0; //推广注册 if (isset($_GET['tg'])) { $data['u_qid'] =$_GET['tg']; $result = mysql_query('select * from xtcms_user where u_id="'.$_GET['tg'].'"'); if($row = mysql_fetch_array($result)){ $u_points=$row['u_points']; } 不难发现这里的SELECT语句有以下几个 $sql = 'select * from xtcms_user where u_name = "'.$u_name.'" and u_password = "'.md5($u_password).'" and u_status=1'; $query = mysql_query("select u_id from xtcms_user where u_name='$username'"); $result = mysql_query('select * from xtcms_user where u_email = "'.$_POST['email'].'"'); $result = mysql_query('select * from xtcms_user where u_id="'.$_GET['tg'].'"'); 但文件开头就声明包含了inc.php文件,说明这里的话包含了过滤函数,也就是对SQL注入是有防护的,对'、"以及都进行了转义,因此这里如果参数是被单引号或者双引号包裹的话,那么这里极有可能算是G了,我们看第一个,也就是 $sql = 'select * from xtcms_user where u_name = "'.$u_name.'" and u_password = "'.md5($u_password).'" and u_status=1'; 它这个不难发现,$u_name和$u_password都被双引号包裹了,因此这里就不存在SQL注入了。但是看一下第二个,第二个的username参数虽然是被双引号进行包裹了,但你会发现这个参数的传值方式是$username = stripslashes(trim($_POST['name']));,这个stripslashes的功能是消除由addslashes函数增加的反斜杠,一个增加一个消除,那这里不就跟没有设置过滤一样吗,因此这个name参数是存在SQL注入的,我们通过BurpSuite进行抓包 然后将内容复制到一个txt文件中 我这里保存在sqlmap目录下 而后打开sqlmap,输入如下payload即可 python sqlmap.py "D:/sqlmap/2.txt" --dbs --batch 可以看到存在延时注入,成功爆破出数据库 vlist.php 在这个界面,用单引号测试一下发现跟正常界面有所不同 看一下后端代码 这里简单看一下的话,不难发现这里的参数cid是不存在任何防护的,即没有被单引号或者双引号包裹,因此这里开头引用的inc.php虽然对SQL注入进行了防护,但在这里其实是没有意义的,用SQLmap跑一下 python sqlmap.py -u http://127.0.0.1:8080/kkcms-kkcms/vlist.php?cid=1 --dbs --batch 后端文件 扫出多个后端文件存在SQL注入,接下来逐一进行检测 admin/cms_admin_edit.php 源码如下 //部分代码 这里的话重点关注肯定是SQL语句,也就是这句话 $result = mysql_query('select * from xtcms_manager where m_id = '.$_GET['id'].''); 发现id是无引号包裹的,这意味着这里是存在SQL注入的,我们去验证一下 id=1 id=1 and sleep(5) 发现两者回显时间不同,说明存在SQL注入,具体为时间盲注,这里就可以编写Python脚本来爆破数据库信息,也可以通过SQLmap,这里不再展示 admin/cms_login.php 一眼顶真,无包裹方式,存在SQL注入 id = 16 and sleep(5) 具体不再演示,此类的我将其列在一起,具体如下所示 admin/cms_nav_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_nav where id = '.$_GET['id'].''); admin/cms_detail_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_vod where d_id = '.$_GET['id'].''); admin/cms_channel_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_vod_class where c_id = '.$_GET['id']); admin/cms_check_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_book where id = '.$_GET['id'].''); admin/cms_player_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_player where id = '.$_GET['id'].''); admin/cms_slideshow_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_slideshow where id = '.$_GET['id'].' '); admin/cms_ad_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_ad where id = '.$_GET['id'].' '); admin/cms_link_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_link where l_id = '.$_GET['l_id'].''); admin/cms_usercard_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_userka where id = '.$_GET['id'].''); admin/cms_youlian_edit.php 其SQL语句如下 $result = mysql_query('select * from xtcms_youlian where id = '.$_GET['id'].''); admin/cms_user.php if (isset($_GET['key'])) { $sql = 'select * from xtcms_user where u_name like "%'.$_GET['key'].'%" order by u_id desc'; $pager = page_handle('page',20,mysql_num_rows(mysql_query($sql))); $result = mysql_query($sql.' limit '.$pager[0].','.$pager[1].''); } 这里的话参数是在like处,这里的话经过本地测试及查找资料并未发现此处可以进行SQL注入,通过SQLmap扫描也无果,各位大师傅如果有思路的话还请指点一二 admin/cms_detail.php if (isset($_GET['cid'])) { if ($_GET['cid'] != 0){ $sql = 'select * from xtcms_vod where d_parent in ('.$_GET['cid'].') order by d_id desc'; $pager = page_handle('page',20,mysql_num_rows(mysql_query($sql))); $result = mysql_query($sql.' limit '.$pager[0].','.$pager[1].''); }else{ $sql = 'select * from xtcms_vod order by d_id desc'; $pager = page_handle('page',20,mysql_num_rows(mysql_query($sql))); $result = mysql_query($sql.' limit '.$pager[0].','.$pager[1].''); } } 这里的话关注这里 $sql = 'select * from xtcms_vod where d_parent in ('.$_GET['cid'].') order by d_id desc'; 这个cid参数是被括号包裹的,这里我们可以尝试通过使用这种payload来进行闭合语句从而进行SQL注入 cid=1) and sleep(1) --+ //此时语句$sql = 'select * from xtcms_vod where d_parent in (1) and sleep(5) 根据回显时间可以看出此处是存在SQL注入的 admin/cms_kamilist.php if (isset($_GET['c_used'])) { $sql = 'select * from xtcms_user_card where c_used="'.$_GET['c_used'].'" order by c_id desc'; $pager = page_handle('page',20,mysql_num_rows(mysql_query($sql))); $result = mysql_query($sql.' limit '.$pager[0].','.$pager[1].''); } 这里的话可以看出参数被双引号包裹了,开头包含了SQL防护文件,涉及了addslashes()函数,所以这里自认为是不存在SQL注入的,找下一处。 ucenter/index.php //部分 这里的话可以看见参数是SESSION传参,不同于之前的GET和POST,而且这里还有双引号包裹,因此这里不存在SQL注入,下一处 类似这种的还有 ucenter/kami.php 其SQL语句如下 $result = mysql_query('select * from xtcms_user where u_name="'.$_SESSION['user_name'].'"'); ucenter/chongzhi.php 其SQL语句如下 $result = mysql_query('select * from xtcms_user where u_name="'.$_SESSION['user_name'].'"'); ucenter/mingxi.php 其SQL语句如下 $result = mysql_query('select * from xtcms_user where u_name="'.$_SESSION['user_name'].'"'); ucenter/cms_user_add.php 源码为
    实战 | 记一次较为详细的代码审计过程