Remote Code Execution via Razor Template Injection

Critical Risk Template & Code Injection
csharpdotnetrazortemplate-injectionrcessti

What it is

Server-Side Template Injection (SSTI) vulnerabilities occur when user-controlled input is passed to Razor.Parse() or similar template compilation methods. Razor templates can contain C# code that gets compiled and executed on the server, allowing attackers to execute arbitrary code, read sensitive files, access databases, or fully compromise the application.

using RazorEngine;
using Microsoft.AspNetCore.Mvc;

[ApiController]
public class TemplateController : ControllerBase
{
    // VULNERABLE: user input passed to Razor.Parse
    [HttpPost("api/template/render")]
    public IActionResult RenderTemplate([FromBody] TemplateRequest request)
    {
        // DANGEROUS: compiles and executes user-controlled template
        string result = Razor.Parse(request.Template, request.Model);
        return Ok(new { result });
    }
}

public class TemplateRequest
{
    public string Template { get; set; }
    public object Model { get; set; }
}

// Attack payload:
// @{ System.Diagnostics.Process.Start("calc.exe"); }
// @{ var content = System.IO.File.ReadAllText(@"C:\secrets\db.config"); }
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

[ApiController]
public class SecureTemplateController : ControllerBase
{
    private readonly Dictionary<string, string> _templates = new()
    {
        ["welcome"] = "Welcome, {{UserName}}! Your account is ready.",
        ["notification"] = "Hello {{UserName}}, you have {{Count}} messages."
    };
    
    // SECURE: use predefined templates only
    [HttpPost("api/template/render")]
    public IActionResult RenderTemplate([FromBody] SecureTemplateRequest request)
    {
        // Safe: only allow predefined template IDs
        if (!_templates.TryGetValue(request.TemplateId, out string template))
        {
            return BadRequest(new { error = "Invalid template ID" });
        }
        
        // Safe: simple placeholder replacement
        string result = ReplacePlaceholders(template, request.Data);
        return Ok(new { result });
    }
    
    private string ReplacePlaceholders(string template, Dictionary<string, string> data)
    {
        string result = template;
        foreach (var kvp in data)
        {
            result = result.Replace($"{{{{{kvp.Key}}}}}", SanitizeString(kvp.Value));
        }
        return result;
    }
    
    private string SanitizeString(string input)
    {
        return System.Web.HttpUtility.HtmlEncode(input ?? "");
    }
}

public class SecureTemplateRequest
{
    public string TemplateId { get; set; }
    public Dictionary<string, string> Data { get; set; }
}

💡 Why This Fix Works

The vulnerable code passes user-controlled template strings to Razor.Parse(), which compiles and executes C# code embedded in the template, allowing RCE. The secure version uses predefined templates selected by ID, with simple placeholder replacement that doesn't execute code.

Why it happens

Passing user-controlled template strings directly to Razor.Parse() or Engine.Razor.RunCompile().

Root causes

User Input in Razor.Parse()

Passing user-controlled template strings directly to Razor.Parse() or Engine.Razor.RunCompile().

Dynamic Template Generation

Building templates dynamically from user input without validation.

Missing Template Sandboxing

Not restricting template execution to safe operations only.

Fixes

1

Use Predefined Templates Only

Store templates securely on server and select by ID, never accept template content from users.

2

Use Safe Template Engines

Replace Razor with safe template engines like Handlebars.NET that don't execute code.

3

Simple String Replacement

For basic templating, use simple placeholder replacement instead of template compilation.

Detect This Vulnerability in Your Code

Sourcery automatically identifies remote code execution via razor template injection and many other security issues in your codebase.