Cross Site Scripting (XSS)¶
What does this mean ?¶
XSS occurs when untrusted content is interpreted as active browser content within an application's origin. The value may come directly from a request, from stored records, or from a client-side source such as a URL fragment. The relevant question is where the value ends up, not whether it contains a particular suspicious word.
What can happen ?¶
Injected script can act through the current page, read accessible data and alter the interface. HttpOnly prevents JavaScript from reading the cookie itself; it does not stop an injected script from initiating authenticated actions. Impact depends on the application's privileges and browser controls.
Recommendation¶
Use framework escaping for normal text and safe DOM properties such as textContent. Output encoding must match the destination: HTML text, an HTML attribute and a JavaScript expression are different contexts. Do not insert untrusted values into inline scripts, event attributes or style source. Validate URL schemes separately.
When a feature intentionally accepts rich HTML, use a maintained HTML sanitizer with an explicit policy. Avoid modifying sanitized markup afterwards in ways that reintroduce unsafe content. A restrictive Content Security Policy adds protection but does not replace correct rendering.
Sample Code¶
These pairs cover HTML text only. They do not establish safety for JavaScript, CSS or URL contexts. Template names and template source must remain server-owned.
Rails ERB escapes ordinary values. html_safe bypasses that protection; it does not clean the string.
<%# Unsafe when name contains untrusted content %>
<p><%= name.html_safe %></p>
<%# Safer: keep automatic HTML escaping %>
<p><%= name %></p>
Browser DOM code after locating the intended element:
// Unsafe
output.innerHTML = userText;
// Safer for plain text
output.textContent = userText;
In React, render {userText} as a text child. dangerouslySetInnerHTML accepts HTML and needs a separate sanitization decision.
Django's HTML templates escape variables by default:
{# Unsafe: safe bypasses escaping #}
<p>{{ name|safe }}</p>
{# Safer: keep autoescaping enabled #}
<p>{{ name }}</p>
ASP.NET Core Razor, for an ordinary string property:
@* Unsafe *@
<p>@Html.Raw(Model.Name)</p>
@* Safer *@
<p>@Model.Name</p>
A servlet HTML-text response using the OWASP Java Encoder dependency:
response.setContentType("text/html;charset=UTF-8");
String name = request.getParameter("name");
if (name == null) name = "";
// Unsafe
response.getWriter().write("<p>" + name + "</p>");
// Safer alternative; do not emit the unsafe line above.
response.getWriter().write("<p>" + org.owasp.encoder.Encode.forHtml(name) + "</p>");
// Unsafe
echo '<p>' . $name . '</p>';
// Safer alternative for HTML text
echo '<p>' . htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</p>';
Use html/template for HTML output, not text/template. With an existing http.ResponseWriter named w:
// Unsafe
fmt.Fprintf(w, "<p>%s</p>", name)
// Safer alternative
t, err := template.New("greeting").Parse("<p>{{.}}</p>")
if err != nil {
return err
}
return t.Execute(w, name)
Import html/template. Do not cast an untrusted value to template.HTML to silence escaping.
Regression checks¶
Render the harmless marker <em>sample</em> & "text" through each output location in a local fixture. For a plain-text field it should appear as text, with no new em element in the DOM. Check stored values as well as immediate responses. Confirm that allowed rich text retains its permitted formatting after sanitization and that URL fields reject disallowed schemes.