漏洞概况
Bludit before 3.9.0 allows remote code execution for an authenticated user by uploading a php file while changing the logo through /admin/ajax/upload-logo.
Bludit before 3.9.0 allows arbitrary file deletion for an authenticated user by uploading a file while changing the picture of the user through bl-kernel/ajax/profile-picture-upload.php
环境搭建
Bludit CMS 3.8.1
php 5.6.40
Ubuntu 18.04 + Mysql 4.7 + Apache2
漏洞分析 1. 任意文件上传
从漏洞概况可以看出,漏洞点在用户更换网站Logo的地方。
开发者的意图是让管理员能够修改网站的Logo。
漏洞代码在bl-kernel/admin/ajax/upload-logo.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 31 32 33 34 35 36 37 38 39 40 41 42 <?php defined('BLUDIT' ) or die ('Bludit CMS.' );header('Content-Type: application/json' ); if (!isset ($_FILES['inputFile' ])) { ajaxResponse(1 , 'Error trying to upload the site logo.' ); } $fileExtension = pathinfo($_FILES['inputFile' ]['name' ], PATHINFO_EXTENSION); $filename = 'logo.' .$fileExtension; if (Text::isNotEmpty( $site->title() )) { $filename = $site->title().'.' .$fileExtension; } $oldFilename = $site->logo(false ); if ($oldFilename) { Filesystem::rmfile(PATH_UPLOADS.$oldFilename); } rename($_FILES['inputFile' ]['tmp_name' ], PATH_UPLOADS.$filename); chmod(PATH_UPLOADS.$filename, 0644 ); $site->set(array ('logo' =>$filename)); ajaxResponse(0 , 'Image uploaded.' , array ( 'filename' =>$filename, 'absoluteURL' =>DOMAIN_UPLOADS.$filename, 'absolutePath' =>PATH_UPLOADS.$filename )); ?>
没有对用户上传的文件后缀进行检查,直接将网站名称($this->title()
)和用户上传的文件后缀名($fileExtension
)进行了拼接。
所以直接上传个php脚本文件
从返回的json信息中甚至可以看到上传的文件路径
1 2 3 4 5 ajaxResponse(0 , 'Image uploaded.' , array ( 'filename' =>$filename, 'absoluteURL' =>DOMAIN_UPLOADS.$filename, 'absolutePath' =>PATH_UPLOADS.$filename ));
访问http://192.168.247.135/bludit/bl-content/uploads/BLUDIT.php :
2. 任意文件删除
与这个漏洞相似的漏洞还发生在bl-kernel/ajax/upload-profile-picture.php
中,这个漏洞可以导致任意文件删除。
代码取出username
中的路径和$_FILES['profilePictureInputFile']['tmp_name']
的后缀名,拼接成$tmpFilename
,然后保存图片,接着删除掉这个临时文件,因为没有对文件路径进行检查,导致了路径遍历,因而造成了任意文件删除。
访问http://192.168.247.135/bludit/admin/edit-user/admin修改用户头像。
我先在bl-content/databases/
文件夹下新建一个1.txt
文件,方便观察效果。
burpsuite抓包,将filename
的后缀改成txt
,并将username
改成../../bl-content/databases/1
。
可以看到文件bl-content/databases/1.txt
被成功删除了。
官方补丁 1. 任意文件上传补丁
bl-kernel/ajax/logo-upload.php
:
bl-kernel/helpers/filesystem.class.php
:
1 2 3 4 public static function mv ($oldname, $newname) { return rename($oldname, $newname); }
bl-kernel/helpers/filesystem.class.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 public static function extension ($file) { return pathinfo($file, PATHINFO_EXTENSION); }
bl-kernel/helpers/text.class.php
:
1 2 3 4 5 public static function lowercase ($string) { return mb_strtolower($string, CHARSET); }
补丁的关键代码就在第9到13行,先取出文件后缀名,然后将后缀名转化为小写字母,然后检查文件名是否在白名单ALLOWED_IMG_EXTENSION
之中。白名单定义在bl-kernel/boot/variables.php
中:
2. 任意文件删除补丁
bl-kernel/ajax/profile-picture-upload.php
:
我们知道unlink删除的文件路径由username
中的路径和$_FILES['profilePictureInputFile']['tmp_name']
的后缀名拼接而成。这里的补丁也分成了两部分,先确保$_FILES['profilePictureInputFile']['tmp_name']
(也就是我们上传的文件名)的后缀名在白名单之中,然后通过判断username
中的路径是否存在/
来判断是否存在路径遍历。
OpenRASP插件编写 对于OpenRASP插件,从漏洞源码可以看出,hook点在第25行的rename,这个点与补丁的距离也不远。
从上面的补丁我们已经知道,补丁采取的过滤方法是取出文件的后缀名,然后转成小写字母,然后检查文件后缀名是不是在白名单之中,所以对应的OpenRASP插件需要完成下面几件事:
取出文件后缀名;
将文件后缀名转换为小写字母;
检查文件后缀名是否在白名单之中。
我们可以直接从params
中的dest
取出文件后缀名,转化为小写字母,然后检查是否存在于白名单之中。
javascript中转小写字母的函数是toLowerCase(str.toLowerCase())。
核心代码:
完整代码:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 const plugin_version = 'CVE-2019-12548' const plugin_name = 'CVE-2019-12548' 'use strict' var plugin = new RASP(plugin_name)const clean = { action: 'ignore' , message: 'Looks fine to me' , confidence: 0 } const danger = { action: 'block' , message: 'May Be Dangerous' , confidence: 100 , algorithm: '' } CVE_2019_12548 = {} CVE_2019_12548.id = 'CVE-2019-12548' CVE_2019_12548.attack_type = 'rename' CVE_2019_12548.attack_params = "var args = params.dest.split('.'); var ext = args[args.length-1].toLowerCase();" CVE_2019_12548.detected_context = true CVE_2019_12548.attack_patch = "ext.indexOf(whitelist) == -1" CVE_2019_12548.whitelist = ['gif' , 'png' , 'jpg' , 'jpeg' , 'svg' ] CVE_2019_12548.known = {} option = CVE_2019_12548 plugin.register(option.attack_type, function (params, context ) { var attack_params = eval (option.attack_params) var whitelist = option.whitelist var known = option.known if (eval (option.detected_context)) { if (eval (option.attack_patch)) { return danger } } else { return clean } })
脚本测试结果 :
参考
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12548
https://github.com/bludit/bludit/issues/1011
https://github.com/bludit/bludit/commit/0dc9904d6218ea61357b39e8cc2831b9795112da#diff-6e3f719cb9636e4905c89575f02f47db
https://rasp.baidu.com/doc/dev/data.html#rename
Author:
Bantian
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
早睡早起身体好