
CVE-2025-23211 के लिए प्रूफ-ऑफ-कॉन्सेप्ट; अधिकृत सुरक्षा सत्यापन, पैच परीक्षण और शमन सत्यापन को समर्थन देने के लिए डेटा-गोपनीयता संबंधी भेद्यता को पुनरुत्पादित करता है।
एक स्व-निहित, पुनरुत्पादन योग्य वातावरण जो CVE-2025-23211 को प्रदर्शित करता है, जो Tandoor Recipes में एक सर्वर-साइड टेम्पलेट इंजेक्शन (SSTI) भेद्यता है जो रिमोट कोड निष्पादन की ओर ले जाती है। रिपॉज़िटरी भेद्य संस्करण को खड़ा करती है, एक्सप्लॉइट दिखाती है, फिर पैच किए गए संस्करण को खड़ा करती है और वही इनपुट अस्वीकृत होते हुए दिखाती है।
| CVE | CVE-2025-23211 |
| Advisory | GHSA-r6rj-h75w-vj8v |
| Class | Server-Side Template Injection (Jinja2), CWE-94 / CWE-1336 |
| CVSS 3.1 | 9.9 (Critical) - AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
| Affected | Tandoor Recipes <= 1.5.23 |
| Fixed in | 1.5.24 |
Tandoor Recipes एक ओपन-सोर्स, स्व-होस्टेड भोजन-योजना और रेसिपी-प्रबंधन एप्लिकेशन है। बैकएंड Django (Python) है जो Gunicorn द्वारा परोसा जाता है, फ्रंटएंड एक Vue.js सिंगल-पेज एप्लिकेशन है, और डेटा PostgreSQL में संग्रहीत होता है।
Tandoor उपयोगकर्ताओं को एक रेसिपी के निर्देश पाठ के भीतर Jinja2 टेम्पलेट अभिव्यक्तियाँ लिखने की अनुमति देता है, यह सुविधा सामग्री मात्राओं को स्केल करने जैसे कार्यों के लिए है। वे निर्देश सर्वर पर रेंडर किए जाते हैं।
निर्देश पाठ को cookbook/helper/template_helper.py में render_instructions() द्वारा संसाधित किया जाता है। प्रभावित संस्करणों में पाठ को Jinja2 के डिफ़ॉल्ट Template() को पास किया जाता है और बिना सैंडबॉक्स के रेंडर किया जाता है:
template = Template(instructions)
instructions = template.render(ingredients=ingredients, scale=scale)
चूँकि डिफ़ॉल्ट Jinja2 वातावरण Python के ऑब्जेक्ट ग्राफ़ को उजागर करता है, एक प्रमाणित उपयोगकर्ता जो रेसिपी संपादित कर सकता है, एक टेम्पलेट अभिव्यक्ति इंजेक्ट कर सकता है जो एक खाली टपल से subprocess.Popen तक पहुँचती है और सर्वर पर एक मनमाना कमांड निष्पादित करती है। डिफ़ॉल्ट कंटेनर root के रूप में चलता है, इसलिए कमांड root के रूप में चलता है।
निर्देश Jinja2 द्वारा रेंडर होने से पहले एक Markdown चरण से गुज़रते हैं, और वह चरण कच्चे अंडरस्कोर को हटा देता है। इसलिए एक्सप्लॉइट अंडरस्कोर को हेक्स-एन्कोड करता है (\x5f\x5f) और डॉट नोटेशन के बजाय attr() फ़िल्टर का उपयोग करता है। payloads.txt देखें।
compose-vulnerable.yml Tandoor 1.5.23 (vulnerable) plus PostgreSQL
compose-patched.yml Tandoor 1.5.24 (patched) plus PostgreSQL
payloads.txt The two proof-of-concept payloads and expected output
enumerate-popen.txt Helper for finding the subprocess.Popen index on other images
इस रिपॉज़िटरी में साक्ष्य नीचे दिए गए सेटअप पर कैप्चर किया गया था। Compose v2 या बाद के संस्करण वाला कोई भी हालिया Docker रिलीज़ वही परिणाम पुनरुत्पादित करता है।
भेद्य स्टैक शुरू करें:
docker compose -f compose-vulnerable.yml up -d
तब तक प्रतीक्षा करें जब तक वेब कंटेनर लॉग में एक वर्कर बूट होता हुआ न दिखे, फिर http://localhost:8080 खोलें।
पहला खाता पंजीकृत करें। पहला उपयोगकर्ता सुपरयूज़र बन जाता है। संकेत मिलने पर एक Space नाम सेट करें।
एक नई रेसिपी बनाएँ। Steps > Instructions फ़ील्ड में, payloads.txt से Payload 1 पेस्ट करें, फिर सहेजें।
देखने के लिए रेसिपी खोलें। रेंडर किए गए निर्देश <class 'subprocess.Popen'> दिखाते हैं, जो पुष्टि करता है कि इंजेक्शन Python आंतरिक तत्वों तक पहुँचता है।
Payload 2 के साथ दोहराएँ। अब रेसिपी देखने पर whoami चलता है और root प्रदर्शित करता है।
प्रक्रिया उपयोगकर्ता की सीधे पुष्टि करें:
docker compose -f compose-vulnerable.yml exec web_recipes whoami
भेद्य स्टैक रोकें:
docker compose -f compose-vulnerable.yml down
पैच किया गया स्टैक शुरू करें:
docker compose -f compose-patched.yml up -d
उन्हीं payloads के साथ वही चरण दोहराएँ। निष्पादित करने के बजाय, रेसिपी दृश्य अब Could not parse template code. Error: Undefined Error दिखाता है।
सब कुछ रीसेट करने और एक खाली डेटाबेस से शुरू करने के लिए, docker compose -f compose-vulnerable.yml down -v चलाएँ। -v फ़्लैग नामित वॉल्यूम हटा देता है, इसलिए अगला रन डेटाबेस को शुरू से फिर से बनाता है।
पैच (commit e6087d5) बिना सैंडबॉक्स वाले Template() को Jinja2 के SandboxedEnvironment से बदल देता है:
env = SandboxedEnvironment()
instructions = env.from_string(instructions).render(ingredients=ingredients, scale=scale)
सैंडबॉक्स Jinja2 के एट्रिब्यूट लुकअप को ओवरराइड करता है ताकि __class__, __base__, __subclasses__, और __getitem__ जैसे असुरक्षित एट्रिब्यूट्स तक पहुँच प्रतिबंधित हो। उनके बिना, payload के पास एक साधारण ऑब्जेक्ट से subprocess.Popen तक कोई रास्ता नहीं होता, इसलिए अभिव्यक्ति निष्पादित होने के बजाय विफल हो जाती है।
एक्सप्लॉइट डॉट नोटेशन के बजाय attr() फ़िल्टर के माध्यम से एट्रिब्यूट्स तक पहुँचता है, इसलिए पैच किए गए कोड में अवरुद्ध पहुँच SecurityError के बजाय UndefinedError के रूप में सामने आती है; दोनों को सैंडबॉक्स्ड संस्करण द्वारा संभाला जाता है।
यह रिपॉज़िटरी शैक्षिक उद्देश्यों के लिए एक सार्वजनिक रूप से प्रकट और पहले से पैच की गई भेद्यता का पुनरुत्पादन करती है। इसे केवल यहाँ प्रदान किए गए स्थानीय कंटेनरों के विरुद्ध चलाएँ। उन सिस्टमों को लक्षित न करें जो आपके स्वामित्व में नहीं हैं या जिनका परीक्षण करने के लिए आप अधिकृत नहीं हैं।