Listen to this Post
The `array integer` operator in Scriban allocates a new array whose size is the product of the attacker‑controlled integer and the length of the source array. The implementation in `ScriptArrayLoopLimit, LimitToString, or calls context.StepLoop(...). As a result, a template as small as ~40 bytes – e.g. `{{ x = [1,2,3,4,5] 200000000; x.size }}` – forces a multi‑gigabyte allocation, leading to a denial‑of‑service.
This vulnerability is the unguarded sibling of several operations that were previously hardened against the same class of abuse:
– `string integer` is protected by a `LimitToString` pre‑check.
– `array.insert_at` is gated by StepLoop/LoopLimit (the fix for GHSA‑24c8‑4792‑22hx, scored 8.7 High, shipped in 7.2.0).
– Range/iteration paths are covered by GHSA‑c875‑h985‑hvrc (“Built‑in operations bypass LoopLimit“, fixed in 7.0.0).
The same LoopLimit‑based hardening pattern was applied to those operations but never to array integer. This can be observed directly in 7.0.0, the release where GHSA‑c875 was patched: `(1..5) 50000000` correctly throws Exceeding number of iteration limit '1000', while `[1,2,3,4,5] 50000000` allocates ~2 GB with no limit. The bug has been present since the operator was introduced in 3.0.0, survives all DoS‑hardening passes up to 7.2.0 (current), and is therefore both a missed sibling of GHSA‑24c8 and an incomplete coverage of GHSA‑c875’s `LoopLimit` hardening.
The root cause lies in `ScriptArray.cs` lines 504‑508 (Multiply case):
var newArray = new ScriptArray<T>(intModifier array.Count);
for (int i = 0; i < intModifier; i++)
{
newArray.AddRange(array);
}
`intModifier` comes directly from `context.ToInt(…)` (line 399). Two problems exist:
1. No resource limit – neither the constructor nor the `AddRange` loop checks LoopLimit, LimitToString, or calls StepLoop. A template requesting 250,000,000 elements creates them all without any “iteration limit” error.
2. Integer overflow in capacity – `intModifier array.Count` uses unchecked `int` arithmetic; the overflow‑safe `long` cast used by the string sibling is absent.
When the oversized allocation fails, the binary‑expression evaluator wraps the exception:
catch (Exception ex) when (!(ex is ScriptRuntimeException))
{
throw new ScriptRuntimeException(span, ex.Message);
}
Thus a host that wraps `Render()` in `try/catch` sees a `ScriptRuntimeException` carrying the original `OutOfMemoryException` message (or `ArgumentOutOfRangeException` on the overflow path).
A minimal PoC (console project targeting net8.0/net9.0 with Scriban 7.2.0) reproduces the issue:
using Scriban;
string tpl = "{{ x = [1,2,3,4,5] 200000000; x.size }}";
var result = Template.Parse(tpl).Render();
Measured peak working set on Scriban 7.2.0 (net8.0, .NET 9 runtime, Linux):
| Multiplier | Elements | Peak Working Set |
||-|-|
| 100,000 | 500K | 49 MB |
| 50,000,000 | 250M | ~2 GB |
| 200,000,000| 1B | ~7.7 GB |
| 400,000,000| 2B | ~15.3 GB |
| 429,496,730| — | integer overflow → `ArgumentOutOfRangeException` |
The issue reproduces identically on all versions from 3.0.0 through 7.2.0. Version 2.1.4 and earlier are not affected because the operator did not exist.
DailyCVE Form:
Platform: ……. Scriban (NuGet package)
Version: …….. 3.0.0 – 7.2.0 (all releases containing the `array integer` operator)
Vulnerability :.. Uncontrolled memory allocation (CWE‑789 / CWE‑1284) via `array integer` with no LoopLimit/LimitToString check and unchecked integer overflow
Severity: ……. 8.7 High (CVSS 4.0) / 7.5 High (CVSS 3.1)
date: ………. 2026‑06‑26
Prediction: ….. Patch expected within 7.2.1 (or 7.3.0) – apply the same StepLoop/LimitToString hardening used for `array.insert_at` and `string integer`
Analytics – What Undercode Say:
Verify the vulnerability on a running instance
curl -X POST https://your-scriban-endpoint/render \
-H "Content-Type: application/json" \
-d '{"template": "{{ x = [1,2,3,4,5] 50000000; x.size }}"}'
Monitor memory pressure during exploitation
watch -n 1 'ps -eo pid,comm,vsz,rss | grep dotnet'
Check if LoopLimit is being enforced (it is not for this path)
grep -n "StepLoop|LoopLimit|Limit" src/Scriban/Runtime/ScriptArray.cs
// Minimal C scanner to detect vulnerable versions
using System.Reflection;
var asm = Assembly.Load("Scriban");
var version = asm.GetName().Version;
if (version >= new Version(3,0,0) && version <= new Version(7,2,0))
Console.WriteLine("Vulnerable to array integer DoS");
Exploit:
A single HTTP request with a ~40‑byte template body triggers the allocation:
{{ x = [1,2,3,4,5] 200000000; x.size }}
On a system with limited memory, this causes the process to be terminated by the OOM‑killer. On systems with sufficient memory, the allocation throws an `OutOfMemoryException` (wrapped as ScriptRuntimeException), but the CPU/GC is pegged for seconds, degrading availability for other requests. The attack requires no authentication, no special privileges, and no `MemberFilter` interaction – it is a pure language operation.
Protection:
- Upgrade to a patched version once available (expected in 7.2.1 or 7.3.0).
- Workaround: If you cannot upgrade immediately, pre‑validate or sanitize all template input to reject expressions that multiply an array by a large integer. For example, use a custom `TemplateContext` with a pre‑render hook that scans for the “ operator with an array left‑hand side.
- Mitigation: Set `LoopLimit` to a low value (e.g., 100) – this does not protect against this specific path, but it limits other iteration‑based attacks. The only effective mitigation is to apply the same hardening used for `array.insert_at` and
string integer: - Call `context.StepLoop(span, ref loopStep)` inside the fill loop to enforce
LoopLimit. - Pre‑check the result size with overflow‑safe arithmetic, e.g.
if (context.LimitToString > 0 && (long)intModifier array.Count > context.LimitToString) throw new ScriptRuntimeException(...). - Use `long` for capacity calculations to eliminate the integer‑overflow path.
Impact:
- Type: Denial of Service via uncontrolled memory allocation (CWE‑789 / CWE‑1284).
- Severity: CVSS 4.0 `AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N` = 8.7 (High); CVSS 3.1 equivalent `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` = 7.5 (High).
- Who is impacted: Any application that renders a template whose text is partially or wholly attacker‑controlled (the documented server‑side template scenario), or that passes attacker‑controlled strings to `object.eval` /
object.eval_template. No `MemberFilter` interaction is required. - Outcome: On systems with sufficient memory, the runtime catches the allocation failure and the host sees a `ScriptRuntimeException` – recoverable per request. On systems where the multi‑GB allocation exceeds available memory, the OS OOM‑killer can terminate the process before the managed exception fires. In all cases, a ~40‑byte template forces a multi‑GB allocation and seconds of pegged CPU/GC – a real per‑request availability degradation and resource amplification.
- Why existing mitigations do not help: `LoopLimit` (default 1000) is the documented control for unbounded iteration/allocation, but the `array int` path never consults it, so a defender running default configuration is not protected.
🎯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

