问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
DTale代码审计-从身份认证绕过到RCE
漏洞分析
D-Tale 是 Flask 后端和 React 前端的组合,为您提供了一种查看和分析 Pandas 数据结构的简便方法,允许用户方便地浏览和分析数据,而无需编写复杂的代码。Dtale 可以在 Jupyter Notebook 中或者独立的网页中运行,使得分析过程更加直观和高效。该系统存在身份验证绕过和RCE漏洞
D-Tale 是 Flask 后端和 React 前端的组合,为您提供了一种查看和分析 Pandas 数据结构的简便方法,允许用户方便地浏览和分析数据,而无需编写复杂的代码。Dtale 可以在 Jupyter Notebook 中或者独立的网页中运行,使得分析过程更加直观和高效。该系统存在身份验证绕过和RCE漏洞 1、身份认证绕过 ======== Dtale 提供了身份验证系统,由于Dtale的web服务基于Flask的。在 Flask 中,`SECRET_KEY` 是一个非常重要的配置项,它用于对会话(session)数据进行加密和签名,以防止未经授权的篡改。如果攻击者知道了应用的 `SECRET_KEY`,他们就能够伪造合法的会话并绕过应用的身份验证或其他重要的安全机制。  通过查看源码,发现`SECRET_KEY`是被硬编码的,在 Flask 中,会话数据默认存储在客户端,通过一个名为 `session` 的 cookie 发送给客户端。在存储之前,Flask 会对会话数据进行序列化并加密,然后使用 `SECRET_KEY` 进行签名。这样我们就可以通过`SECRET_KEY`伪造session进而绕过认证系统  Flask Session 的组成结构主要由三部分构成,第一部分为 Session Data ,即会话数据。第二部分为 Timestamp ,即时间戳。第三部分为 Cryptographic Hash ,即加密哈希。 要伪造cookie,首先要知道session的格式,对于已经登录的系统,我们可以对其第一部分的 Session Data进行解密,它实际为base64编码后的结果,然后对其中的相关字段进行修改后再进行签名加密。对于不能登录的系统,可以分析其源码,以下为Dtale登录部分的逻辑  通过`/login`路由,会首先通过`authenticate`方法判断用户名密码是否正确,如果正确,然后使用`session` 来存储登录状态和用户名。所以session的格式为:`{'username':'asd','logged_in':'true'}`。 然后使用[flask-session-cookie-manager](https://github.com/noraj/flask-session-cookie-manager)工具来伪造session  最后替换session即可成功登录系统  2、远程命令执行 ======== 在Pandas中,`query` 方法用于基于表达式对 DataFrame 进行筛选。它允许以更直观和简洁的方式编写查询条件。但实际上,`query()`方法内部实现依赖于Python的`eval()`函数,`eval()` 函数用来执行一个字符串表达式,并返回表达式的值。 理论上我们可以执行任意python代码,但经过实验发现并不行,并报错**ValueError: "**import**" is not a supported function**  这是因为`query`会首先会将其中的字符串转换为抽象语法树,由于`__import__`并不是当前上下文中本地变量里的,所以在访问时会报错`UndefinedVariableError`,进而进入下面的异常捕获的流程  在`FuncNode`中会判断该变量是否在`MATHOPS`中,在`MATHOPS`中定义了一些常见的numpy库筛选和计算的函数,显然`__import__`没在其中。   既然无法直接执行,那可以尝试反射的方法去调用`__import__`方法,进而包含任意库从而RCE 在query中可以使用`age > 30`这种基本用法外,还可以使用`@` 用于引用函数外部的变量,也就是说,当你需要在 `query` 表达式中使用当前作用域中的变量时,可以用 `@` 来访问。例如下面这个例子 ```python import pandas as pd data = {'age': [25, 30, 45, 35], 'name': ['Alice', 'Bob', 'Charlie', 'David']} df = pd.DataFrame(data) # 定义一个外部变量 age_threshold = 30 # 在 query 中使用外部变量 result = df.query('age > @age_threshold') print(result) ``` 这样就可以灵活的使用程序中的变量进行筛选条件。  既然可以使用`@`引用函数外部的变量,我们就可以通过`pandas`库中反射调用基类,进而调用`__import__`。这里可以搜索`builtins`,因为`builtins`是**python的内建模块**,且`builtins`中具有`__import__`方法   通过搜索在`pandas`库中有两处都引用了`builtins`,我们挑第一个来构造链,其路径在`core/common.py`下,所以构造链为 ```php @pd.core.common.builtins.__import__("os").system("calc") ``` 接下来就是寻找如何触发`query`进而RCE。 进入后台后,可以可视化的分析Pandas 数据结构,但是这里我们使用筛选选择自定义过滤器时,这里会提示`Custom Filtering is currently disabled.`,并提示需要在启动代码中加入对应的配置项`enable_custom_filters=True`才可以使用。  首先查看对应的代码,搜索当前路由`/popup`,可以看到代码最终调用了`base_render_template`对模板进行渲染 ```python @dtale.route("/popup/<popup_type>") @dtale.route("/popup/<popup_type>/<data_id>") def view_popup(popup_type, data_id=None): """ :class:`flask:flask.Flask` route which serves up a base jinja template for any popup, additionally forwards any request parameters as input to template. :param popup_type: type of popup to be opened. Possible values: charts, correlations, describe, histogram, instances :type popup_type: str :param data_id: integer string identifier for a D-Tale process's data :type data_id: str :return: HTML """ ... return base_render_template( "dtale/popup.html", data_id, title=title, popup_title=popup_title, js_prefix=popup_type, grid_links=grid_links, back_to_data=text("Back To Data"), ) ``` 在`base_render_template`下或获取当前配置中的各种参数,并将参数带入渲染模板   所以说这里所有的配置参数的值最后都是渲染在了前端页面上,那我们就可以直接抓包修改返回包即可绕过。  但是当我们执行自定义的Filter时还是会被提示 not enabled!,通过查看该路由的后端代码,发现在后端也对`enable_custom_filters`的值进行了验证。   但是我们找到了真正执行filter的地方时通过`run_query`方法进而在该方法内用`query`执行传递的参数的。所以可以在这个文件内搜索`run_query`看看那些地方还调用了该方法  最后找到这样一处,在这个方法下并没有二次验证`base_render_template`的值,直接从get请求中获取`query`的值带入`run_query`方法。看到此路由信息为`/chart-data`,请求`http://127.0.0.1:40000/dtale/chart-data/1?query=@pd.core.frame.com.builtins.__import__(%22os%22).system(%22calc%22)`即可触发。  3、修复方法 ====== 在3.13.1中修复了身份认证绕过漏洞,将`SECRET_KEY`的值设置成了随机数  在3.14.1中修复了RCE漏洞,增加了对`base_render_template`的验证 
发表于 2025-02-10 09:49:12
阅读 ( 17470 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
中铁13层打工人
79 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!