high · 8.4Aug 20, 2026

Winter CMS: Authenticated Twig Sandbox Escape via Eloquent __call Forwarding (Bypass of CVE-2024-54149)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Authenticated backend users of Winter CMS with template-editing permissions can escape the Twig sandbox by calling Eloquent model methods that the blocklist missed, allowing arbitrary SQL execution…

Packagewinter/wn-system-module
Ecosystemcomposer
Affected>= 1.2.7, < 1.2.13
Fixed in1.2.13
Winter CMS: Authenticated Twig Sandbox Escape via Eloquent __call Forwarding (Bypass of CVE-2024-54149)

The problem

Winter CMS runs CMS templates inside a Twig sandbox ('safe mode') backed by System\Twig\SecurityPolicy. The fix for CVE-2024-54149 added a flat blocklist of dangerous method names to that policy.

The blocklist checked method names directly on the object being called, but Eloquent models forward unknown calls through __call to the underlying query builder. A method blocked on the builder was not blocked when reached via a model, Eloquent builder proxy, or relation object.

This let an attacker reach write-capable builder methods (saveQuietly, deleteQuietly, increment, decrement, newQuery, getConnection, getConnectionResolver, pivot and relation helpers, and higher-order collection callables) entirely unchecked by the sandbox.

Proof of concept

A working proof-of-concept for this issue in winter/wn-system-module, with the exact payload below.

javascript
{{- /* In a CMS page, layout, or partial with safe mode ON */ -}}
{% set users = 'Winter\\Backend\\Models\\User'|class_resolve %}
{% set record = users.first() %}
{# saveQuietly() skips events and is not in the flat blocklist #}
{{ record.saveQuietly() }}

{# Or: reach the raw connection through newQuery() -> getConnection() #}
{% set qb = record.newQuery() %}
{% set conn = qb.getConnection() %}
{{ conn.statement('DROP TABLE backend_users') }}

The root cause is CWE-693 (Protection Mechanism Failure): the blocklist was a flat name-based check that did not model PHP's runtime dispatch chain. Eloquent's __call magic method transparently forwards unrecognised method calls from a Model to its Builder, and from a Builder to the underlying Connection.

Because saveQuietly, deleteQuietly, newQuery, getConnection, and similar methods were never added to the flat blocklist, the sandbox passed them through without complaint.

The patch (commit 725bbcda) replaced the flat blocklist with a transitive forwarder-aware check: a method blocked on the query builder is now also blocked when it arrives via __call through a Model, Eloquent Builder, or relation object. The database connection and connection resolver are locked down entirely, source() and constant() Twig functions are restricted, and a new SafeCollection/SafePaginator wrapper neutralises callable arguments passed to higher-order collection methods.

The fix

Upgrade winter/wn-system-module to v1.2.13 or later. After upgrading, clear the compiled Twig template cache (php artisan cache:clear) so existing templates recompile under the updated policy. If an immediate upgrade is not possible, manually apply commit 725bbcda232466f7f71381c271c6916573d576e6 and restrict cms.manage_pages, cms.manage_layouts, and cms.manage_partials permissions to fully trusted administrators only.

Reported by Mounir Elsrogy (@M9nx).

References: [1][2][3][4][5]

Related research