PT-2026-30291 · Nuget · Scriban

Published

2026-03-24

·

Updated

2026-03-24

CVSS v3.1

7.5

High

VectorAV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary

Scriban'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.

Details

The relevant code path is:
  • ScriptBlockStatement.Evaluate() calls context.CheckAbort() once per statement in src/Scriban/Syntax/Statements/ScriptBlockStatement.cs lines 41–46.
  • LoopLimit enforcement is tied to script loop execution via TemplateContext.StepLoop(), not to internal helper iteration.
  • array.size in src/Scriban/Functions/ArrayFunctions.cs lines 596–609 calls list.Cast<object>().Count() for non-collection enumerables.
  • 1..N creates a ScriptRange from ScriptBinaryExpression.RangeInclude() in src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs lines 745–748.
  • ScriptRange then yields every element one by one without going through StepLoop() in src/Scriban/Runtime/ScriptRange.cs.
This means a single statement can perform arbitrarily large iteration without being stopped by LoopLimit.
There is also a related memory-amplification path in string * int:
  • ScriptBinaryExpression.CalculateToString() appends in a plain for loop in src/Scriban/Syntax/Expressions/ScriptBinaryExpression.cs lines 301–334.

Proof of Concept

Setup

bash
mkdir scriban-poc3
cd scriban-poc3
dotnet new console --framework net8.0
dotnet add package Scriban --version 6.6.0

Program.cs

csharp
using Scriban;

var template = Template.Parse("{{ 1..1000000 | array.size }}");

var context = new TemplateContext
{
  LoopLimit = 1
};

Console.WriteLine(template.Render(context));

Run

bash
dotnet run

Actual Output

1000000

Expected Behavior

A safety limit of LoopLimit = 1 should prevent a template from performing one million iterations worth of work.

Optional Stronger Variant (Memory Amplification)

csharp
using Scriban;

var template = Template.Parse("{{ 'A' * 200000000 }}");
var context = new TemplateContext
{
  LoopLimit = 1
};

template.Render(context);
This variant demonstrates that LoopLimit also does not constrain large internal allocation work.

Impact

This 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.
The issue impacts:
  • Template-as-a-service systems
  • CMS or email rendering systems that accept user templates
  • Any multi-tenant use of Scriban with untrusted template content

Fix

Resource Exhaustion

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

GHSA-C875-H985-HVRC

Affected Products

Scriban