CVE-2026-22218 Chainlit 框架恣意文件读取毛病全分析

[复制链接]
发表于 3 天前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
毛病简介

Chainlit 是一个开源的 Python 框架,专门用于快速构建对话式人工智能(Conversational AI)应用步伐和大语言模子(LLM)接口。该框架基于 FastAPI 和 Socket.IO 构建,提供了丰富的用户界面组件和及时通讯本领,使开发者可以大概轻松创建雷同 ChatGPT 的对话界面。Chainlit 广泛应用于谈天呆板人、AI 助手、客户服务体系等场景,支持多种 LLM 后端(如 OpenAI、Anthropic Claude、LangChain 等)的集成,并提供了完满的用户认证、会话管理、文件处理处罚等企业级功能
核心题目:Chainlit 在处理处罚自界说元素(Custom Element)时,没有对用户传入的文件路径做任何验证,也没有对未认证用户举行有用拦截,导致恣意人都可以让服务器读取其本地恣意文件并通过接口返回给攻击者。
更具体地说,这个毛病由两个独立的代码缺陷叠加形成:

  • 缺陷一:权限查抄形同虚设——当服务器未设置欺压身份认证时,核心接口的 if current_user 判定直接被跳过
  • 缺陷二:路径完全不做校验——用户传入的 path 字段被原封不动地传入文件读取函数,攻击者可以指向服务器上恣意位置
这两个缺陷单独看都算严峻,组合在一起就造成了"无认证+恣意文件读取"的高危毛病
[img=720,397.971181556196]https://pic1.imgdb.cn/item/69eefddbfc83efdcc4c08d48.png[/img]

毛病复现

整个攻击分为两步,合计只需两次 HTTP 哀求加一个 WebSocket 毗连:

  • 第一步:向 /project/element 发送 PUT 哀求,在哀求体中注入一个包罗恣意文件路径(如 /etc/passwd)的 path 字段,触发服务器读取该文件并缓存,同时通过 WebSocket 得到一个"文件令牌"(chainlitKey)
  • 第二步:携带这个文件令牌访问 /project/file/{chainlitKey},服务器直接把刚才读取的文件内容返回给攻击者
[img=720,539.3092862624712]https://pic1.imgdb.cn/item/69eefdecfc83efdcc4c08d51.png[/img]

创建一个 demo.py
  1. import chainlit as cl
  2. @cl.step(type="tool")
  3. async def tool():
  4.    # Fake tool
  5.    await cl.sleep(2)
  6.    return "Response from the tool!"
  7. @cl.on_message  # this function will be called every time a user inputs a message in the UI
  8. async def main(message: cl.Message):
  9.    """
  10.    This function is called every time a user inputs a message in the UI.
  11.    It sends back an intermediate response from the tool, followed by the final answer.
  12.    Args:
  13.        message: The user's message.
  14.    Returns:
  15.        None.
  16.    """
  17.    # Call the tool
  18.    tool_res = await tool()
  19.    await cl.Message(content=tool_res).send()
复制代码
使用 python 假造情况 方便搭建情况
  1. python -m venv venv
  2. venv\Scripts\Activate.ps1
  3. python -m pip install chainlit==2.9.3 #安装存在漏洞的 chainlit 版本
  4. chainlit run demo.py -w
复制代码
[img=720,320.7857142857143]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/dca8255d-9d18-4a91-85e0-c20ff0269849.png[/img]

运行构造好的 chainlit_file_read_exploit.py 指定 url 和必要读取的文件内容,就可以将文件打印出来
[img=720,354.85714285714283]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/e17dbf64-7e1f-4f83-9834-a182f473b789.png[/img]

毛病分析

第一步是通过调用 PUT /project/element​ 接口注入恶意文件路径,当攻击者在哀求参数中传入包罗恣意路径的 path​ 字段时(如 /etc/passwd​),服务器端的 persist_file()​ 函数会读取该路径指定的文件内容并将其复制到暂时目次中,同时将暂时文件路径与一个随机天生的文件标识符(file_id)创建映射关系并注入到当前会话的文件映射表(session.files)中,随后服务器通过 WebSocket 消息将这个文件标识符(chainlitKey)推送给客户端,攻击者必要监听 WebSocket 毗连来捕捉这个关键的文件ID。
server.py#update_thread_element
[img=720,316.92857142857144]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/57b23fc4-5056-4ccd-b590-4da7c82d2ff5.png[/img]

update_thread_element​ 函数汲取到哀求后,起首调用 Element.from_dict()​ 方法分析哀求体中的元素字典,该方法根据 type​ 字段判定元素范例并创建对应的对象实例,当 type​ 为 custom​ 时会创建 CustomElement​ 对象,随后服务器调用该对象的 update() 方法。
element.py#from_dict
[img=720,358.07142857142856]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/05925688-fe80-4919-a67d-9b0e71c63a33.png[/img]

在创建过程中 from_dict()​ 方法会提取哀求中的全部字段包罗用户可控的 path​ 字段并通报给对象构造函数,此时恶意路径(如 /etc/passwd​)被完备生存到 CustomElement​ 对象的 path 属性中。
【----资助网安学习,以下全部学习资料免费领!加vx:YJ-2021-1,备注 “博客园” 获取!】
 ① 网安学习发展路径头脑导图
 ② 60+网安经典常用工具包
 ③ 100+SRC毛病分析陈诉
 ④ 150+网安攻防实战技能电子书
 ⑤ 最权势巨子CISSP 认证测验指南+题库
 ⑥ 超1800页CTF实战本事手册
 ⑦ 最新网安大厂口试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
