漏洞概况

  • 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的地方。

1

开发者的意图是让管理员能够修改网站的Logo。

2

漏洞代码在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.');
}

// 取出文件后缀名(php)
// File extension
$fileExtension = pathinfo($_FILES['inputFile']['name'], PATHINFO_EXTENSION);

// 若已经设置站名名称,将Logo文件名设为站点名称+$fileExtension
// Final filename
$filename = 'logo.'.$fileExtension;
if (Text::isNotEmpty( $site->title() )) {
$filename = $site->title().'.'.$fileExtension;
}

// 删除原先的Logo文件
// Delete old image
$oldFilename = $site->logo(false);
if ($oldFilename) {
Filesystem::rmfile(PATH_UPLOADS.$oldFilename);
}

// 保存新Logo文件
// Move from temporary directory to uploads
rename($_FILES['inputFile']['tmp_name'], PATH_UPLOADS.$filename);

// Permissions
chmod(PATH_UPLOADS.$filename, 0644);

// Store the filename in the database
$site->set(array('logo'=>$filename));

ajaxResponse(0, 'Image uploaded.', array(
'filename'=>$filename,
'absoluteURL'=>DOMAIN_UPLOADS.$filename,
'absolutePath'=>PATH_UPLOADS.$filename
));

?>

没有对用户上传的文件后缀进行检查,直接将网站名称($this->title())和用户上传的文件后缀名($fileExtension)进行了拼接。

9

所以直接上传个php脚本文件

3

从返回的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 :

4

2. 任意文件删除

与这个漏洞相似的漏洞还发生在bl-kernel/ajax/upload-profile-picture.php中,这个漏洞可以导致任意文件删除。

12

代码取出username中的路径和$_FILES['profilePictureInputFile']['tmp_name']的后缀名,拼接成$tmpFilename,然后保存图片,接着删除掉这个临时文件,因为没有对文件路径进行检查,导致了路径遍历,因而造成了任意文件删除。

访问http://192.168.247.135/bludit/admin/edit-user/admin修改用户头像。

11

我先在bl-content/databases/文件夹下新建一个1.txt文件,方便观察效果。

7

burpsuite抓包,将filename的后缀改成txt,并将username改成../../bl-content/databases/1

8

可以看到文件bl-content/databases/1.txt被成功删除了。

官方补丁

1. 任意文件上传补丁

bl-kernel/ajax/logo-upload.php :

patch

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
/*
| Returns the file extension
| Example:
| @file /home/diego/dog.jpg
| @return jpg
|
| @file string Full path of the file
|
| @return string
*/
public static function extension($file) {
return pathinfo($file, PATHINFO_EXTENSION);
}

bl-kernel/helpers/text.class.php :

1
2
3
4
5
// String to lowercase
public static function lowercase($string)
{
return mb_strtolower($string, CHARSET);
}

补丁的关键代码就在第9到13行,先取出文件后缀名,然后将后缀名转化为小写字母,然后检查文件名是否在白名单ALLOWED_IMG_EXTENSION之中。白名单定义在bl-kernel/boot/variables.php中:

patch-2

2. 任意文件删除补丁

bl-kernel/ajax/profile-picture-upload.php:

patch-3

我们知道unlink删除的文件路径由username中的路径和$_FILES['profilePictureInputFile']['tmp_name']的后缀名拼接而成。这里的补丁也分成了两部分,先确保$_FILES['profilePictureInputFile']['tmp_name'](也就是我们上传的文件名)的后缀名在白名单之中,然后通过判断username中的路径是否存在/来判断是否存在路径遍历。

OpenRASP插件编写

对于OpenRASP插件,从漏洞源码可以看出,hook点在第25行的rename,这个点与补丁的距离也不远。

10

从上面的补丁我们已经知道,补丁采取的过滤方法是取出文件的后缀名,然后转成小写字母,然后检查文件后缀名是不是在白名单之中,所以对应的OpenRASP插件需要完成下面几件事:

  1. 取出文件后缀名;
  2. 将文件后缀名转换为小写字母;
  3. 检查文件后缀名是否在白名单之中。

我们可以直接从params中的dest取出文件后缀名,转化为小写字母,然后检查是否存在于白名单之中。

6

javascript中转小写字母的函数是toLowerCase(str.toLowerCase())。

核心代码:

hot_patch

完整代码:

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

脚本测试结果 :

5

参考

  1. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12548
  2. https://github.com/bludit/bludit/issues/1011
  3. https://github.com/bludit/bludit/commit/0dc9904d6218ea61357b39e8cc2831b9795112da#diff-6e3f719cb9636e4905c89575f02f47db
  4. https://rasp.baidu.com/doc/dev/data.html#rename