Mar 25, 2026·6 min read·3 visits
Scriban < 7.0.0 allows unauthenticated attackers to crash the host application via a StackOverflowException by piping deeply nested or self-referencing objects to the `object.to_json` function. This results in a complete Denial of Service. The issue is fixed in version 7.0.0.
Scriban versions prior to 7.0.0 suffer from an uncontrolled recursion vulnerability within the `object.to_json` built-in function. By passing a specially crafted self-referencing or deeply nested object to this function, an attacker can trigger an infinite recursive loop. This exhausts the execution stack, resulting in an uncatchable StackOverflowException that immediately terminates the hosting .NET process.
Scriban is a widely used scripting engine and template parsing library for .NET applications. It provides built-in functions for data manipulation, including object.to_json, which serializes internal objects into JSON string representations. This function allows template authors to easily output variables and internal data structures to the rendered view.
A critical vulnerability exists within the implementation of this JSON serialization function in versions prior to 7.0.0. The engine fails to implement proper recursion depth controls or circular reference tracking when traversing object properties. When processing complex objects, the parser blindly follows nested properties without validating the traversal depth against the system's execution capabilities.
Consequently, an attacker who can supply arbitrary template content can force the engine into an infinite recursion loop. This results in a StackOverflowException that immediately terminates the hosting .NET process, leading to a complete Denial of Service (DoS). The vulnerability operates over the network with low complexity and zero required privileges, assuming the target application exposes template compilation to unauthenticated users.
The root cause of this vulnerability lies in the WriteValue internal static local function, located within src/Scriban/Functions/ObjectFunctions.cs. This function is responsible for recursively traversing object properties and serializing them into a JSON output stream. It is invoked whenever a template pipes an internal object to the object.to_json built-in function.
During standard execution, Scriban employs a centralized mechanism to prevent unbounded recursion using configurable properties like ObjectRecursionLimit. However, the WriteValue function operates independently of these global safety mechanisms. It accepts a TemplateContext object strictly for member access operations but neglects to invoke the engine's EnterRecursive() or CheckAbort() safeguards.
Furthermore, the implementation lacks circular reference detection. When the function processes an object that contains a reference to itself, it continuously re-evaluates the same memory structure. The absence of a recursion depth counter or calls to RuntimeHelpers.EnsureSufficientExecutionStack() ensures the runtime cannot gracefully halt execution before the underlying thread's stack space is entirely consumed.
The vulnerable implementation of WriteValue strictly handles object traversal without maintaining state regarding the traversal depth. The code iterates over complex object members and recursively calls itself for each nested value, lacking any terminating condition for cyclic graphs.
The remediation strategy introduces two critical control flow changes within the WriteValue function. First, it implements an explicit depth parameter that increments upon each recursive call, checking it against the context's defined recursion limit. Second, it integrates explicit execution stack validation.
// Patched implementation excerpt (v7.0.0)
static void WriteValue(TemplateContext context, Utf8JsonWriter writer, object value, int depth = 0)
{
if (context.ObjectRecursionLimit != 0 && depth > context.ObjectRecursionLimit)
{
throw new ScriptRuntimeException(context.CurrentSpan, "Recursion limit exceeded.");
}
RuntimeHelpers.EnsureSufficientExecutionStack();
// ... complex object serialization logic ...
WriteValue(context, writer, memberValue, depth + 1);
}This approach guarantees that either the logical depth limit is reached, throwing a catchable ScriptRuntimeException, or the runtime stack check proactively throws an InsufficientExecutionStackException before a fatal crash occurs. The EnsureSufficientExecutionStack method checks the remaining stack space and allows the .NET runtime to safely abort the operation without corrupting the host process.
Exploitation requires the ability to supply or modify a Scriban template executed by the target application. No authentication is necessary if the application exposes template rendering to unauthenticated users, such as in web-to-print services, dynamic email generators, or customizable CMS themes.
The attack relies on constructing an object literal with a self-referencing property, followed by passing this object to the object.to_json pipeline. A single line of template code achieves this state and initiates the recursive loop.
{{ x = {}; x.self = x; x | object.to_json }}Alternatively, an attacker can bypass cyclic tracking entirely by constructing a sufficiently deep nested object structure. The serialization function processes each layer, consuming a stack frame for every nested level, and eventually exhausts the call stack prior to completing the request.
{{ a = {}; b = {inner: a}; c = {inner: b}; deepest | object.to_json }}Both vectors produce the identical unrecoverable process crash. The exploit succeeds consistently regardless of the underlying operating system or system architecture, relying entirely on the .NET runtime's fixed stack allocation limits.
The security impact of this vulnerability is a severe Denial of Service. In .NET runtimes, a StackOverflowException is categorized as a fatal, unrecoverable error. It cannot be intercepted or handled using standard try/catch blocks within application code.
When the vulnerability is triggered, the entire hosting process terminates immediately. This affects the application domain executing the Scriban template, bringing down IIS application pools, Kestrel web servers, or background worker services that host the engine. All active connections are dropped, and all concurrent processing halts.
The attack vector operates over the network with low complexity and zero required privileges. Because the process termination halts the entire service, the availability impact is complete. Service restoration mandates an automated restart policy or manual administrative intervention. In environments without robust high-availability configurations, a single request removes the service from operation.
The primary remediation for this vulnerability is upgrading the Scriban NuGet package to version 7.0.0 or later. This release introduces the necessary depth tracking and stack validation logic to gracefully handle deeply nested or cyclic objects.
If immediate upgrading is structurally infeasible, developers must implement a temporary workaround by modifying the execution environment. The object.to_json function can be explicitly unregistered from the BuiltinFunctions context before template execution begins. This prevents the parser from routing untrusted input to the vulnerable serialization logic.
Security teams should also audit application architectures for untrusted template inputs. Running template engines in isolated, resource-constrained execution environments, such as dedicated microservices or ephemeral containers, limits the blast radius of unrecoverable process crashes. Ensuring the host architecture implements automatic restart policies mitigates the duration of the outage.
CVSS:3.1/AV:N/AC:L/PR:N/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 |
|---|---|
| Vulnerability Class | Uncontrolled Recursion (CWE-674) |
| Affected Component | object.to_json (Scriban.Functions.ObjectFunctions) |
| CVSS Score | 7.5 (High) |
| Impact | Denial of Service (Unrecoverable Process Crash) |
| Attack Vector | Network / Arbitrary Template Input |
| Exploitation Status | Proof-of-Concept Available |
| Authentication Required | None |
The product does not properly control the amount of recursion that takes place, consuming excessive resources, such as allocated memory or the program stack.