Description
ASP.NET Core's ControllerBase.Content, Results.Content and TypedResults.Content do not HTML-encode the supplied string. When request text is returned directly as text/html, the browser parses it as markup rather than data. An attacker can inject tags, event handlers or other executable markup and cause reflected cross-site scripting (XSS).
Input validation and output encoding are separate controls. Format checks can reduce the attack surface but cannot replace the handling required by HTML, attribute, URL, JavaScript and CSS contexts. A custom function named Sanitize, Encode or Clean is not sufficient evidence that its result is safe for the destination.
Potential impact
- Theft of sessions or sensitive information
- Phishing interfaces and modified page content
- Requests or client-side actions using the victim's permissions
- Malicious content delivered through a trusted site origin
Remediation
Prefer Razor's ordinary encoded output over assembling raw HTML. Ordinary @value output encodes strings for HTML, but Html.Raw, HtmlString and other IHtmlContent paths can bypass that protection. Do not use them for untrusted values.
If markup is unnecessary, return text/plain or structured data. For an HTML text node, encode the complete untrusted value at the final output boundary with WebUtility.HtmlEncode or HtmlEncoder.Default.Encode. Use context-specific encoders or safe data transfer for URLs, JavaScript and CSS. Prefer encoded HTML data attributes or JSON and safe sinks such as textContent over concatenating values into JavaScript.
If users must author rich HTML, ordinary encoding will not preserve its formatting. Use a maintained allow-list HTML sanitizer appropriate to the rendering environment at the rendering boundary. A current DOMPurify release is one option in the browser. Sanitize for the exact insertion context and do not combine or modify the sanitized result before insertion. Element.setHTML() is not yet a Baseline API across all supported environments, so do not assume it is a general compatibility solution.
Examples
Before
Controller response
using Microsoft.AspNetCore.Mvc;
public sealed class PreviewController : ControllerBase
{
[HttpGet("/preview")]
public IActionResult Preview([FromQuery] string html)
{
return Content(html, "text/html");
}
}
html is bound directly from the request, and Content does not encode it.
Minimal API response
using Microsoft.AspNetCore.Mvc;
app.MapGet("/preview", ([FromQuery] string html) =>
Results.Content(html, "text/html"));
After
Recommended: ordinary Razor encoding
using Microsoft.AspNetCore.Mvc;
public sealed class PreviewController : Controller
{
[HttpGet("/preview-name")]
public IActionResult Preview([FromQuery] string name)
{
return View("Preview", model: name);
}
}
<h1>@Model</h1>
Razor's ordinary string output is HTML-encoded. Using Html.Raw(Model) for the same value removes that protection.
Recommended: responses without markup
public IActionResult Preview([FromQuery] string text)
{
return Content(text, "text/plain");
}
Limited case: HTML text-node encoding
using System.Text.Encodings.Web;
public IActionResult Preview([FromQuery] string name)
{
var encodedName = HtmlEncoder.Default.Encode(name);
return Content($"<h1>{encodedName}</h1>", "text/html");
}
This example applies only to an HTML text node. Reusing the encoded result in <script>, styles, URLs or event-handler attributes does not guarantee safety.
When rich HTML is required
The server can deliver content as data, with the browser sanitizing it through a current DOMPurify release immediately before insertion.
const cleanHtml = DOMPurify.sanitize(untrustedHtml);
preview.innerHTML = cleanHtml;
Keep DOMPurify patched and prevent later template or string processing from changing the sanitized result. When choosing a server-side sanitizer, assess active maintenance, allowed elements and attributes, URL protocols and the target HTML context.
References
- Microsoft: Prevent Cross-Site Scripting (XSS) in ASP.NET Core
- Microsoft: Razor syntax reference — Expression encoding
- Microsoft: ControllerBase.Content
- Microsoft: Results.Content
- Microsoft: TypedResults.Content
- OWASP Cross Site Scripting Prevention Cheat Sheet
- OWASP ASVS 5.0.0
- WHATWG HTML Standard: Dynamic markup insertion
- MDN: Element.setHTML()
- DOMPurify Security Goals & Threat Model
- CWE-79: Improper Neutralization of Input During Web Page Generation