Django是Python世界最有影响力的web框架。

DjangoUeditor是一款可以在Django应用中集成百度Ueditor HTML编辑器的插件(Ueditor HTML编辑器是百度开源的在线HTML编辑器)。

DjangoUeditor插件上存在一个漏洞,可以导致任意文件上传。


影响版本

DjangoUeditor < 1.9.143

漏洞分析

最近在学习分析python web框架方面的漏洞,恰好看到了WooYun 上2015年的一个关于DjangoUeditor的漏洞,就拿来分析一下。

不要看这个漏洞距今已经快两年了,笔者认为这个漏洞还是有分析的价值的,因为DjangoUeditor作为一个由百度开发的富文本编辑器Ueditor移植到Django中的组件,它的使用率还是挺高的;再者,DjangoUeditor于2015年1月17号后,再也没有更新过,见github上作者的说明:

经笔者测试,现在可以下载使用的1.9.143版本中,依然存在着这个漏洞。因此这个漏洞依然有着一定的影响的。

下面开始正式分析下这个漏洞,根据wooyun上的说明,可以在利用这个插件进行上传图片时,改变imagePathFormat变量的值,即可上传任意文件。

搭建好环境后,来看一下后台的样式,如下图所示

我们来传一张图片并抓包看一下,如下图所示:
注意图中两处红圈处,其中一处便是wooyun中提到的imagePathFormat参数,另一处action参数,这个参数在下文中会用到。

可以看出,当上传一个图片(或者是文件)时候,POST提交的路径是/ueditor/controller/,我们看看DjangoUeditor的路由怎么定义的,来看一下后台代码DjangoUeditor/urls.py

1
2
3
4
5
6
7
8
9
10
11
#coding:utf-8
from django import VERSION
if VERSION[0:2]>(1,3):
from django.conf.urls import patterns, url
else:
from django.conf.urls.defaults import patterns, url

from views import get_ueditor_controller
urlpatterns = patterns('',
url(r'^controller/$',get_ueditor_controller)
)

从urls.py中可以看到,路由定义到views.py中的get_ueditor_controller方法上去了
接下来跟到views.py中去,找到get_ueditor_controller方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_ueditor_controller(request):
"""获取ueditor的后端URL地址 """
action=request.GET.get("action","")
reponseAction={
"config":get_ueditor_settings,
"uploadimage":UploadFile,
"uploadscrawl":UploadFile,
"uploadvideo":UploadFile,
"uploadfile":UploadFile,
"catchimage":catcher_remote_image,
"listimage":list_files,
"listfile":list_files
}
return reponseAction[action](request)

传入的action参数值是uploadimage,因此接下来return进入对应的UploadFile方法

跟到UploadFile方法中看一看(已省略中间若干代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ef UploadFile(request):
"""上传文件"""
。。。。。。。。。。。。。。。。

#检测保存路径是否存在,如果不存在则需要创建
upload_path_format={
"uploadfile":"filePathFormat",
"uploadimage":"imagePathFormat",
"uploadscrawl":"scrawlPathFormat",
"uploadvideo":"videoPathFormat"
}

path_format_var=get_path_format_vars()
path_format_var.update({
"basename":upload_original_name,
"extname":upload_original_ext[1:],
"filename":upload_file_name,
})
#取得输出文件的路径 OutputPathFormat,OutputPath,OutputFile=get_output_path(request,upload_path_format[action],path_format_var)

注意最后一行,在这里调用了一个get_output_path方法来获得输出文件路径以及各式话后的文件名。

为什么要注意这个get_output_path方法呢?回想一下,这个漏洞注入点是什么?是imagePathFormat参数,这里传入get_output_path方法的第二个参数upload_path_format[action]值是什么?见下图。

当action参数值是uploadimage时,upload_path_format[action]值为 imagePathFormat,imagePathFormat将作为第二个参数的值进入get_output_path方法。

接下来跟进get_output_path方法

1
2
3
4
5
6
7
8
9
10
11
def get_output_path(request,path_format,path_format_var):
#取得输出文件的路径
OutputPathFormat=(request.GET.get(path_format,USettings.UEditorSettings["defaultPathFormat"]) % path_format_var).replace("\\","/")
OutputPath,OutputFile=os.path.split(OutputPathFormat)
OutputPath=os.path.join(USettings.gSettings.MEDIA_ROOT,OutputPath)
if not OutputFile:#如果OutputFile为空说明传入的OutputPathFormat没有包含文件名,因此需要用默认的文件名
OutputFile=USettings.UEditorSettings["defaultPathFormat"] % path_format_var
OutputPathFormat=os.path.join(OutputPathFormat,OutputFile)
if not os.path.exists(OutputPath):
os.makedirs(OutputPath)
return ( OutputPathFormat,OutputPath,OutputFile)

现在path_format的值为imagePathFormat,django中的request.GET.get()方法可以跟两个参数,如果get中可以取得第一个参数的值,则使用第一个的值,如果取不到,则使用第二个配置到的默认值。经过处理后OutputPathFormat的值为uploads/images。

接下来用os.path.split来将路径和文件名分割,然后分别赋值给OutputPath,OutputFile

下面就到了漏洞存在的地方了,往下看,下面会判断OutputFile是否为空,为空的话就用标准模板进行格式化,我们这里OutputFile显然为空,最终的文件名会返回一个格式化的名字,如下图

具体格式化方法如下:
1
2
3
4
5
6
7
8
9
10
def get_path_format_vars():
return {
"year":datetime.datetime.now().strftime("%Y"),
"month":datetime.datetime.now().strftime("%m"),
"day":datetime.datetime.now().strftime("%d"),
"date": datetime.datetime.now().strftime("%Y%m%d"),
"time":datetime.datetime.now().strftime("%H%M%S"),
"datetime":datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
"rnd":random.randrange(100,999)
}
但是,如果我们构造的imagePathFormat参数中的值不是一个单纯的路径,而是一个包含文件名的参数呢?(如imagePathFormat = uploads%2Fimages%2Fhehehe.xxxx) 显而易见,OutputFile就是我们构造的hehehe.xxxx。 验证如下
经验证,写入的路径也是可以控制的,不仅限于images路径,并且可以覆盖其他的文件。
### 修补防御 可以通过修改代码,对用户上传的OutputFile类型进行过滤,只允许符合要求的类型进行上传。