前言
在现代数据驱动的世界中,数据安全和隐私保护变得越来越重要。Apache Superset 作为一个广泛使用的开源数据探索和可视化平台,其安全性尤其受到关注。本文将详细介绍我们在一次审计过程中发现的一个严重漏洞,以及如何利用这一漏洞绕过 Apache Superset 的安全措施,执行任意 SQL 查询。
什么是 Apache Superset?
Apache Superset 是一个开源平台,用于数据探索和可视化。它提供了一个无需编码的界面,用于快速图表制作,使用户能够在不编写复杂 SQL 查询的情况下探索数据。此外,Superset 还提供了一个基于 Web 的 SQL 编辑器,用于更高级的分析。尽管可以执行 SQL 查询,但并非所有操作都对用户开放,因为安全性和数据隔离是考虑的重要因素。
背景
在一次针对某企业 Web 应用的安全审计过程中,发现某企业的应用程序使用了Apache Superset(版本4.0.1,撰写本文时的最新版本)来展示数据图表和分析结果。通过分析应用程序与Burp之间的交互,发现可以与Apache Superset的API进行交互。
在进一步的测试中,发现可以通过以下API路径控制DBMS执行某些 SQL 查询:
/superset/explore_json/(使用 xpath_exists(xpath, xml [, nsarray]) 将基于时间的注入转换为基于错误的注入,该函数评估XPath 1.0表达式)
/api/v1/chart/data
需要注意的是,在Apache Superset中执行 SQL 查询是正常的,因为这是该应用程序的主要目的,并不被视为漏洞。这篇文章的目的是展示已识别的关于防止攻击者执行任意 SQL 请求的安全措施的弱点。
一旦用户(因此也是攻击者)试图执行任意请求,这些请求就会被安全机制阻止。
查看代码
为了确定阻塞的原因,只需从GitHub获取源代码。经过快速的代码审查,以下代码片段似乎检查查询是否包含任何子查询。
文件:superset/models/helpers.py
函数:validate_adhoc_subquery()
def validate_adhoc_subquery(
sql: str,
database_id: int,
default_schema: str,
) -> str:
"""
Check if adhoc SQL contains sub-queries or nested sub-queries with table.
If sub-queries are allowed, the adhoc SQL is modified to insert any applicable RLS
predicates to it.
:param sql: adhoc sql expression
:raise SupersetSecurityException if sql contains sub-queries or
nested sub-queries with table
"""
statements = []
for statement in sqlparse.parse(sql):
if has_table_query(statement):
if not is_feature_enabled("ALLOW_ADHOC_SUBQUERY"):
raise SupersetSecurityException(
SupersetError(
error_type=SupersetErrorType.ADHOC_SUBQUERY_NOT_ALLOWED_ERROR,
message=_("Custom SQL fields cannot contain sub-queries."),
level=ErrorLevel.ERROR,
)
)
statement = insert_rls_in_predicate(statement, database_id, default_schema)
statements.append(statement)
return ";\n".join(str(statement) for statement in statements)
如上所示,validate_adhoc_subquery() 函数调用了 has_table_query() 函数。
文件:superset/sql_parse.py
函数:has_table_query()
def has_table_query(token_list: TokenList) -> bool:
"""
Return if a statement has a query reading from a table.
>>> has_table_query(sqlparse.parse("COUNT(*)")[0])
False
>>> has_table_query(sqlparse.parse("SELECT * FROM table")[0])
True
Note that queries reading from constant values return false:
>>> has_table_query(sqlparse.parse("SELECT * FROM (SELECT 1)")[0])
False
"""
state = InsertRLSState.SCANNING
for token in token_list.tokens:
# Ignore comments
if isinstance(token, sqlparse.sql.Comment):
continue
# Recurse into child token list
if isinstance(token, TokenList) and has_table_query(token):
return True
# Found a source keyword (FROM/JOIN)
if imt(token, m=[(Keyword, "FROM"), (Keyword, "JOIN")]):
state = InsertRLSState.SEEN_SOURCE
# Found identifier/keyword after FROM/JOIN
elif state == InsertRLSState.SEEN_SOURCE and (
isinstance(token, sqlparse.sql.Identifier) or token.ttype == Keyword
):
return True
# Found nothing, leaving source
elif state == InsertRLSState.SEEN_SOURCE and token.ttype != Whitespace:
state = InsertRLSState.SCANNING
return False
has_table_query() 函数使用 sqlparse 库解析将要执行的 SQL 查询,以在执行前验证或拒绝查询。该功能的工作原理如下:查询元素被标记化,如果查询包含禁止的元素,则查询被拒绝。我们发现,可能是FROM子句的使用触发了安全机制。
阅读文档并找到绕过方法
在参考了文档中的几个命令设置实验环境后,发现默认的DBMS是PostgreSQL,于是开始研究如何绕过安全措施以执行任意查询。。
git clone https://github.com/apache/superset
cd superset
docker compose -f docker-compose-image-tag.yml up
查看了PostgreSQL的文档,并识别了以下函数:
- query_to_xml(query text, nulls boolean, tableforest boolean, targetns text):将关系表的内容映射为 XML 值。
- query_to_xml_and_xmlschema(query text, nulls boolean, tableforest boolean, targetns text):在一个文档(或森林)中生成 XML 数据映射及其对应的 XML Schema,并将其链接在一起。
- table_to_xml(tbl regclass, nulls boolean, tableforest boolean, targetns text):将关系表的内容映射为 XML 值。
- table_to_xml_and_xmlschema(tbl regclass, nulls boolean, tableforest boolean, targetns text):在一个文档(或森林)中生成 XML 数据映射及其对应的 XML Schema,并将其链接在一起。
- database_to_xml(nulls boolean, tableforest boolean, targetns text):生成整个模式或当前数据库的类似映射。
这些函数接受字符串作为参数,将其作为 SQL 查询执行,并将结果格式化为预期的输出(XML)。因此,我们可以通过调用这些函数来执行任意请求。在解析过程中(由 sqlparse 进行),我们的恶意查询被视为字符串(函数参数),并被“标记化”为字符串,这就是为什么 has_table_query 函数无法检测到注入的原因。
结论
通过这次审计,不仅发现了Apache Superset中的一个严重漏洞,还展示了如何通过深入研究和文档分析找到绕过安全措施的方法。这一发现提醒我们,即使是最成熟的数据平台也可能存在安全漏洞,因此持续的安全审计和及时的漏洞修复至关重要。希望这篇文章能帮助大家更好地理解Apache Superset的安全机制及其潜在的漏洞,并采取相应的防护措施,确保数据的安全性和完整性。