element.py#CustomElement#update
[img=720,256.5]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/b8707a5a-b4dd-4d01-a99c-75b30856010f.png[/img]

调用CustomElement​的 update()​ 方法,该方法内部会调用父类 Element​ 的 send() 方法。
element.py#Element#send

起首 send()​ 方法调用 await self._create(persist=persist) 实验文件恒久化处理处罚
element.py#Element#create
[img=720,336.85714285714283]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/9d72770c-4f4d-4489-bbae-a41ba5ad6233.png[/img]

_create()​ 方法检测到对象存在 path​ 属性后会调用 session.persist_file() 函数
session.py#BaseSession#persist_file
[img=720,466.07142857142856]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/d33f33b5-7257-47af-bfe7-9971c2bb6b82.png[/img]

session.persist_file()​ 函数,该函数使用 aiofiles​ 异步读取攻击者指定路径的文件内容,将内容复制到会话专属的暂时目次中(如 /tmp/chainlit/{session_id}/{file_id}​),同时天生一个随机的文件标识符(UUID格式),并在会话的文件映射表(session.files)中创建该标识符与暂时文件路径的映射关系。
element.py#Element#send

成文件恒久化后 send()​ 方法实验第二个关键使用,调用 await context.emitter.send_element(self.to_dict()) 将元素信息发送到前端
element.py#Element#to_dict
[img=720,316.92857142857144]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/a97e4fca-d72b-471e-acee-22c489025103.png[/img]

to_dict()​ 方法负责将 CustomElement​ 对象转换为字典格式,该字典包罗对象的全部关键属性如 id​、type​、name​、display​ 以及最告急的 chainlitKey(即刚才得到的文件标识符)
emitter.py#send_element
[img=720,70.71428571428571]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/97e0f07b-f023-407d-909a-dab8ff69b7a4.png[/img]

转换后的字典通过 send_element()​ 方法通报给 emitter​ 的 emit 函数
该函数是在 WebSocket 毗连创建时注入到会话对象中的闭包函数,它调用 Socket.IO 的全局发送方法将包罗 chainlitKey​ 的元素字典通过 WebSocket 推送给客户端,攻击者通过监听 WebSocket 消息流捕捉变乱名为 element​ 的消息,从消息数据中提取 chainlitKey 字段的值即可得到文件标识符,至此完成第一步的路径注入、文件复制和标识符获取使用。
第二步是使用第一步得到的文件标识符访问 GET /project/file/{file_id}​ 接口来读取文件内容,服务器根据哀求中的 session_id​ 参数定位到对应的会话对象,从该会话的文件映射表中查找文件ID对应的暂时文件路径,由于权限查抄存在 if current_user:​ 的逻辑缺陷,未认证用户可以绕过权限验证,服务器直接使用 FileResponse 返回暂时文件的内容,而该暂时文件已经是目的敏感文件的完备副本,从而实现恣意文件读取,整个攻击过程无需任何身份认证,攻击者仅需创建一个匿名 WebSocket 毗连即可完成使用。
server.py#get_file
[img=720,361.92857142857144]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/2b9bd6bf-90ca-4b26-94a7-b95de5923a40.png[/img]

get_file()​ 函数通过 WebsocketSession.get_by_id()​ 方法根据 session_id​ 从全局会话字典中获取对应的会话对象,从该会话对象的 files​ 映射表中查找 file_id​ 对应的文件纪录,获取此中存储的暂时文件路径,末了使用 FileResponse() 直接返回该暂时文件的内容给客户端,由于暂时文件已经是目的敏感文件的完备副本,攻击者乐成得到恣意文件的内容。
毛病修复

现在官方已发布修复版本,发起用户尽快更新至 Chainlit 的修复版本或更高版本:Chainlit ≥ 2.9.4
官方在 2.9.4 版本中通过引入 _sanitize_custom_element()​ 输入清算函数修复了该毛病,该函数采取白名单机制重构了 update_thread_element()​ 和 delete_thread_element()​ 两个接口的元素处理处罚逻辑,在创建 CustomElement 对象时仅提取并验证 id、name、display、props 等正当字段,而将用户可控的 path 字段从输入参数中完全扫除,使得攻击者纵然在哀求中注入包罗路径遍历字符的恶意 path 值,该字段也会在对象构造阶段被自动过滤抛弃,无法通报到后续的文件使用流程中,从根本上阻断了通过 /project/element​ 接口注入恣意文件路径并通过 /project/file/{chainlitKey} 接口读取敏感文件的攻击链,有用防止了路径遍历毛病的使用。

[img=720,188.35714285714286]https://www.yijinglab.com/guide-img/4ff6a819-51d7-4fe0-ad1a-ee696ab51138/fb573ef4-62b8-43ea-a917-a817af459373.png[/img]


[img=720,404.53179190751445]https://pic1.imgdb.cn/item/69eefe02fc83efdcc4c08d72.png[/img]

更多网安技能的在线实训练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表