{"id":"CVE-2026-74786","aliases":["EUVD-2026-59952","GHSA-m2p3-hwv5-xpqw","GHSA-w7vg-5328-m33q"],"status":"published","title":"Scriban before 7.0.0 Denial of Service via Unbounded Template Output","description":"Scriban before 7.0.0 (affected versions \u003c= 6.6.0) contains a denial-of-service vulnerability in which the LimitToString safety limit (default 1MB) can be bypassed because ObjectToString resets the per-call length counter (_currentToStringLength) on every top-level call and StringBuilderOutput enforces no cumulative output-size limit. An attacker who can supply a template can render a near-limit string repeatedly in a loop, allocating approximately 1GB of memory and causing an out-of-memory condition that crashes the host application.","severity":{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00476,"percentile":0.41408,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-770"],"published":"2026-03-24T22:15:43Z","modified":"2026-10-28T02:31:12Z","fixes":[{"ecosystem":"NuGet","package":"Scriban","fixed_in":["7.0.0"],"sources":["ghsa","osv"]},{"ecosystem":"NuGet","package":"Scriban.Signed","fixed_in":["7.0.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"NuGet","package":"Scriban","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.0.0"}]}]},{"source":"ghsa","ecosystem":"NuGet","package":"Scriban.Signed","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.0.0"}]}]},{"source":"osv","ecosystem":"NuGet","package":"Scriban","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.0.0"}]}],"versions":["0.1.0","0.10.0","0.11.0","0.12.0","0.12.1","0.13.0","0.14.0","0.15.0","0.16.0","0.2.0","0.2.1","0.2.2","0.3.0","0.3.1","0.3.1-pre028","0.4.0","0.5.0","0.6.0","0.7.0","0.9.0","0.9.0-pre100","0.9.1","1.0.0","1.0.0-beta-001","1.0.0-beta-002","1.0.0-beta-003","1.0.0-beta-004","1.0.0-beta-005","1.0.0-beta-006","1.1.0","1.1.1","1.2.0","1.2.1","1.2.2","1.2.3","1.2.4","1.2.5","1.2.6","1.2.7","1.2.8","1.2.9","2.0.0","2.0.0-alpha-001","2.0.0-alpha-002","2.0.0-alpha-003","2.0.0-alpha-004","2.0.0-alpha-005","2.0.0-alpha-006","2.0.1","2.1.0","2.1.1","2.1.2","2.1.3","2.1.4","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.1.0","3.2.0","3.2.1","3.2.2","3.3.0","3.3.1","3.3.2","3.3.3","3.4.0","3.4.1","3.4.2","3.5.0","3.6.0","3.7.0","3.8.0","3.8.1","3.8.2","3.9.0","4.0.0","4.0.1","4.0.2","4.1.0","5.0.0","5.1.0","5.10.0","5.11.0","5.12.0","5.12.1","5.2.0","5.3.0","5.4.0","5.4.1","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.5.0","5.5.1","5.5.2","5.6.0","5.7.0","5.8.0","5.9.0","5.9.1","6.0.0","6.1.0","6.2.0","6.2.1","6.3.0","6.4.0","6.5.0","6.5.1","6.5.2","6.5.3","6.5.4","6.5.5","6.5.6","6.5.7","6.5.8","6.6.0"]},{"source":"osv","ecosystem":"NuGet","package":"Scriban.Signed","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.0.0"}]}],"versions":["0.10.0","0.11.0","0.12.0","0.12.1","0.13.0","0.14.0","0.15.0","0.16.0","0.9.0","0.9.1","1.0.0","1.0.0-beta-001","1.0.0-beta-002","1.0.0-beta-003","1.0.0-beta-004","1.0.0-beta-005","1.0.0-beta-006","1.1.0","1.1.1","1.2.0","1.2.1","1.2.2","1.2.3","1.2.4","1.2.5","1.2.6","1.2.7","1.2.8","1.2.9","2.0.0","2.0.0-alpha-001","2.0.0-alpha-002","2.0.0-alpha-003","2.0.0-alpha-004","2.0.0-alpha-005","2.0.0-alpha-006","2.0.1","2.1.0","2.1.1","2.1.2","2.1.3","2.1.4","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.1.0","3.2.0","3.2.1","3.2.2","3.3.0","3.3.1","3.3.2","3.3.3","3.4.0","3.4.1","3.4.2","3.5.0","3.6.0","3.7.0","3.8.0","3.8.1","3.8.2","3.9.0","4.0.0","4.0.1","4.0.2","4.1.0","5.0.0","5.1.0","5.10.0","5.11.0","5.12.0","5.12.1","5.2.0","5.3.0","5.4.0","5.4.1","5.4.2","5.4.3","5.4.4","5.4.5","5.4.6","5.5.0","5.5.1","5.5.2","5.6.0","5.7.0","5.8.0","5.9.0","5.9.1","6.0.0","6.1.0","6.2.0","6.2.1","6.3.0","6.4.0","6.5.0","6.5.1","6.5.2","6.5.3","6.5.4","6.5.5","6.5.6","6.5.7","6.5.8","6.6.0"]}],"products":[{"vendor":"scriban","product":"scriban"}],"references":[{"url":"https://github.com/scriban/scriban/security/advisories/GHSA-m2p3-hwv5-xpqw","tags":["vendor-advisory","exploit","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/scriban-before-denial-of-service-via-unbounded-template-output","tags":["third-party-advisory","advisory"]},{"url":"https://github.com/advisories/GHSA-m2p3-hwv5-xpqw"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-74786","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-w7vg-5328-m33q"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/74xxx/CVE-2026-74786.json","tags":["advisory"]},{"url":"https://github.com/scriban/scriban","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-74786","title":"Scriban before 7.0.0 Denial of Service via Unbounded Template Output","description":"Scriban before 7.0.0 (affected versions \u003c= 6.6.0) contains a denial-of-service vulnerability in which the LimitToString safety limit (default 1MB) can be bypassed because ObjectToString resets the per-call length counter (_currentToStringLength) on every top-level call and StringBuilderOutput enforces no cumulative output-size limit. An attacker who can supply a template can render a near-limit string repeatedly in a loop, allocating approximately 1GB of memory and causing an out-of-memory condition that crashes the host application.","status":"published","published":"2026-08-16T13:14:11.837Z","modified":"2026-08-17T17:37:43.578Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T14:20:03.4818Z"}],"euvd":[{"id":"EUVD-2026-59952","description":"Scriban before 7.0.0 (affected versions \u003c= 6.6.0) contains a denial-of-service vulnerability in which the LimitToString safety limit (default 1MB) can be bypassed because ObjectToString resets the per-call length counter (_currentToStringLength) on every top-level call and StringBuilderOutput enforces no cumulative output-size limit. An attacker who can supply a template can render a near-limit string repeatedly in a loop, allocating approximately 1GB of memory and causing an out-of-memory condition that crashes the host application.","status":"published","published":"2026-08-16T13:14:11Z","modified":"2026-08-17T17:37:43Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T18:01:48.853514Z"}],"ghsa":[{"id":"GHSA-m2p3-hwv5-xpqw","title":"Scriban: Denial of Service via Unbounded Cumulative Template Output Bypassing LimitToString","description":"## Summary\n\nThe `LimitToString` safety limit (default 1MB since commit `b5ac4bf`) can be bypassed to allocate approximately 1GB of memory by exploiting the per-call reset of `_currentToStringLength` in `ObjectToString`. Each template expression rendered through `TemplateContext.Write(SourceSpan, object)` triggers a separate top-level `ObjectToString` call that resets the length counter to zero, and the underlying `StringBuilderOutput` has no cumulative output size limit. An attacker who can supply a template can cause an out-of-memory condition in the host application.\n\n## Details\n\nThe root cause is in `TemplateContext.Helpers.cs`, in the `ObjectToString` method:\n\n```csharp\n// src/Scriban/TemplateContext.Helpers.cs:89-111\npublic virtual string ObjectToString(object value, bool nested = false)\n{\n    if (_objectToStringLevel == 0)\n    {\n        _currentToStringLength = 0;  // \u003c-- resets on every top-level call\n    }\n    try\n    {\n        _objectToStringLevel++;\n        // ...\n        var result = ObjectToStringImpl(value, nested);\n        if (LimitToString \u003e 0 \u0026\u0026 _objectToStringLevel == 1 \u0026\u0026 result != null \u0026\u0026 result.Length \u003e= LimitToString)\n        {\n            return result + \"...\";\n        }\n        return result;\n    }\n    // ...\n}\n```\n\nEach time a template expression is rendered, `TemplateContext.Write(SourceSpan, object)` calls `ObjectToString`:\n\n```csharp\n// src/Scriban/TemplateContext.cs:693-701\npublic virtual TemplateContext Write(SourceSpan span, object textAsObject)\n{\n    if (textAsObject != null)\n    {\n        var text = ObjectToString(textAsObject);  // fresh _currentToStringLength = 0\n        Write(text);\n    }\n    return this;\n}\n```\n\nThe `StringBuilderOutput.Write` method appends unconditionally with no size check:\n\n```csharp\n// src/Scriban/Runtime/StringBuilderOutput.cs:47-50\npublic void Write(string text, int offset, int count)\n{\n    Builder.Append(text, offset, count);  // no cumulative limit\n}\n```\n\n**Execution flow:**\n1. Template creates a string of length 1,048,575 (one byte under the 1MB `LimitToString` default)\n2. A `for` loop iterates up to `LoopLimit` (default 1000) times\n3. Each iteration renders the string via `Write(span, x)` → `ObjectToString(x)`\n4. `ObjectToString` resets `_currentToStringLength = 0` since `_objectToStringLevel == 0`\n5. The string passes the `LimitToString` check (1,048,575 \u003c 1,048,576)\n6. Full string is appended to `StringBuilder` — no cumulative tracking\n7. After 1000 iterations: ~1GB allocated in-memory\n\n## PoC\n\n```csharp\nusing Scriban;\n\n// Uses only default TemplateContext settings (LoopLimit=1000, LimitToString=1048576)\nvar template = Template.Parse(\"{{ x = \\\"\\\" | string.pad_left 1048575 }}{{ for i in 1..1000 }}{{ x }}{{ end }}\");\n// This will allocate ~1GB in the StringBuilder, likely causing OOM\nvar result = template.Render();\n```\n\nEquivalent Scriban template:\n```scriban\n{{ x = \"\" | string.pad_left 1048575 }}{{ for i in 1..1000 }}{{ x }}{{ end }}\n```\n\nEach of the 1000 loop iterations outputs a 1,048,575-character string. Each passes the per-call `LimitToString` check independently. Total output: ~1,000,000,000 characters (~1GB) allocated in the `StringBuilder`.\n\n## Impact\n\n- **Denial of Service:** An attacker who can supply Scriban templates (common in CMS, email templating, report generation) can crash the host application via out-of-memory\n- **Process-level impact:** OOM kills the entire .NET process, not just the template rendering — affects all concurrent users\n- **Bypass of safety mechanism:** The `LimitToString` limit was specifically introduced to prevent resource exhaustion, but the per-call reset makes it ineffective against cumulative abuse\n- **Low complexity:** The exploit template is trivial — a single line\n\n## Recommended Fix\n\nAdd a cumulative output size counter to `TemplateContext` that tracks total bytes written across all `Write` calls, independent of the per-object `LimitToString`:\n\n```csharp\n// In TemplateContext.cs — add new property and field\nprivate long _totalOutputLength;\n\n/// \u003csummary\u003e\n/// Gets or sets the maximum total output length in characters. Default is 10485760 (10 MB). 0 means no limit.\n/// \u003c/summary\u003e\npublic int OutputLimit { get; set; } = 10485760;\n\n// In TemplateContext.Write(string, int, int) — add check before writing\npublic TemplateContext Write(string text, int startIndex, int count)\n{\n    if (text != null)\n    {\n        if (OutputLimit \u003e 0)\n        {\n            _totalOutputLength += count;\n            if (_totalOutputLength \u003e OutputLimit)\n            {\n                throw new ScriptRuntimeException(CurrentSpan, \n                    $\"The output limit of {OutputLimit} characters was reached.\");\n            }\n        }\n        // ... existing indent/write logic\n    }\n    return this;\n}\n```\n\nThis provides defense-in-depth: `LimitToString` caps individual object serialization, while `OutputLimit` caps total template output.","status":"published","published":"2026-03-24T22:15:43Z","modified":"2026-07-06T13:09:52Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T14:40:38.48196Z"},{"id":"GHSA-w7vg-5328-m33q","title":"Scriban before 7.0.0 (affected versions \u003c= 6.6.0) contains a denial-of-service vulnerability in...","description":"Scriban before 7.0.0 (affected versions \u003c= 6.6.0) contains a denial-of-service vulnerability in which the LimitToString safety limit (default 1MB) can be bypassed because ObjectToString resets the per-call length counter (_currentToStringLength) on every top-level call and StringBuilderOutput enforces no cumulative output-size limit. An attacker who can supply a template can render a near-limit string repeatedly in a loop, allocating approximately 1GB of memory and causing an out-of-memory condition that crashes the host application.","status":"published","published":"2026-08-16T15:30:26Z","modified":"2026-08-16T15:30:27Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T15:10:32.287939Z"}],"nvd":[{"id":"CVE-2026-74786","description":"Scriban before 7.0.0 (affected versions \u003c= 6.6.0) contains a denial-of-service vulnerability in which the LimitToString safety limit (default 1MB) can be bypassed because ObjectToString resets the per-call length counter (_currentToStringLength) on every top-level call and StringBuilderOutput enforces no cumulative output-size limit. An attacker who can supply a template can render a near-limit string repeatedly in a loop, allocating approximately 1GB of memory and causing an out-of-memory condition that crashes the host application.","status":"published","published":"2026-08-16T14:16:56.523Z","modified":"2026-08-31T20:30:14.457Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"secondary"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T14:12:38.647684Z"}],"osv":[{"id":"CVE-2026-74786","title":"Scriban before 7.0.0 Denial of Service via Unbounded Template Output","description":"Scriban before 7.0.0 (affected versions \u003c= 6.6.0) contains a denial-of-service vulnerability in which the LimitToString safety limit (default 1MB) can be bypassed because ObjectToString resets the per-call length counter (_currentToStringLength) on every top-level call and StringBuilderOutput enforces no cumulative output-size limit. An attacker who can supply a template can render a near-limit string repeatedly in a loop, allocating approximately 1GB of memory and causing an out-of-memory condition that crashes the host application.","status":"published","published":"2026-08-16T13:14:11.837Z","modified":"2026-10-28T02:31:12.706586145Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T15:45:16.748692Z"},{"id":"GHSA-m2p3-hwv5-xpqw","title":"Scriban: Denial of Service via Unbounded Cumulative Template Output Bypassing LimitToString","description":"## Summary\n\nThe `LimitToString` safety limit (default 1MB since commit `b5ac4bf`) can be bypassed to allocate approximately 1GB of memory by exploiting the per-call reset of `_currentToStringLength` in `ObjectToString`. Each template expression rendered through `TemplateContext.Write(SourceSpan, object)` triggers a separate top-level `ObjectToString` call that resets the length counter to zero, and the underlying `StringBuilderOutput` has no cumulative output size limit. An attacker who can supply a template can cause an out-of-memory condition in the host application.\n\n## Details\n\nThe root cause is in `TemplateContext.Helpers.cs`, in the `ObjectToString` method:\n\n```csharp\n// src/Scriban/TemplateContext.Helpers.cs:89-111\npublic virtual string ObjectToString(object value, bool nested = false)\n{\n    if (_objectToStringLevel == 0)\n    {\n        _currentToStringLength = 0;  // \u003c-- resets on every top-level call\n    }\n    try\n    {\n        _objectToStringLevel++;\n        // ...\n        var result = ObjectToStringImpl(value, nested);\n        if (LimitToString \u003e 0 \u0026\u0026 _objectToStringLevel == 1 \u0026\u0026 result != null \u0026\u0026 result.Length \u003e= LimitToString)\n        {\n            return result + \"...\";\n        }\n        return result;\n    }\n    // ...\n}\n```\n\nEach time a template expression is rendered, `TemplateContext.Write(SourceSpan, object)` calls `ObjectToString`:\n\n```csharp\n// src/Scriban/TemplateContext.cs:693-701\npublic virtual TemplateContext Write(SourceSpan span, object textAsObject)\n{\n    if (textAsObject != null)\n    {\n        var text = ObjectToString(textAsObject);  // fresh _currentToStringLength = 0\n        Write(text);\n    }\n    return this;\n}\n```\n\nThe `StringBuilderOutput.Write` method appends unconditionally with no size check:\n\n```csharp\n// src/Scriban/Runtime/StringBuilderOutput.cs:47-50\npublic void Write(string text, int offset, int count)\n{\n    Builder.Append(text, offset, count);  // no cumulative limit\n}\n```\n\n**Execution flow:**\n1. Template creates a string of length 1,048,575 (one byte under the 1MB `LimitToString` default)\n2. A `for` loop iterates up to `LoopLimit` (default 1000) times\n3. Each iteration renders the string via `Write(span, x)` → `ObjectToString(x)`\n4. `ObjectToString` resets `_currentToStringLength = 0` since `_objectToStringLevel == 0`\n5. The string passes the `LimitToString` check (1,048,575 \u003c 1,048,576)\n6. Full string is appended to `StringBuilder` — no cumulative tracking\n7. After 1000 iterations: ~1GB allocated in-memory\n\n## PoC\n\n```csharp\nusing Scriban;\n\n// Uses only default TemplateContext settings (LoopLimit=1000, LimitToString=1048576)\nvar template = Template.Parse(\"{{ x = \\\"\\\" | string.pad_left 1048575 }}{{ for i in 1..1000 }}{{ x }}{{ end }}\");\n// This will allocate ~1GB in the StringBuilder, likely causing OOM\nvar result = template.Render();\n```\n\nEquivalent Scriban template:\n```scriban\n{{ x = \"\" | string.pad_left 1048575 }}{{ for i in 1..1000 }}{{ x }}{{ end }}\n```\n\nEach of the 1000 loop iterations outputs a 1,048,575-character string. Each passes the per-call `LimitToString` check independently. Total output: ~1,000,000,000 characters (~1GB) allocated in the `StringBuilder`.\n\n## Impact\n\n- **Denial of Service:** An attacker who can supply Scriban templates (common in CMS, email templating, report generation) can crash the host application via out-of-memory\n- **Process-level impact:** OOM kills the entire .NET process, not just the template rendering — affects all concurrent users\n- **Bypass of safety mechanism:** The `LimitToString` limit was specifically introduced to prevent resource exhaustion, but the per-call reset makes it ineffective against cumulative abuse\n- **Low complexity:** The exploit template is trivial — a single line\n\n## Recommended Fix\n\nAdd a cumulative output size counter to `TemplateContext` that tracks total bytes written across all `Write` calls, independent of the per-object `LimitToString`:\n\n```csharp\n// In TemplateContext.cs — add new property and field\nprivate long _totalOutputLength;\n\n/// \u003csummary\u003e\n/// Gets or sets the maximum total output length in characters. Default is 10485760 (10 MB). 0 means no limit.\n/// \u003c/summary\u003e\npublic int OutputLimit { get; set; } = 10485760;\n\n// In TemplateContext.Write(string, int, int) — add check before writing\npublic TemplateContext Write(string text, int startIndex, int count)\n{\n    if (text != null)\n    {\n        if (OutputLimit \u003e 0)\n        {\n            _totalOutputLength += count;\n            if (_totalOutputLength \u003e OutputLimit)\n            {\n                throw new ScriptRuntimeException(CurrentSpan, \n                    $\"The output limit of {OutputLimit} characters was reached.\");\n            }\n        }\n        // ... existing indent/write logic\n    }\n    return this;\n}\n```\n\nThis provides defense-in-depth: `LimitToString` caps individual object serialization, while `OutputLimit` caps total template output.","status":"published","published":"2026-03-24T22:15:43Z","modified":"2026-08-17T03:55:59.220548628Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T15:46:51.10002Z"}]}}
