bees cms是通过PHP+MYSQL开发,多语言系统,内容模块易扩展,模板风格多样化,模板制作简单功能强大,专业SEO优化,后台操作方便,完全可以满足企业网站、外贸网站、事业单位、教育机构、个人网站使用。
人生中第一次完整的代码审计实战,使用的Seay代码审计工具,环境使用的Phpstudy一键部署。
拿到源代码后,首先看一下index.php文件,了解一下系统入口。该文件主要操作就是包含了三个文件,如下所示:
require_once('includes/init.php');
require_once('includes/fun.php');
require_once('includes/lib.php');
init.php文件负责一些初始化工作,主要包括将用户输入的数据进行转义过滤,代码如下:
if (!get_magic_quotes_gpc()) //如果没有设置magic_quotes_gpc则手动过滤参数
{
if (isset($_REQUEST))
{
$_REQUEST = addsl($_REQUEST);
}
$_COOKIE = addsl($_COOKIE);
$_POST = addsl($_POST);
$_GET = addsl($_GET);
}
if (isset($_REQUEST)){$_REQUEST = fl_value($_REQUEST);} //过滤sql注入
$_COOKIE = fl_value($_COOKIE);
$_GET = fl_value($_GET);
addsl函数中会将用户输入的参数循环调用addslashes,会将单双引号和反斜杠和空字符进行转义,通过这一轮操作后,字符型注入基本凉凉~不过数值型注入还是可以的。
经过addsl转义之后,又调用了fl_value过滤一下sql注入的一些字段,详细代码如下:
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);
}
可以看到过滤了一些sql注入需要用到的关键字,但是这是可以绕过的,比如select可以双写绕过,union可以用“uni union on”绕过,其实这个函数用处不大,主要是addsl。
接下来,get到了第一个漏洞:
漏洞1 变量覆盖:
在经历过转义过滤后还有如下操作:
@extract($_POST);
@extract($_GET);
@extract($_COOKIE);
很明显,该操作有全局变量覆盖的漏洞,可以通过该漏洞覆盖SESSION变量直接进入后台,但是测试的时候没成功,不知道为什么。
以上就是init.php的主要功能,在审计其他文件的时候,如果包含了该文件,字符型注入就可以直接放弃了。
看完这个文件后又看了看fun.php,和lib.php,没发现什么问题后开始审计admin目录。
首先看一下login.php,这是后台登陆的入口。
漏洞2 字符型sql注入:
刚刚看完init.php觉得应该不会有什么sql注入了,结果漏洞就来了~
原因是该文件没有包含init.php,至于原因,咱也不知道~详细代码如下:
@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); //存在sql注入
可以看到,该文件没有包含Init.php,只是调用了fl_value过滤一下sql注入需要的字段,可是前面也说了,这个函数并没有太大的用处,是可以绕过的。然后将用户输入直接带入了check_login函数,该函数没有任何过滤措施:
function check_login($user,$password){
$rel=$GLOBALS['mysql']->fetch_asc("select id,admin_name,admin_password,admin_purview,is_disable from ".DB_PRE."admin where admin_name='".$user."' limit 0,1");
$rel=empty($rel)?'':$rel[0];
if(empty($rel)){
msg('不存在该管理用户','login.php');
}
$password=md5($password);
if($password!=$rel['admin_password']){ //存在弱类型比较漏洞,只要不等号两边都是0e开头哈希值将被解析为0.
msg("输入的密码不正确");
}
if($rel['is_disable']){
msg('该账号已经被锁定,无法登陆');
}
$_SESSION['admin']=$rel['admin_name'];
$_SESSION['admin_purview']=$rel['admin_purview'];
$_SESSION['admin_id']=$rel['id'];
$_SESSION['admin_time']=time();
$_SESSION['login_in']=1;
$_SESSION['login_time']=time();
$ip=fl_value(get_ip());
$ip=fl_html($ip);
$_SESSION['admin_ip']=$ip;
unset($rel);
header("location:admin.php");
该函数不光存在sql注入,还存在PHP弱类型比较漏洞,关于弱类型比较在这里就不详细说明了,可以参考https://www.cnblogs.com/Mrsm1th/p/6745532.html。
知道了漏洞原理之后,可以通过如下payload直接登陆后台:
user=1'+uni+union+on+seleselectct+1,'admin','202cb962ac59075b964b07152d234b70',1,'0&password=123
完事之后,开始在admin目录一个一个文件开始审计。
漏洞3 sql注入:
该漏洞存在admin_book.php,在删除留言的时候存在两个数字型sql注入,详细代码如下:
elseif($action=='del'){
$id=$_GET['id'];
if(empty($id)){die("<script type=\"text/javascript\">alert('参数发生错误,请重新操作');history.go(-1);</script>");}
$sql="delete from ".DB_PRE."book where id=".$id;
$mysql->query($sql);
msg('删除完成','?lang='.$lang.'&nav='.$admin_nav.'&admin_p_nav='.$admin_p_nav);
}
//删除多选
//存在注入,原因跟上面一样
elseif($action=='del_all'){
$id=$_POST['all'];
if(empty($id)){msg('请选择需要删除的内容','?lang='.$lang);}
foreach($id as $k=>$v){
$sql="delete from ".DB_PRE."book where id=".$v;
$mysql->query($sql);
}
msg("所选内容已经删除",'?lang='.$lang.'&nav='.$admin_nav.'&admin_p_nav='.$admin_p_nav);
}
漏洞原因主要是由于没有调用intval过滤参数,导致可以通过报错注入或者延迟注入。
漏洞4 sql注入:
该漏洞存在于admin_db.php文件中,数据库查询limit后参数可控,详细代码如下:
elseif($action=='save_back'){
if(!check_purview('data_backup')){msg('<span style="color:red">操作失败,你的权限不足!</span>');}
$db = $_POST['db'];
$init = isset($_POST['init'])?$_POST['init']:0;
$sql_size = 1048;
$dir = isset($_GET['dir'])?$_GET['dir']:'';
//缓存所有表
if($init){
if(empty($db)){msg('请选择要备份的表');}
$str="<?php\n\$table_arr=".var_export($db,true).";\n?>";
$file=DATA_PATH.'cache/db_cache.php';
creat_inc($file,$str);
//创建备份目录
$dir = 'db'.date(YmdHms,time());
@mkdir(DATA_PATH.'backup/'.$dir);
}
@include(DATA_PATH.'cache/db_cache.php');
$table_id = isset($_GET['table_id'])?$_GET['table_id']-1:0;
$r_start = isset($_GET['r_start'])?$_GET['r_start']:0;
$sql = '';
$start = isset($r_start)?$r_start:0;
for($i=$table_id;$i<count($table_arr)&& strlen($sql)<$sql_size*1000;$i++){
$table = $table_arr[$i];
//当前表的备份小于卷大小
if(strlen($sql) < $sql_size*1000){
//备份表
if(!$start){
$rel=$GLOBALS['mysql']->fetch_asc("SHOW CREATE TABLE `{$table}` ");
$sql.="DROP TABLE IF EXISTS `".$table."`;\n";
$sql.=$rel[0]['Create Table'].";\n";
}
//备份数据
$offset=5;
while(strlen($sql) < $sql_size*1000){
$record=$GLOBALS['mysql']->fetch_asc("select*from ".$table." limit {$start},{$offset}");
数据库查询中的start字段可控,但是参考https://www.jianshu.com/p/6c1420a7a7d9的注入方法没成功。
漏洞5 任意文件删除:
该漏洞也存在于admin_db.php文件中,由于删除文件时没有对文件路径进行检查,可以造成目录穿越导致任意文件删除。详细代码如下:
elseif($action=='del'){
if(!check_purview('data_import')){msg('<span style="color:red">操作失败,你的权限不足!</span>');}
$fl = $_GET['fl'];
if(empty($fl)){err('<span style="color:red">参数传递错误,请重新操作</span>');}
$db_handler=@opendir(DATA_PATH.'backup/'.$fl);
if($db_handler){
while(false!==($d_file=readdir($db_handler))){
@unlink(DATA_PATH.'backup/'.$fl.'/'.$d_file);
}
}
@rmdir(DATA_PATH.'backup/'.$fl);
msg($fl.'删除成功','?action=import&nav='.$admin_nav.'&admin_p_nav='.$admin_p_nav);
可以看到,fl参数没有任何进行任何检查。
漏洞6 sql注入:
该漏洞存在于admin_flash_ad.php文件,是一个数字型注入,详细代码如下:
elseif($action == 'del_cate')
{
$id = $_GET['id'];
if(empty($id)){msg('参数传递错误,请重新操作');}
if($id=='1'){msg('该分类为固定分类,不能删除');}
//是否有内容
$sql = "select count(id) as n from ".DB_PRE."flash_ad where cate_id =".$id;
$rel=$mysql->fetch_asc($sql);
if($rel[0]['n']){msg('<span style="color:red">请先删除该分类下的图片</span>');}
$sql = 'delete from '.DB_PRE.'flash_ad_cate where id='.$id;
$mysql->query($sql);
msg('分类成功删除','?action=list_cate'.'&nav=list_flash_cate&admin_p_nav='.$admin_p_nav);
}
echo PW;
function is_sq(){if(!ck_ck()){$sql="SELECT COUNT(id) AS m FROM ".DB_PRE."flash_ad WHERE lang='".$GLOBALS['lang']."'";$rel=$GLOBALS['mysql']->fetch_asc($sql);if($rel[0]['m']>=3){return true;}}}
同样因为没有对数字型参数进行过滤。
漏洞7 任意文件上传,任意目录上传,任意文件删除:
由于这三个漏洞在一个代码块中,就写在一起了,详细代码如下:
elseif($action=='save_edit'){
$id=intval($_POST['id']);
if(empty($id)){msg('参数发生错误,请重新操作');}
$is_thumb=intval($_POST['is_thumb']);
$thumb_width=intval($_POST['thumb_width']);
$thumb_width=empty($thumb_width)?$_sys['thump_width']:$thumb_width;
$thumb_height=intval($_POST['thumb_height']);
$thumb_height=empty($thumb_height)?$_sys['thumb_height']:$thumb_height;
$pic_alt=$_POST['pic_alt'];//图片alt
$pic_thumb=$_POST['pic_thumb'];//图片缩略图
$pic_thumb = iconv('UTF-8','GBK',$pic_thumb);
$pic_ext=$_POST['pic_ext'];//图片后缀名
$file_name=CMS_PATH.$_POST['pic'];//上传图片路径
$file_name = iconv('UTF-8','GBK',$file_name);
$pic_name=$_POST['pic_name'];//图片名称
$pic_name = iconv('UTF-8','GBK',$pic_name);
$pic_path=$_POST['pic_path'];//图片所在目录
$pic_cate=$_POST['pic_cate'];//图片类别
$new_pic=$_FILES['new_pic'];
$return_thumb='';//缩略图
if(file_exists(DATA_PATH.'sys_info.php')){include(DATA_PATH.'sys_info.php');}
//是否重新上传图片
if(is_uploaded_file($new_pic['tmp_name'])){
//判断大小
if($new_pic['size']>$_sys['upload_size']){msg('图片太大,请缩小');}
//判断格式
if(!in_array(strtolower($new_pic['type']),array('image/gif','image/jpeg','image/png','image/jpg','image/bmp','image/pjpeg'))){msg('上传图片格式不正确');} //存在任意文件上传
//图片信息
$new_pic_info=pathinfo($new_pic['name']);
//替换图片
$new_pic_name=CMS_PATH.$pic_path.$pic_name.'.'.$new_pic_info['extension'];
//删除原来图片
//@unlink($file_name);
//上传图片
@move_uploaded_file($new_pic['tmp_name'],$new_pic_name); //任意目录上传
//对文件重新赋值,方便生成缩略图
$file_name=$new_pic_name;
//更新数据库
$new_pic_sql=",pic_ext='".$new_pic_info['extension']."',pic_size='".$new_pic['size']."'";
}
if($is_thumb){//开启缩略图
$file_info=@getimagesize($file_name);
if(empty($file_info)){msg('图片不存在,操作失败');}
//删除以前的缩略图
if($pic_thumb){@unlink(CMS_PATH.'upload/'.$pic_thumb);} //任意文件删除
首先是任意文件上传,很明显只检查了content-type,最直白的上传漏洞了。
任意目录上传,由于move_uploaded_file的路径参数的一部分可控,所以可以任意目录上传,但是文件后缀无法改变。
任意文件删除,由于删除的文件路径可控,所以可以删除任意文件。这个参数压根不应该由客户端指定。
漏洞8 任意文件上传:
该漏洞存在于admin_pic_upload.php文件,详细代码如下:
if(is_uploaded_file($v)){
$pic_info['tmp_name']=$v;
$pic_info['size']=$_FILES['up']['size'][$k];
$pic_info['type']=$_FILES['up']['type'][$k];
$pic_info['name']=$_FILES['up']['name'][$k];
$pic_name_alt=empty($is_alt)?'':$pic_alt[$k];
$is_up_size = $_sys['upload_size']*1000*1000;
//任意文件上传
$value_arr=up_img($pic_info,$is_up_size,array('image/gif','image/jpeg','image/png','image/jpg','image/bmp','image/pjpeg','image/x-png'),$up_is_thumb,$up_thumb_width,$up_thumb_height,$logo=1,$pic_name_alt);
//处理上传后的图片信息
可以看到调用了up_img上传图片,但是通过传参就可以看出用于校验的type数组明显是content-type,跟上面的任意文件上传一个原理。
漏洞9 任意文件上传:
漏洞存在于upload.php,详细代码如下:
if(isset($_FILES['up'])){
if(is_uploaded_file($_FILES['up']['tmp_name'])){
if($up_type=='pic'){
$is_thumb=empty($_POST['thumb'])?0:$_POST['thumb'];
$thumb_width=empty($_POST['thumb_width'])?$_sys['thump_width']:intval($_POST['thumb_width']);
$thumb_height=empty($_POST['thumb_height'])?$_sys['thump_height']:intval($_POST['thumb_height']);
$logo=0;
$is_up_size = $_sys['upload_size']*1000*1000;
//任意文件上传
$value_arr=up_img($_FILES['up'],$is_up_size,array('image/gif','image/jpeg','image/png','image/jpg','image/bmp','image/pjpeg'),$is_thumb,$thumb_width,$thumb_height,$logo);
和之前的任意文件上传一样,没什么好说的。