Server Side Template Injection in Aspnet
How Server Side Template Injection Manifests in Aspnet
Server Side Template Injection (SSTI) in ASP.NET applications occurs when user-controlled input is improperly passed into template rendering engines like Razor, Razor Pages, or third-party engines such as Scriban or Fluid. Unlike classic SSTI in Jinja or Twig, ASP.NET’s Razor engine is less prone to direct code execution due to its compiled nature, but vulnerabilities still arise through unsafe data binding, dynamic view rendering, or improper use of Html.Raw() and @Html.DisplayFor() with untrusted input.
A common vulnerable pattern involves Razor Pages where user input is directly embedded into a view path or template string. For example, an attacker might manipulate a route parameter to traverse into unintended views or inject executable syntax if the application uses dynamic compilation via RazorLight or DotLiquid with insufficient input sanitization.
// Vulnerable: User input used directly in view name
public async Task OnGet(string page)
{
// If 'page' comes from user input without validation
return Page(); // Actually renders /Pages/{page}.cshtml
}
If page is set to something like ../../etc/passwd or _ViewStart;@System.IO.File.Delete("log.txt") (in engines that allow expression execution), it can lead to path traversal or, in rare cases with misconfigured template engines, remote code execution. Another vector is through ViewComponent invocation where the component name is user-controlled and resolved without validation.
In Blazor applications, SSTI is less common in server-side rendering due to the component model, but risks exist when using MarkupString with unsanitized user input or when integrating client-side templating libraries that are mistakenly used server-side.
Aspnet-Specific Detection
Detecting SSTI in ASP.NET requires black-box scanning that identifies injection points in template rendering contexts. middleBrick performs active testing by sending payloads designed to trigger expression evaluation in common .NET templating engines and observing responses for signs of successful injection, such as unexpected output, errors revealing stack traces, or changes in application behavior.
For Razor-based applications, middleBrick tests for payloads that attempt to access @System.Environment or @System.IO.File through injection points in URL parameters, form fields, or headers that are reflected in rendered output. It also checks for misuse of Html.Raw() where attacker-controlled data is rendered without encoding.
When scanning APIs that serve HTML responses (e.g., Razor Pages endpoints), middleBrick includes checks for:
- Reflection of user input in
<script>tags or attribute contexts that could lead to XSS, often a precursor to SSTI exploitation in hybrid attacks - Error messages that expose
InvalidOperationExceptionorRazorCompilationExceptionwhen malicious template syntax is submitted - Behavioral changes indicating successful execution, such as file access or delayed responses from
System.Threading.Thread.Sleep()probes
For applications using third-party engines like Scriban, middleBrick sends engine-specific payloads (e.g., {{ 'a' | system.process.start('cmd.exe') }}) to detect if the engine is exposed and improperly configured. The scanner correlates runtime behavior with OpenAPI spec analysis to identify endpoints that return dynamic views based on user input.
Example detection via middleBrick CLI:
middlebrick scan https://api.example.com/template?user=admin
This command triggers the full scan, including SSTI checks, and returns a risk score with findings if template injection vectors are detected.
Aspnet-Specific Remediation
Fixing SSTI in ASP.NET applications centers on preventing user input from being interpreted as template code or used to dynamically select views without strict validation. The primary defenses are input validation, output encoding, and avoiding dynamic template resolution where possible.
For Razor Pages and MVC views, never use user input directly in return View(), return Page(), or view component names. Instead, map user input to a predefined set of allowed values.
// Secure: Validate input against allowed views
public async Task OnGet(string page)
{
var allowedPages = new[] { "home", "about", "contact" };
if (!allowedPages.Contains(page, StringComparer.OrdinalIgnoreCase))
{
return NotFound();
}
return Page(); // Now safe: only predefined pages allowed
}
When rendering user-provided content that must include HTML (e.g., blog comments), use HtmlEncoder or AntXss to encode output unless absolutely necessary, and if so, sanitize with a trusted library like HtmlSanitizer.
// Secure: Encode user input before rendering
public string GetUserComment(string rawComment)
{
var encoder = HtmlEncoder.Create();
return encoder.Encode(rawComment); // Prevents HTML/JS execution
}
// Or, if HTML is required, sanitize
var sanitizer = new HtmlSanitizer();
var safeHtml = sanitizer.Sanitize(userProvidedHtml);
For applications using third-party templating engines (e.g., Scriban in API responses), ensure the engine is configured with strict member access rules. Disable access to unsafe types and methods.
// Secure: Configure Scriban with restricted access
var template = Template.Parse(templateSource);
var context = new TemplateContext {
MemberAccessStrategy = MemberAccessStrategy.Unrestricted // Avoid this!
// Instead, use:
MemberAccessStrategy = MemberAccessStrategy.Default // Only allows safe members
};
var result = template.Render(context, new { UserInput = Model.UnsafeInput });
Additionally, apply the principle of least privilege: run the application with a restricted service account that cannot access sensitive files or execute arbitrary commands, limiting the impact of any potential template injection.
Frequently Asked Questions
Can Server Side Template Injection occur in ASP.NET Core Razor Pages even though Razor is compiled?
Html.Raw(), or integration with third-party templating engines like Scriban or DotLiquid that allow expression execution if user input is passed without validation or sanitization.