bluecms 1.6 审计与渗透测试

Posted bfengj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bluecms 1.6 审计与渗透测试相关的知识,希望对你有一定的参考价值。

前言

听My0n9s师傅说bluecms比较适合审计入门,正好也到了2周审计一个CMS的时候了,就从网上下载了这个CMS的源码,开始审计与渗透测试。
源码下载:
bluecmd

安装

安装的时候主要先看看哪些目录是可写的,到时候如果可以传马或者SQL注入可以写马啥的,至少对于可用的目录知道一些。而且也大致看一下,cache是缓存,upload估计就是上传的路径了,backup是备份,说不定可能存在源码泄露啥的,complie就不清楚了,好像就是smarty?。
在这里插入图片描述

之后就是正常的安装,而且安装那里的用户名和密码不是有默认的,对于非默认设置用户名和密码的CMS,一般若用户名密码的可能性就会小一些。
然后我安装装不好,我吐了。整不明白,在step5:

elseif($act == 'step5')
{
	define('IN_BLUE', TRUE);
	include dirname(__FILE__) . '/../include/common.inc.php';
	include BLUE_ROOT . 'admin/include/common.fun.php';
	update_data_cache();
	update_pay_cache();
	if(is_writable(BLUE_ROOT.'data/'))
	{
		$fp = @fopen(BLUE_ROOT.'data/install.lock', 'wb+');
		fwrite($fp, 'OK');
		fclose($fp);
	}
 	$install_smarty->display('step5.htm');
}

这里:include dirname(__FILE__) . '/../include/common.inc.php';,跟进是这里:

require(BLUE_ROOT.'include/smarty/Smarty.class.php');

这里弄完程序就自动退出了,没搞懂道理哪里出了问题,就暂时放弃这里,反正CMS大致都已经安装成功了。

再看一下有没有重复安装的可能:

 if(file_exists(BLUE_ROOT.'data/install.lock')){
 	install_showmsg('您已经安装过本系统,如果想重新安装,请删除data目录下install.lock文件', '../index.php');
 }
 function install_showmsg($msg,$gourl='goback', $is_write = false)
 {
 	global $install_smarty;
 	$install_smarty->assign("msg",$msg);
 	$install_smarty->assign("gourl",$gourl);
 	$install_smarty->display("showmsg.htm");
 	exit();
 }

有exit,因此没法重复安装。重复安装的话一般就是3种利用,一种是安装时候大概率会把配置写进文件里,而install的时候大部分情况开发都是不过滤的,所以有机会写马。第二种就是直接自己写管理员用户名密码,然后直接拿后台,去后台想办法getshell了。第三种就暂时不说了,因为最近刚拿这种姿势出了题,等比赛结束了再补一下,应该很多师傅都利用过。

index.php

以前从来不看这个,从极致CMS的时候开始看index.php的处理,然后xdcms也跟着学长的文章看了index.php的处理。这里也看一下bluecms的index.php的处理。具体的不分析了,看一下结果叭。最开始的这里:
在这里插入图片描述

跟进common.inc.php看看,一开始是一些ip,模板,数据库啥的处理。然后这段奇奇怪怪的代码:

if(!$_SESSION['user_id'])
{
	if($_COOKIE['BLUE']['user_id'] && $_COOKIE['BLUE']['user_name'] && $_COOKIE['BLUE']['user_pwd'])
	{

 		if(check_cookie($_COOKIE['BLUE']['user_name'], $_COOKIE['BLUE']['user_pwd']))
		{
 			update_user_info($_COOKIE['BLUE']['user_name']);
 		}
 	}
	else if($_COOKIE['BLUE']['user_name'])
	{
		$user_name = $_COOKIE['BLUE']['user_name'];
		$user = $db->query("SELECT COUNT(*) AS num FROM ".table('user')." WHERE user_name='$user_name'");
		if($user['num'] == 1)
		{
			$active = 0;
		}
		else
		{
			$active = 1;
		}
	}
	else
	{
 		setcookie("BLUE[user_id]", '', -86400, $cookiepath, $cookiedomain);
 		setcookie("BLUE[user_name]", '', -86400, $cookiepath, $cookiedomain);
 		setcookie("BLUE[user_pwd]", '', -86400, $cookiepath, $cookiedomain);
 	}
}

直接对Cookie进行一些处理,先看一下check_cookie

 function check_cookie($user_name, $pwd){
 	global $db, $_CFG;
 	$sql = "SELECT pwd FROM ".table('user')." WHERE user_name='$user_name'";

 	$user = $db->getone($sql);
 	if(md5($user['pwd'].$_CFG['cookie_hash']) == $pwd) return true;
 	else return false;
 }

