Back to scan results
Check 37 of 45

Server-Side Template Injection (SSTI)

We place uniquely wrapped arithmetic expressions into public template-like GET inputs. An exact evaluated result confirms server-side template processing; new engine-specific errors are flagged for manual review.

What this check probes

The discovery stage inspects same-origin GET forms and links for parameters such as template, view, message, content, preview, layout, renderer, name, and search. A bounded root-page fallback covers common names when no suitable public form is advertised.

Each selected input receives six template syntaxes containing only the arithmetic expression 3131+1. The expression is wrapped with a unique marker, so a failure requires the exact result pciscan373132end to appear in the probe response while remaining absent from both the page and parameter baselines.

Responses are also compared for new Jinja, Twig, FreeMarker, Velocity, Thymeleaf, Razor, ERB, Go, Handlebars, Mustache, Nunjucks, Smarty, and Pug error signatures. An engine-specific error or a new HTTP 5xx response produces a warning, not a confirmed failure.

Raw reflection, ordinary HTTP responses, redirects, response-size changes, and generic validation messages are not treated as proof. The check never accesses configuration, traverses objects or classes, calls functions or methods, reads files, runs commands, creates callbacks, causes delays, submits POST requests, signs in, or guesses unlinked endpoints. Authenticated previews, email jobs, document generators, JSON bodies, and background rendering remain outside this scan.

Why this matters for PCI DSS

SSTI occurs when untrusted text becomes template source instead of data. Depending on the engine and its permissions, successful injection can expose application secrets and customer data or reach files, internal services, and operating-system commands.

PCI DSS secure-development requirements expect applications to prevent injection, separate code from untrusted data, handle errors safely, and apply least privilege. A pass here covers only the public GET inputs reached by these non-destructive arithmetic probes.

How to fix it

Keep the template source fixed and server-owned. Let a short allowlisted identifier select a view, then pass request values only through the view model. For an ASP.NET MVC application targeting .NET Framework 4.7.2 or 4.8:

var allowedViews = new Dictionary<string, string>(
    StringComparer.OrdinalIgnoreCase)
{
    { "receipt", "Receipt" },
    { "summary", "AccountSummary" }
};

string viewName;
if (!allowedViews.TryGetValue(templateId, out viewName))
    throw new HttpException(400, "Unknown template.");

return View(viewName, new ReceiptViewModel
{
    Message = message // Data, never template source.
});

Do not pass message, template, a database field, or uploaded text to a runtime Razor compiler or another template engine's compile/render-source API. Normal Razor model output is HTML-encoded by default; output encoding helps prevent XSS, but it does not make user-controlled template source safe.

If user-authored templates are an essential product feature, prefer a logicless language and use a maintained sandbox with an allowlist of variables and helpers. Remove reflection, dynamic member access, includes, file and network access, process execution, environment variables, and unsafe escape hatches. Enforce strict size, recursion, memory, and execution-time limits in a separate low-privilege worker.

Store templates as immutable reviewed assets, authorize template selection, validate the model schema, and avoid raw-output helpers unless content has been sanitized for its exact output context. Return generic errors without template source, stack traces, engine versions, class names, file paths, or secrets.

Fixed it? Re-run the scan to confirm.

Run scan again