Mar 25, 2026·5 min read·3 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