代码审计

PHP代码审计实践——BlueCMS V1.6

2021-12-29  本文已影响0人  book4yi

前言:


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);
    }
}

触发点一,漏洞代码分析:/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 代码审计

上一篇下一篇

猜你喜欢

热点阅读