PHP代码审计实践——BlueCMS V1.6
前言:
PHP代码审计计划一直学习到22年3月份,今天是后悔学安全的第485天。
BlueCMS
-
任意文件读取/修改:
漏洞代码位于:/bluecms/admin/tpl_manage.php
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');
}
这里挺直接的,使用fopen函数打开文件时,直接利用将用户可控的动态变量进行拼接,然后将在模板运行时为模板变量赋值,可以读取任意文件,并且进行修改:
-
文件包含:
漏洞代码位于:/bluecms/user.php
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";
}
这里有两个思路:00截断、点号截断
00截断利用条件:
magic_quotes_gpc=off
PHP版本 < 5.3.4
但由于有设置全局过滤,所以此路不通:
# common.inc.php
if(!get_magic_quotes_gpc())
{
$_POST = deep_addslashes($_POST);
$_GET = deep_addslashes($_GET);
$_COOKIES = deep_addslashes($_COOKIES);
$_REQUEST = deep_addslashes($_REQUEST);
}
那么只能尝试点号截断,且php版本需要小于5.2.8(?)可以成功,只适用windows,点号需要长于256
-
任意文件删除:
可以造成任意文件删除的点挺多的,本文仅列举三处
触发点一,漏洞代码分析:/bluecms/admin/article.php
elseif($act == 'do_edit'){
$title = !empty($_POST['title']) ? trim($_POST['title']) : '';
$color = !empty($_POST['color']) ? trim($_POST['color']) : '';
$cid = !empty($_POST['cid']) ? intval($_POST['cid']) : '';
if(empty($cid)){
showmsg('新闻分类不能为空');
}
$author = !empty($_POST['author']) ? trim($_POST['author']) : $_SESSION['admin_name'];
$source = !empty($_POST['source']) ? trim($_POST['source']) : '';
$is_recommend = intval($_POST['is_recommend']);
$is_check = intval($_POST['is_check']);
if((!empty($_POST['lit_pic1']) && !empty($_FILES['lit_pic2']['name'])) || !empty($_FILES['lit_pic2']['name']))
{
if (file_exists(BLUE_ROOT . $_POST['lit_pic1']))
{
@unlink(BLUE_ROOT . $_POST['lit_pic1']);
}
成功删除根目录下的delete.txt文件:
触发点二,漏洞代码分析:/bluecms/admin/database.php
elseif($act == 'del')
{
$file_name = !empty($_GET['file_name']) ? trim($_GET['file_name']) : '';
$file = BLUE_ROOT.DATA."backup/".$file_name;
if(!@unlink($file))
{
showmsg('删除备份文件失败');
}
else
{
showmsg('删除备份文件成功', 'database.php?act=restore');
}
}
触发点三,漏洞代码分析:/bluecms/publish.php
elseif($act == 'del_pic')
{
$id = $_REQUEST['id'];
$db->query("DELETE FROM ".table('post_pic').
" WHERE pic_path='$id'");
if(file_exists(BLUE_ROOT.$id))
{
@unlink(BLUE_ROOT.$id);
}
}
-
SQL注入:
触发点一,漏洞代码分析:/bluecms/admin/article.php
elseif($act == 'del'){
$article = $db->getone("SELECT cid, lit_pic FROM ".table('article')." WHERE id=".$_GET['id']);
$sql = "DELETE FROM ".table('article')." WHERE id=".intval($_GET['id']);
$db->query($sql);
if (file_exists(BLUE_ROOT.$article['lit_pic'])) {
@unlink(BLUE_ROOT.$article['list_pic']);
}
showmsg('删除本地新闻成功', 'article.php?cid='.$article['cid']);
}
虽然设置了全局过滤,但此处为数字型注入,可以绕过魔术引号的转义,并不影响SQL注入:
触发点二:
首先我们注意到common.inc.php
中对$_POST、$_GET、$_COOKIES、$_REQUEST
统一进行gpc处理,但是遗漏了$_SERVER
,然后想到有没有可能,Web应用程序会将用户的IP写入到数据库中,于是乎发现了:
# common.fun.php
function getip()
{
if (getenv('HTTP_CLIENT_IP'))
{
$ip = getenv('HTTP_CLIENT_IP');
}
elseif (getenv('HTTP_X_FORWARDED_FOR'))
{ //获取客户端用代理服务器访问时的真实ip 地址
$ip = getenv('HTTP_X_FORWARDED_FOR');
}
elseif (getenv('HTTP_X_FORWARDED'))
{
$ip = getenv('HTTP_X_FORWARDED');
}
elseif (getenv('HTTP_FORWARDED_FOR'))
{
$ip = getenv('HTTP_FORWARDED_FOR');
}
elseif (getenv('HTTP_FORWARDED'))
{
$ip = getenv('HTTP_FORWARDED');
}
else
{
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
可以通过在请求头中添加相关字段来伪造IP地址,继续查找存在插入客户端IP的相关代码:
# `/bluecms/common.php`
$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);
触发点三,漏洞代码分析:bluecms/user.php
elseif($act == 'index_login'){
$user_name = !empty($_REQUEST['user_name']) ? trim($_REQUEST['user_name']) : '';
$pwd = !empty($_REQUEST['pwd']) ? trim($_REQUEST['pwd']) : '';
$remember = isset($_REQUEST['remember']) ? intval($_REQUEST['remember']) : 0;
if($user_name == ''){
showmsg('用户名不能为空');
}
if($pwd == ''){
showmsg('密码不能为空');
}
$row = $db->getone("SELECT COUNT(*) AS num FROM ".table('admin')." WHERE admin_name='$user_name'");
if($row['num'] == 1){
showmsg('系统用户组不能从前台登录');
}
$w = login($user_name, $pwd);
可以看到在登录处没有做任何的过滤,虽然设置了全局过滤对所有的参数值执行了addslashes(),但由于mysql使用了GBK编码,故存在宽字节注入的可能:
触发点四,此处是通过WVS扫描出来的,yyds!
漏洞代码分析:/bluecms/include/common.inc.php
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']);
}
}
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;
}
在未登录的情况下,会验证客户端COOKIE是否正确,这里又很有意思,虽然设置了过滤函数:deep_addslashes(),但是针对$_COOKIE['BLUE']['user_name']
,只会检查到username
这个键名就结束了,也就是说并没有对键值进行检测,那么所有包含了include/common.inc.php
的脚本文件都存在COOKIE注入:
-
重装漏洞:
漏洞代码分析:bluecms/install/index.php
require_once(dirname(__FILE__) . '/include/common.inc.php');
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');
}
安装的时候一直到step5时发现页面空白,当时觉得很蹊跷,看了一遍源码似乎没啥问题。当$act == 'step5'
时,会判断/data/目录是否可写并生成lock文件,然后返回step5.htm页面。但成功部署完以后,却又没生成lock文件,这时只能问问度娘了。
归根到底是require的问题,代码里包含了/include/common.inc.php
和/../include/common.inc.php
,这两个文件都包含了如下代码:require(BLUE_ROOT.'include/smarty/Smarty.class.php');
,而require运行时碰到错误会终止运行,两个调用文件都包含了Smarty类的文件,这里重复调用产生了错误,也就不会生成install.lock文件,修复的话把require改成require_once即可,这样就不会有重复调用而产生的错误。
参考如下:
Bluecms代码审计
从小众blueCMS入坑代码审计
bluecms代码审计(一):安装漏洞
通过 BlueCMS 学习 php 代码审计