DjangoUEditor任意文件上传漏洞分析

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

#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方法

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方法中看一看(已省略中间若干代码)

def 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方法

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显然为空,最终的文件名会返回一个格式化的名字,如下图

具体格式化方法如下:

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类型进行过滤,只允许符合要求的类型进行上传。

如果您需要了解更多内容,可以
加入QQ群:570982169
直接询问:010-68438880

Spread the word. Share this post!

Meet The Author

Leave Comment