Mar 25, 2026·7 min read·4 visits
Scriban versions before 7.0.0 are vulnerable to Denial of Service via unbounded resource consumption. Unauthenticated or low-privileged users can crash the host application by providing crafted template expressions that bypass configured string length and loop limits.
The Scriban .NET templating engine versions prior to 7.0.0 contain three distinct denial-of-service vulnerabilities. These flaws arise from improper enforcement of resource limits during expression evaluation, specifically concerning string multiplication, bitwise shifts, and range enumerations. An attacker with template authoring privileges can exploit these vectors to trigger OutOfMemoryException or CPU exhaustion, resulting in abrupt application termination or degraded performance.
Scriban is a fast, powerful, and safe templating language and engine for .NET, frequently utilized in content management systems, static site generators, and email marketing platforms. The engine exposes a configuration object, TemplateContext, which administrators use to enforce execution limits. These boundaries include LimitToString for string allocations and LoopLimit for maximum iteration counts.
Versions of Scriban prior to 7.0.0 fail to enforce these safety controls across three specific expression evaluation paths. The application trusts the constraints established in TemplateContext but contains implementation flaws that allow certain operations to bypass these checks entirely. This results in unrestricted resource allocation and processing.
An attacker who can supply or modify template content can exploit these paths to cause a Denial of Service (DoS). The vulnerabilities are classified under CWE-400 (Uncontrolled Resource Consumption), CWE-770 (Allocation of Resources Without Limits or Throttling), and CWE-834 (Excessive Iteration). The impact varies depending on the targeted vector but culminates in either process termination or complete thread pool exhaustion.
The attack vector requires no elevated privileges beyond the ability to submit template data. Applications that parse user-provided strings via Template.Parse() without pre-processing validation are inherently vulnerable. The severity is constrained to a 6.5 Medium rating due to the requirement of application-specific template input mechanisms and the lack of confidentiality or integrity impacts.
The first vulnerability vector exists within ScriptBinaryExpression.cs handling the string multiplication operator (string * int). The CalculateToString method attempts to replicate the input string a specified number of times using a standard for loop and a StringBuilder. The implementation lacks a pre-allocation check against context.LimitToString, allowing the StringBuilder to grow until the .NET runtime throws an OutOfMemoryException.
The second vector involves the ShiftLeft operator (<<), implemented in CalculateLongWithInt and CalculateBigIntegerNoFit. Unlike the exponentiation operator (Power), which uses BigInteger.ModPow and enforces a MaxBigInteger boundary, the left-shift operation applies no such limits. Shifting a BigInteger by an extremely large integer value forces the .NET runtime to allocate a backing array large enough to hold the shifted bits, again leading to an immediate OutOfMemoryException.
The third vector exploits range operators (.. and ..<) used in conjunction with built-in array functions. Range operators in Scriban return lazy IEnumerable<object> iterators. When these ranges are passed directly to functions like array.size or array.join, the underlying implementation fully enumerates the iterator. Because this enumeration happens inside the native function rather than the template engine's main execution loop, it never invokes context.StepLoop(). Consequently, the enumeration entirely bypasses the context.LoopLimit protection, causing CPU exhaustion.
The flaw in the string multiplication implementation is located between lines 319 and 334 in src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs. The vulnerable code executes a loop based strictly on user-supplied values without boundary verification.
var leftText = context.ObjectToString(left);
var builder = new StringBuilder();
for (int i = 0; i < value; i++)
{
builder.Append(leftText);
}
return builder.ToString();The fundamental issue here is that context.LimitToString (which defaults to 1MB) is ignored. The patch resolves this by introducing a mathematical boundary check prior to the loop execution. By calculating the expected final size (long)value * leftText.Length, the engine can throw a controlled ScriptRuntimeException before any substantial memory allocation occurs.
// Patched logic
if (context.LimitToString > 0 && (long)value * leftText.Length > context.LimitToString)
{
throw new ScriptRuntimeException(span, "String multiplication would exceed LimitToString");
}The fix for the loop limit bypass requires the engine to track iteration counts within the built-in functions that consume IEnumerable<object>. The patched RangeInclude and RangeExclude structures are updated to increment and verify against context.LoopLimit during every iteration step. This ensures that a range calculation like (0..1000000000) halts after reaching the threshold.
Exploitation relies on passing a single crafted expression into the Template.Parse() method and subsequently calling template.Render(context). The attacker does not need to understand the underlying architecture, only that Scriban is processing the input.
To trigger the string multiplication Out of Memory (OOM) condition, the attacker supplies a template that multiplies a small string by an exceptionally large integer. The code below demonstrates this attack. The default LimitToString configuration of 1,048,576 bytes fails to prevent the approximately 2GB allocation attempt.
var template = Template.Parse("{{ 'AAAA' * 500000000 }}");
var context = new TemplateContext();
template.Render(context); // Throws OutOfMemoryExceptionFor the bitwise shift OOM, the attacker leverages the << operator. The resulting BigInteger allocation bypasses normal numerical limits. The following snippet forces the application to allocate a BigInteger containing 100 million bits (approximately 12.5MB per evaluation, easily scaled by the attacker to exhaust available memory).
var template = Template.Parse("{{ 1 << 100000000 }}");
var context = new TemplateContext();
template.Render(context); // Throws OutOfMemoryExceptionThe CPU exhaustion vector targets the loop limit bypass. By piping an enormous range into the array.size built-in function, the application attempts to enumerate 1 billion items synchronously. This blocks the executing thread indefinitely, starving the application's thread pool and resulting in a denial of service.
var template = Template.Parse("{{ (0..1000000000) | array.size }}");
var context = new TemplateContext();
template.Render(context); // High CPU utilization, hangs executionThe primary consequence of this vulnerability is a complete Denial of Service. In the context of .NET runtime environments, an OutOfMemoryException is typically a fatal error. Modern .NET runtimes generally terminate the process entirely when an OOM exception is thrown by the execution engine, rather than allowing the application to catch and recover from the error.
Process termination affects all users currently interacting with the host application. If the application handles web requests, all active connections are dropped. In environments orchestrated by container managers (e.g., Kubernetes), this leads to continuous pod restarts (CrashLoopBackOff) if the attacker automates the malicious request.
The CPU exhaustion vector introduces a secondary impact model. While it may not crash the process immediately, it ties up application threads. If an attacker submits multiple requests utilizing the loop bypass vector, the .NET thread pool becomes starved. Subsequent legitimate requests will queue and eventually time out, effectively rendering the application unresponsive.
The definitive mitigation for this vulnerability is upgrading the Scriban NuGet package to version 7.0.0 or later. This release introduces comprehensive boundary checks in the abstract syntax tree evaluation logic, explicitly capping string operations, bitwise shifts, and range iterators to the limits defined in TemplateContext.
If immediate patching is not technically feasible, development teams must implement input validation workarounds. The most robust temporary measure involves restricting template authoring capabilities strictly to highly trusted administrators. In multi-tenant environments where user-supplied templates are a core feature, restricting access may not be an option.
In cases where untrusted input must be processed by vulnerable versions, implement a strict pre-compilation regex filter. The filter should scan the template string and reject inputs containing multiplication operators (*), left shift operators (<<), and range operators (.. and ..<). This degrades the functionality of the templating engine but neutralizes the known attack vectors.
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 Alexandre Mutel | >= 0.1.0, < 7.0.0 | 7.0.0 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Denial of Service (DoS) |
| CWE ID | CWE-400, CWE-770, CWE-834 |
| CVSS v3.1 Score | 6.5 Medium |
| Attack Vector | Network (Remote) |
| Privileges Required | Low |
| User Interaction | None |
| Exploit Status | Proof of Concept available |
The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.