Apr 24, 2026·8 min read·5 visits
Kirby CMS <4.9.0 and 5.0.0-5.3.x are vulnerable to SSTI via double evaluation of dynamic option fields and a REST API authorization bypass. Attackers with low-privilege access can expose sensitive system data or publish unauthorized content.
Kirby CMS versions prior to 4.9.0 and 5.4.0 contain a critical double template resolution vulnerability leading to Server-Side Template Injection (SSTI). The software also suffers from an authorization bypass in the REST API, allowing authenticated users to circumvent editorial workflows and publish content without appropriate status-change permissions.
CVE-2026-34587 identifies two distinct but highly impactful vulnerabilities within Kirby CMS. The primary flaw is a Server-Side Template Injection (SSTI) classified under CWE-1336, originating from a double template resolution mechanism in the Options package. The secondary issue is an authorization bypass (CWE-285) in the REST API components governing page creation workflows. Both vulnerabilities require prior authentication but allow users to exceed their intended privilege boundaries.
The SSTI attack surface is concentrated in the rendering logic of dynamic option fields, specifically within OptionsQuery and OptionsApi. These components parse blueprint-defined templates to populate user interface elements such as select dropdowns, radio buttons, and checkboxes. When these fields utilize data sourced from user-controllable input, the templating engine processes the input twice. This mechanism allows malicious template syntax injected by low-privileged editors to execute when viewed by an administrative user.
The authorization bypass component fundamentally compromises Kirby's role-based access control (RBAC) architecture. Kirby explicitly separates pages.create permissions from pages.changeStatus permissions to enforce an editorial review process. The vulnerability exists within the REST API request handlers, which fail to validate the intended publication state during initial page creation.
Exploitation of these vulnerabilities requires specific environmental configurations. The SSTI requires the presence of dynamic blueprints that query user-modifiable fields. The authorization bypass requires the attacker to possess base page creation rights. While dependent on configuration, default or common blueprint patterns frequently satisfy these conditions, exposing instances to immediate risk.
The technical core of the SSTI vulnerability lies in the sequential evaluation logic applied to dynamic blueprint fields. When a field configuration specifies a query, such as query: site.children, the system initiates a first-pass resolution. This pass queries the database and fetches the relevant data model strings, such as the titles of existing pages. The resulting strings are assigned to the option field's text or value attributes.
The critical error occurs immediately following this retrieval phase. The system routes the newly populated, user-controllable strings back through the template parser for a second pass. The developers intended this pass to resolve variables within the blueprint definition itself. However, because the system fails to differentiate between trusted blueprint definitions and untrusted user input, any template syntax embedded in the retrieved strings is dynamically evaluated as active code.
A supplementary vector for query smuggling exists within the Xml::value() helper function. This function processes data intended for XML output. Prior to the patch, the function implemented an over-simplified check, returning data unescaped if the string began with the exact sequence <![CDATA[. This implementation failed to validate whether the CDATA block was properly closed, allowing attackers to append executable template tags immediately after a well-formed CDATA prefix.
The authorization bypass stems from incomplete state validation in PageRules::create. The method confirms the user holds the pages.create permission but inherently assumes the resulting page will default to a draft state, as enforced by the Panel UI. The REST API exposes the underlying model directly, accepting arbitrary JSON payloads. The method fails to check the isDraft flag during the initial creation sequence, meaning a POST request declaring isDraft: false circumvents the pages.changeStatus permission check entirely.
The vulnerable code path for the SSTI involved the unconditional processing of array structures returned by OptionsQuery and OptionsApi. In earlier versions, the Option class lacked a mechanism to signify that a string had already been resolved and should be treated as literal text. Consequently, any string containing double curly braces {{ }} triggered the template parser during final panel rendering.
The patch introduced in commit c11d9f2e5cb4facedcb58a0517a9efb24e0665e1 fundamentally alters this architecture by introducing a boolean $resolve property to the Option class. The rendering method was modified to check this property before executing the template parser:
// src/Option/Option.php
public function render(ModelWithContent $model, bool $safeMode = true): array {
return [
// ...
'info' => $info && $this->resolve === true ? $model->$method($info) : $info,
'text' => $text && $this->resolve === true ? $model->$method($text) : $text,
];
}By explicitly setting $resolve = false when generating options from untrusted data sources, the system safely bypasses the second evaluation phase, mitigating the SSTI.
The authorization bypass was remediated in commit a88ef33e81ed286d9dddf5a2f2d239aa73cd0d31. The fix modifies src/Cms/PageRules.php to explicitly enforce the publication rules during the creation phase if the client attempts to override the draft state.
// src/Cms/PageRules.php
if ($page->isDraft() === false) {
self::publish($page);
}This minimal but effective addition ensures the publish() rule constraints—which verify the pages.changeStatus permission—are invoked proactively during API-driven creation requests.
Further hardening was applied in commit d6b9a70779bf97d7a037c8e598115dd454cb0406 to address the CDATA smuggling mechanism. The Xml::value() method was rewritten to employ a strict regular expression validation: /]]>(?!<!\[CDATA\[|$)/. This prevents attackers from appending template tags outside of a verified, closed CDATA block.
Exploiting the SSTI vulnerability requires an attacker to possess authenticated access to the Kirby instance with permissions to modify content, such as a low-level editor role. The target instance must utilize a blueprint configuration that dynamically populates an options field via site.children or similar queries. The attacker modifies a data source, typically a page title, injecting a malicious Kirby query payload.
A primary SSTI payload targets the administrative credentials stored within the CMS database. The attacker sets the title of a page they control to the following Handlebars-style syntax:
{{ site.user('admin').password }}The execution is asynchronous. The payload lies dormant until a higher-privileged user, such as an administrator, views the panel interface containing the vulnerable dynamic options field. Upon rendering, the system performs the second resolution pass, executing the injected query. The administrator's panel UI subsequently displays the requested password hash or sensitive configuration data within the dropdown or checkbox list.
Exploiting the XML bypass requires the attacker to embed the malicious query behind a CDATA tag. If standard template injection is sanitized at the blueprint level but XML helpers are invoked, the attacker submits:
<![CDATA[test]]>{{ site.version }}The vulnerable Xml::value() helper returns the string intact. The secondary parsing stage then processes the appended {{ site.version }}, exposing the internal software version or executing arbitrary system queries.
The authorization bypass is executed via direct interaction with the Kirby REST API. The attacker constructs a standard HTTP POST request directed at the /api/pages endpoint. The request body includes the necessary parameters for page creation, but the attacker manually inserts the `
The exploitation of the SSTI vulnerability results in a severe breach of confidentiality. Attackers successfully leveraging this flaw can access any data point exposed through the Kirby query language. This includes administrative user hashes, active session tokens, application configuration variables, and potentially sensitive database records. The CVSS vector designates a High Confidentiality metric (VC:H) due to the complete exposure of the application's internal state.
The integrity of the system is similarly compromised. Because Kirby queries can invoke mutator functions, an attacker can use the template injection to force administrative accounts into executing state-changing actions upon viewing the infected UI element. The CVSS Integrity metric (VI:H) reflects this capability to corrupt data or alter configurations through forced administrative execution.
The authorization bypass inflicts direct operational damage by subverting established editorial and compliance workflows. Attackers with restricted creation roles can instantly publish arbitrary, potentially malicious or reputation-damaging content directly to the live production site. This bypasses all secondary review mechanisms, rendering the separation of duties in the RBAC model ineffective.
While the combined impact is critical, the vulnerability does not directly yield arbitrary remote code execution (RCE) on the underlying operating system. The evaluation is strictly constrained to the Kirby templating sandbox. Complete system compromise is restricted unless the attacker chains the exposed administrative hashes with external credential reuse attacks or further post-authentication exploits within the Kirby administrative panel.
Administrators must immediately update Kirby CMS installations to the patched releases. The official fixed versions are 4.9.0 for the 4.x release line and 5.4.0 for the 5.x release line. Applying these updates resolves the architectural flaw in the Options class and enforces status validation within the API request handlers. No further code modifications are required post-update.
In environments where immediate upgrading is technically unfeasible, administrators must conduct a thorough audit of all custom blueprints. Specifically, developers must identify any select, radio, or checkboxes fields utilizing query: or api: dynamic population methods. Temporarily hardcoding these options or restricting edit access to the underlying data sources will mitigate the SSTI vector until the patch is applied.
To mitigate the REST API authorization bypass in unpatched environments, security teams must review current user role assignments. Any role lacking the pages.changeStatus permission should ideally have its pages.create permission revoked if strict editorial control is a business requirement. Alternatively, placing the API endpoints behind a Web Application Firewall (WAF) configured to drop requests containing "isDraft": false from unauthorized IP ranges provides a temporary network-level defense.
The vendor patches demonstrate a comprehensive resolution to the reported issues. The introduction of the $resolve boolean explicitly prevents the dual-evaluation path, eliminating the core logic flaw. Furthermore, the newly implemented validateAreaAccess('system') checks ensure defense-in-depth, strictly gating sensitive internal variables from unauthorized querying even if future template injection variants emerge.
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
kirby getkirby | < 4.9.0 | 4.9.0 |
kirby getkirby | >= 5.0.0, < 5.4.0 | 5.4.0 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Server-Side Template Injection & Auth Bypass |
| CWE ID | CWE-1336, CWE-285 |
| CVSS Score | 7.6 (High) |
| Attack Vector | Network |
| Privileges Required | Low |
| Exploit Status | PoC Available |
The software uses a template engine to render or process data, but it does not properly neutralize special elements, allowing an attacker to inject template directives.