直接的拼接,直接一个SQL注入。。。这个CMS给我搞得人傻了,又是个SQL注入防护的近乎为0的CMS吗。。。。
但是这里是没回显的,但是如果mysql的设置没做防护的话,已经可以写shell了。跟进getone看一下,里面有个query函数:

    function query($sql){
    	if(!$query=@mysql_query($sql, $this->linkid)){
    		$this->dbshow("Query error:$sql");
    	}else{
    		return $query;
    	}
    }

用的mysql_query,没法堆叠注入。而且这里也是判断为空就直接$this->dbshow("Query error:$sql");mysql_query也用的@,怪不得我试不能报错注入。那这样的话就是0过滤的布尔注入了,可以拿sqlmap直接脱裤。而且先提前看一下用户名密码的处理,我这里的用户名和密码都是feng,数据库里是这样:
在这里插入图片描述
把这个pwd直接md5解一下就是的了,所以如果弱密码的话也能md5直接解,强密码的话就有些难了。不过问题不大,或许login那里也能SQL注入呢。

接下来的感觉就是一些index页面的东西了。也挺好,才index.php就拿到一个SQL注入了。

admin登录

其他的暂且不管,先看一下后台登录的那里。访问/admin/index.php肯定会转到login.php了,还是先看一下index.php,一是猜测可能这个index还是有相同的问题,二就是看看鉴权有没有问题。
还是:require_once(dirname(__FILE__) . "/include/common.inc.php");
跟进去,还是同样的问题:

 if(empty($_SESSION['admin_id']) && $_REQUEST['act'] != 'login' && $_REQUEST['act'] != 'do_login' && $_REQUEST['act'] != 'logout'){
 	if($_COOKIE['Blue']['admin_id'] && $_COOKIE['Blue']['admin_name'] && $_COOKIE['Blue']['admin_pwd']){
 		if(check_cookie($_COOKIE['Blue']['admin_name'], $_COOKIE['Blue']['admin_pwd'])){
 			update_admin_info($_COOKIE['Blue']['admin_name']);
 		}
 	}else{
 		setcookie("Blue[admin_id]", '', 1, $cookiepath, $cookiedomain);
 		setcookie("Blue[admin_name]", '', 1, $cookiepath, $cookiedomain);
 		setcookie("Blue[admin_pwd]", '', 1, $cookiepath, $cookiedomain);
 		echo '<script type="text/javascript">top.location="login.php?act=login";</script>';
 		exit();
 	}
 }elseif($_SESSION['admin_id']){
	 update_admin_info($_SESSION['admin_name']);
 }

check_cookie那里进行SQL注入。
但是这里不太一样了,这里我们考虑的就不是SQL注入了,是想办法进后台。check_cookie还是老样子:

 function check_cookie($user_name, $pwd){
 	global $db, $_CFG;
 	$sql = "SELECT pwd FROM ".table('user')." WHERE user_name='$user_name'";

 	$user = $db->getone($sql);
 	if(md5($user['pwd'].$_CFG['cookie_hash']) == $pwd) return true;
 	else return false;
 }

这里还需要一层pwd的认证,能SQL注入这个肯定问题不大。但是我之前看部分处理和这里的,都似乎有些执着于代码的漏洞,没有考虑这个到底是干什么的。仔细再想想的话,这部分好像是Cookie的鉴权,也就是说似乎可以login的时候通过用户名和密码来登录,也可以通过Cookie来获得认证。
看看这个update_admin_info干了什么:

 function update_admin_info($admin_name){
 	global $timestamp, $online_ip, $db;
	$admin = $db->getone("SELECT admin_id, admin_name, purview FROM ".table('admin')." WHERE admin_name = '$admin_name'");
 	$_SESSION['admin_id'] = $admin['admin_id'];
 	$_SESSION['admin_name'] = $admin['admin_name'];
 	$_SESSION['admin_purview'] = explode(',', $admin['purview']);
	$user = $db->getone("SELECT user_id, user_name, money FROM ".table('user')." WHERE user_name='$admin[admin_name]'");
	$_SESSION['user_id'] = $user['user_id'];
	$_SESSION['user_name'] = $user['user_name'];
	$_SESSION['money'] = $user['money'];

	$last_login_time = $timestamp;
	$last_login_ip = $online_ip;
	$db->query("UPDATE ".table('user')." SET last_login_time='$last_login_time' WHERE user_id=".$user['user_id']);
	$sql = "UPDATE ".table('admin')." SET last_login_time = '$last_login_time', last_login_ip = '$last_login_ip' WHERE admin_id='$_SESSION[admin_id]'";
	$db->query($sql);
 }

