Server Side Template Injection in Aspnet with Hmac Signatures
Server Side Template Injection in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) in ASP.NET occurs when user-controlled data is embedded into a template engine and interpreted as code. When HMAC signatures are used to validate or bind incoming data to a template context, a subtle misconfiguration can turn the signature into a carrier for malicious template logic rather than a protection mechanism.
Consider an endpoint that accepts a JSON payload containing both template and signature fields. The server recomputes the HMAC over the template using a shared secret and compares it to the client-supplied signature. If the comparison is performed correctly but the template is later rendered without isolation, an attacker can craft a template that includes SSTI payloads. Because the signature validates integrity, the server may treat the malicious template as trusted, expanding the attack surface to authenticated contexts where signature verification gives false confidence.
In ASP.NET with Razor or custom template engines, SSTI can manifest through TemplateEngine APIs that accept strings and produce output. For example, if the engine supports dynamic object models and the attacker can control property names or method calls, they may execute arbitrary code paths. The HMAC does not prevent injection; it only ensures the template has not been altered in transit. If the server deserializes or binds user input into the model passed to the template, the signature remains valid while the rendered output executes attacker-controlled logic.
Real-world patterns include using ConfigurationBinder or JsonConvert.DeserializeObject to map incoming data into a view model that is then passed to RazorLight or similar libraries. An attacker might supply a template like {{7*7}} in a property that ends up in the model, and if the engine evaluates expressions, the server performs unintended calculations or triggers side effects. The HMAC-verified payload gives the application no indication that the content is unsafe, because the signature covers the untrusted data itself.
Linked to known attack patterns, this scenario mirrors OWASP API Top 10 #1 (Broken Object Level Authorization) when template context becomes an authorization boundary, and can intersect with data exposure if templates render sensitive configuration or environment details. CVE-class behaviors arise when template injection leads to remote code execution in server-side rendering pipelines, particularly when the runtime has access to filesystem or environment variables.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
Remediation focuses on strict separation between integrity verification and template rendering. HMAC should be used to sign only the parts of the payload that must remain immutable, and templates should be resolved from a pre-approved set, never directly from user input.
First, validate the HMAC over a canonical representation of the data that does not include the template content, or treat the template as an independent artifact located by a trusted identifier. If the template must be signed, ensure the signature is verified before any deserialization or binding, and reject the request if verification fails.
Use strongly typed models and avoid passing raw user strings into template engines. If you must render dynamic templates, use a sandboxed context and disable expression evaluation. Below are concrete C# examples for ASP.NET that demonstrate secure HMAC handling.
Example 1: Verifying HMAC before model binding
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class TemplateController : ControllerBase
{
private const string Secret = "your-256-bit-secret";
[HttpPost("render")]
public IActionResult Render([FromBody] TemplateRequest request)
{
if (!VerifyHmac(request.Data, request.Signature, Secret))
{
return Unauthorized("Invalid signature");
}
// Safe: use a precompiled template or a trusted resolver
var result = TrustedTemplateEngine.Render("precompiled_template", request.Data);
return Ok(new { Output = result });
}
private static bool VerifyHmac(string data, string signature, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var computed = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data)));
return CryptographicOperations.FixedTimeEquals(
Convert.FromBase64String(signature),
Convert.FromBase64String(computed));
}
}
public class TemplateRequest
{
public string Data { get; set; }
public string Signature { get; set; }
}
Example 2: Using a template registry to avoid user-controlled template paths
using System.Collections.Concurrent;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class SafeTemplateController : ControllerBase
{
private static readonly ConcurrentDictionary Registry =
new(StringComparer.OrdinalIgnoreCase)
{
["welcome"] = new CompiledTemplate("Welcome, {{name}}!"),
["report"] = new CompiledTemplate("Total: {{total}}")
};
[HttpPost("render")}
public IActionResult Render([FromBody] RenderRequest request)
{
if (!Registry.TryGetValue(request.TemplateId, out var template))
{
return BadRequest("Unknown template");
}
// Verify HMAC over the template ID and any immutable parameters
if (!HmacValidator.Verify(request.TemplateId, request.Signature, "shared-secret"))
{
return Unauthorized("Invalid signature");
}
var output = template.Render(request.Model);
return Ok(new { Output = output });
}
}
public class RenderRequest
{
public string TemplateId { get; set; }
public object Model { get; set; }
public string Signature { get; set; }
}
public interface ITemplate
{
string Render(object model);
}
public class CompiledTemplate : ITemplate
{
private readonly string _raw;
public CompiledTemplate(string raw) => _raw = raw;
public string Render(object model) => _raw.Replace("{{name}}", model?.GetType().GetProperty("Name")?.GetValue(model)?.ToString() ?? "");
}
public static class HmacValidator
{
public static bool Verify(string data, string signature, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var hash = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data)));
return hash == signature;
}
}
These examples emphasize that HMAC is a guard for integrity, not a sanitizer for content. Always treat user-controlled strings as untrusted when passed to template engines, and prefer precompiled or strictly governed templates over dynamic evaluation.