Mar 25, 2026·5 min read·45 visits
Scriban versions prior to 7.0.0 fail to cumulatively track string allocations across template loops. Attackers can leverage this bypass to allocate gigabytes of memory using default settings, crashing the host .NET application.
The Scriban template engine for .NET contains a flaw in its memory allocation limiting logic. An attacker who can supply malicious templates can bypass the `LimitToString` safety mechanism, causing the engine to allocate excessive memory. This leads to an Out-of-Memory (OOM) condition and subsequent application crash, resulting in a Denial of Service (DoS).
Scriban is a popular text templating engine and parser designed for the .NET framework. It is frequently integrated into applications that require dynamic content generation, such as email templating services, static site generators, and web applications. When processing untrusted user input, templating engines must strictly control resource consumption to prevent abuse.
Vulnerability GHSA-M2P3-HWV5-XPQW identifies a flaw in Scriban's resource limitation controls, specifically categorized under CWE-770 (Allocation of Resources Without Limits or Throttling). The vulnerability allows an attacker to construct a specialized template that systematically bypasses the engine's built-in string allocation limits. This bypass results in unbounded memory consumption during the template evaluation and rendering phases.
All versions of the Scriban NuGet package prior to version 7.0.0 are vulnerable to this issue. Exploitation requires the target application to accept and evaluate templates provided by an attacker. A successful attack consumes the host system's available memory, predictably resulting in a Denial of Service (DoS) for the entire application environment.
The Scriban engine implements a safety setting named LimitToString to prevent excessive memory allocation during string conversion and generation. Since commit b5ac4bf, this limit has defaulted to 1,048,576 bytes (1MB). The vulnerability exists because this specific safety check was enforced on a per-expression basis rather than cumulatively across the entire template execution lifecycle.
Internally, the engine tracks the size of the current string being constructed using the _currentToStringLength variable. When a top-level string conversion operation completes, the engine evaluates the condition _objectToStringLevel == 0. When this condition evaluates to true, the engine resets the _currentToStringLength counter back to zero, erasing the record of previous allocations.
This counter reset behavior introduces a logical flaw when combined with template loop constructs. An attacker can define a loop where each individual iteration generates a distinct string that remains just under the 1MB threshold. Because the tracking counter resets after each expression evaluates, the engine never triggers the LimitToString violation exception, allowing aggregate memory allocations to grow unboundedly.
Exploiting this vulnerability relies on combining the per-expression limit bypass with Scriban's default loop iteration limits. By default, Scriban enforces a LoopLimit of 1,000 iterations to prevent infinite loops. An attacker can construct a payload that maximizes memory allocation within these default boundaries without triggering any exceptions.
The following Proof of Concept (PoC) demonstrates the attack using standard TemplateContext settings. The payload instructs the engine to iterate 1,000 times. During each iteration, the engine performs string multiplication to generate a string of 1,048,575 characters (assuming single-byte characters, this equals 1,048,575 bytes).
using Scriban;
// This payload relies on default TemplateContext settings:
// LoopLimit = 1000
// LimitToString = 1,048,576 (1MB)
var template = Template.Parse(@"{{ for i in 1..1000 }}{{ 'a' * 1048575 }}{{ end }}");
// Each iteration generates a string of 1,048,575 bytes.
// 1,048,575 < 1,048,576 (passes the LimitToString check).
// Total allocation = 1,000 * 1,048,575 bytes ≈ 1GB.
template.Render(); Because 1,048,575 bytes is strictly less than the 1,048,576-byte LimitToString threshold, the per-expression check passes. The engine then resets the tracking counter. Across 1,000 iterations, the application allocates approximately 1 gigabyte of memory in rapid succession. In environments with concurrency, multiple identical requests will quickly exhaust system memory.
The primary impact of this vulnerability is a high-severity Denial of Service (DoS) affecting the availability of the host application. In the .NET runtime environment, an unhandled OutOfMemoryException typically cannot be caught and safely recovered from using standard exception handling mechanisms. When the runtime fails to allocate requested memory, it terminates the hosting process entirely.
Process termination affects all users currently interacting with the application, not just the attacker executing the malicious payload. Any active transactions, background processing tasks, or in-memory state will be lost when the process terminates. If the application runs within a containerized environment (such as Docker or Kubernetes) with strict memory quotas, the orchestrator will forcibly kill the container via an OOMKilled signal.
Depending on the application architecture, the required privileges for exploitation range from Low to None. If the application exposes template generation features to unauthenticated users over a network, an attacker can trigger the OOM condition remotely with a single HTTP request. There is no impact on confidentiality or integrity, as the vulnerability does not expose sensitive data or allow arbitrary code execution.
The vendor addressed this vulnerability in Scriban version 7.0.0. The patch modifies the memory tracking logic to enforce cumulative string allocation limits across the entire template execution context, rather than on a per-expression basis. Developers must upgrade all vulnerable Scriban package references to version 7.0.0 or later to ensure complete protection.
For environments where immediate patching is not technically feasible, developers can implement manual mitigations by explicitly restricting the TemplateContext settings. Administrators should instantiate the TemplateContext and explicitly set both LimitToString and LoopLimit to highly restrictive values tailored to the specific operational requirements of the application. This reduces the maximum possible memory allocation a single template execution can trigger.
As a defense-in-depth measure, security teams should implement resource quotas at the infrastructure level. Configuring memory limits via cgroups in Linux, container memory limits in Docker, or App Service plans in Azure ensures that an OOM condition within the application process does not compromise the underlying host or other co-located services.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Scriban Scriban | < 7.0.0 | 7.0.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-770 |
| Attack Vector | Network |
| Impact | Denial of Service (DoS) |
| CVSS v3.1 | 6.5 (Moderate) |
| Exploit Status | PoC Available |
| Privileges Required | Low / None |
Allocation of Resources Without Limits or Throttling
A high-severity access control vulnerability in ToolHive CLI before v0.30.1 and ToolHive Studio before v0.38.0 allows local containerized MCP servers to bypass network isolation. This enables malicious workloads to establish TCP/IP connections to administrative and control plane endpoints exposed on the host loopback interface.
AnyCable is a real-time communication server. Prior to version 1.6.15, its Pusher-compatible REST API suffered from an authentication bypass vulnerability because it failed to verify that the request body matched the signature-validated body_md5 parameter. This allows attackers to perform replay attacks with modified body contents.
A denial-of-service vulnerability exists in AnyIO prior to version 4.14.2. Standard error streams of process-pool workers are connected to an operating system pipe that is never drained by the parent process. This allows a worker to fill the pipe buffer and deadlock indefinitely.
CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.
CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.
CVE-2026-84992 is a Cross-Site Scripting (XSS) vulnerability affecting md-editor-v3 before version 6.5.4. It occurs because the fenced-code block language parser directly interpolates unescaped language metadata into unquoted HTML attributes inside the custom rendering callback. This bypasses the built-in XSSPlugin which runs during the parsing phase, before rendering.