从admin表重查东西,然后赋值给$_SESSION,因此这里可以通过SQL注入来成为admin。
目前我们的SQL注入已经可以脱裤了,admin的管理员用户名和md5加密后的密码都是知道的,比如我这里用户名是feng。如果这样:

Blue[admin_id]=1;Blue[admin_pwd]=5dfeb62681ec9e6132b9e494fba872e7;Blue[admin_name]=feng

这部分的cookie_hash也是在数据库表里的,能爆出来,pwd就是feng管理员的那md5加密后的密码了,同样能注出来,所以这里的结果是已知的了,可以让check_cookie返回true。

 	if(md5($user['pwd'].$_CFG['cookie_hash']) == $pwd) return true;

然后update_admin_info那里,我用的username是feng,所以信息也是feng的信息,也就是说以管理员的身份登录了:
在这里插入图片描述
因此后台也能拿下。

再老老实实看看正常的后台登录那里能不能SQL注入:

 elseif($act == 'do_login'){
 	$admin_name = isset($_POST['admin_name']) ? trim($_POST['admin_name']) : '';
 	$admin_pwd = isset($_POST['admin_pwd']) ? trim($_POST['admin_pwd']) : '';
 	$remember = isset($_POST) ? intval($_POST['rememberme']) : 0;
 	if($admin_name == ''){
 		showmsg('用户名不能为空');
 	}
 	if($admin_pwd == ''){
 		showmsg('用户密码不能为空');
 	}
 	if(check_admin($admin_name, $admin_pwd)){
 		update_admin_info($admin_name);
 		if($remember == 1){
 			setcookie('Blue[admin_id]', $_SESSION['admin_id'], time()+86400);
 			setcookie('Blue[admin_name]', $admin_name, time()+86400);
			setcookie('Blue[admin_pwd]', md5(md5($admin_pwd).$_CFG['cookie_hash']), time()+86400);
 		}
 	}else{
 		showmsg('您输入的用户名和密码有误');
 	}
 	showmsg('欢迎您 '.$admin_name.' 回来,现在将转向管理中心...', 'index.php');

我定睛一看,又没过滤啊我服了。。。。跟进看看check_admin:

function check_admin($name, $pwd)
{
	global $db;
	$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$name' and pwd = md5('$pwd')");
 	if($row['num'] > 0)
 	{
 		return true;
 	}
 	else
 	{
 		return false;
 	}
}

这tm不是随便注入???然后我试试注入发现我注不了。。。一脸懵逼。。。然后手动debug了一下,发现怎么输入的变量被转义了。。。然后发现common.inc.php的这里原来是有过滤的:

 if(!get_magic_quotes_gpc())
 {
 	$_POST = deep_addslashes($_POST);
 	$_GET = deep_addslashes($_GET);
 	$_COOKIES = deep_addslashes($_COOKIES);
 	$_REQUEST = deep_addslashes($_REQUEST);
 }
function deep_addslashes($str)
{
	if(is_array($str))
	{
		foreach($str as $key=>$val)
		{
			$str[$key] = deep_addslashes($val);
		}
	}
	else
	{
		$str = addslashes($str);
	}
	return $str;
}

addslashes,注锤子注。。。然后我突然想起来,Cookie不是也被转义了吗。。。。然后我就发现了这个:

$_COOKIES = deep_addslashes($_COOKIES);

开发真的牛逼。。。牛逼牛逼,我给开发点赞,真有你的。

后来看了一下别的师傅的审计文章,还能宽字节注入。主要就是因为这个CMS的数据库用的是gbk,我当时也发现了这个问题,但是去本地打开看看:
在这里插入图片描述

哦是utf-8就没想这事。我tm人傻了,是他CMS的mysql_connect用的gbk,关我本来的数据库是啥编码锤子事。。。正因为他mysql_connect用的gbk,所以才能宽字节注入,所以讲道理这个CMS真的SQL注入随便日了。。。。

    function __construct($dbhost, $dbuser, $dbpw, $dbname = '', $dbcharset = 'gbk', $connect = 1) {
    	$this -> mysql($dbhost, $dbuser, $dbpw, $dbname, $dbcharset, $connect);
    }

文件上传

登录进后台第一件事肯定就是去传马,试了一下,传

以上是关于bluecms 1.6 审计与渗透测试的主要内容,如果未能解决你的问题,请参考以下文章

bluecms v1.6 sp1 代码审计学习

[php审计实战篇]BlueCms v1.6 Union注入

测试网站搭建+渗透+审计之第三篇Swagger接口渗透测试

老男孩网络安全课程主要学啥?

测试网站搭建+渗透+审计之第二篇渗透测试

代码审计EasySNS_V1.6远程图片本地化导致Getshell