XSS prevention for Ruby on Rails
This is a cross-site scripting (XSS) prevention cheat sheet by Semgrep, Inc. It contains code patterns of potential XSS in an application. Instead of scrutinizing code for exploitable vulnerabilities, the recommendations in this cheat sheet pave a safe road for developers that mitigate the possibility of XSS in your code. By following these recommendations, you can be reasonably sure your code is free of XSS.
Mitigation summary
In general, always let Rails render ERB template files rather than constructing them in code. If HTML escaping is needed, use html_safe()
in Ruby code and review each individual usage carefully. Once reviewed, mark the line with # nosem
. Beware of putting data in dangerous locations in templates. And as always, run a security checker continuously on your code.
Check your project using Semgrep
The following command runs an optimized set of rules for your project:
semgrep --config p/default
1. Unescaped variable enters template engine in Ruby code
1.A. Using html_safe()
html_safe()
marks the supplied string as "safe for HTML rendering." This bypasses HTML escaping and potentially creates XSS vulnerabilities.
Example:
html = "<div>#{name}</div>".html_safe
References
Mitigation
Ban html_safe()
. Alternatively, If needed, review each usage and exempt with # nosem
.
Semgrep rule
ruby.rails.security.audit.xss.avoid-html-safe.avoid-html-safe1.B. Using content_tag()
content_tag()
's escaping behavior has changed between Rails 2 and 3. In Rails 2, no supplied content is escaped. In Rails 2 and 3, attribute names are not escaped. Further, the returned value is marked as "safe," the same as if html_safe()
had been used. This confusing behavior makes it difficult to use content_tag()
properly; improper use can create XSS vulnerabilities in your application.
Example:
content_tag :p, "Hello, #{name}"
References
Mitigation
Ban content_tag()
. Alternatively, If necessary, prefer html_safe()
due to its straightforward behavior.
Semgrep rule
ruby.rails.security.audit.xss.avoid-content-tag.avoid-content-tag1.C. Using raw()
raw()
disables HTML escaping for the returned content. This permits raw HTML to be rendered in a template, which could create a XSS vulnerability.
Example:
raw @user.name
References
Mitigation
Ban raw()
. Alternatively, Prefer html_safe()
if necessary.
Semgrep rule
ruby.rails.security.audit.xss.avoid-raw.avoid-raw1.D. Disabling of ActiveSupport#escape_html_entities_in_json
ActiveSupport#escape_html_entities_in_json
is a setting which determines whether Hash#to_json()
will escape HTML characters. Disabling this could create XSS vulnerabilities.
Example:
config.active_support.escape_html_entities_in_json = false
References
escape_html_entities_in_json
documentation- Brakeman scanner - Cross-site scripting (JSON)
- How to disable HTML escaping for JSON, but keep enabled for views?
Mitigation
Ban disabling of ActiveSupport#escape_html_entities_in_json
. Alternatively, If HTML is needed in JSON, use JSON.generate()
and review each usage carefully. Exempt each case with # nosem
.
Semgrep rule
ruby.lang.security.json-entity-escape.json-entity-escape2. Bypassing the template engine
2.A. Manually creating an ERB template
Manually creating an ERB template could create a server-side template injection (SSTI) vulnerability if it is created with user input. (This could also result in XSS.) Due to the severity of this type of vulnerability, it is better to use a template file instead of creating templates in code.
Example:
ERB.new("<div>#{@user.name}</div>").result
References
Mitigation
Ban template creation in code. Alternatively, Use ERB template files.
Semgrep rule
ruby.rails.security.audit.xss.manual-template-creation.manual-template-creation2.B. Rendering an inline template with render inline
render inline:
is the same as creating a template manually and is therefore susceptible to the same vulnerabilities as manually creating an ERB template. This can result in a SSTI or XSS vulnerability.
Example:
render inline: "<div>#{@user.name}</div>"
References
Mitigation
Ban render inline:
. Alternatively, Use ERB template files.
Semgrep rule
ruby.rails.security.audit.xss.avoid-render-inline.avoid-render-inline2.C. Using render text:
render text:
unintuitively sets the Content-Type to text/html. This means anything rendered through render text:
will be interpreted as HTML. Templates rendered in this manner could create a XSS vulnerability.
Example:
render text: "<div>#{@user.name}</div>"
References
Mitigation
Ban render text:
. Alternatively, Use ERB template files.
Semgrep rule
ruby.rails.security.audit.xss.avoid-render-text.avoid-render-text3. Templates: Variable explicitly unescaped
3.A. Using html_safe()
html_safe()
marks the supplied string as "safe for HTML rendering." This bypasses HTML escaping and potentially creates XSS vulnerabilities.
Example:
<%= name.html_safe %>
References
Mitigation
Ban html_safe()
. Alternatively, Prefer using html_safe()
in Ruby code instead of templates.
Semgrep rule
ruby.rails.security.audit.xss.templates.avoid-html-safe.avoid-html-safe3.B Using content_tag()
content_tag()
's escaping behavior has changed between Rails 2 and 3. In Rails 2, no supplied content is escaped. In Rails 2 and 3, attribute names are not escaped. Further, the returned value is marked as "safe," the same as if html_safe()
had been used. This confusing behavior makes it difficult to use content_tag()
properly; improper use can create XSS vulnerabilities in your application.
Example:
<%= content_tag :p, "Hello, #{name}" %>
References
Mitigation
Ban content_tag()
. Alternatively, If necessary, prefer html_safe()
in Ruby code due to its straightforward behavior.
Semgrep rule
ruby.rails.security.audit.xss.templates.avoid-content-tag.avoid-content-tag3.C. Using raw()
raw()
disables HTML escaping for the returned content. This permits raw HTML to be rendered in a template, which could create a XSS vulnerability.
Example:
<%= raw @user.name =>
References
Mitigation
Ban raw()
. Alternatively, Prefer html_safe()
in Ruby code if necessary.
Semgrep rule
ruby.rails.security.audit.xss.templates.avoid-raw.avoid-raw3.D. Using <%== ... %>, which is an alias for html_safe()
The double-equals ==
is an ERB alias for html_safe()
. This will mark the contents as "safe for rendering" and may introduce an XSS vulnerability.
Example:
<%== @user.name %>
References
Mitigation
Ban <%== ... %>
, which is an alias for html_safe()
. Alternatively, Prefer html_safe()
in Ruby code if necessary.
Semgrep rule
ruby.rails.security.audit.xss.templates.alias-for-html-safe.alias-for-html-safe4. Templates: Variable in dangerous location
4.A Unquoted variable in HTML attribute
Unquoted template variables rendered into HTML attributes is a potential XSS vector because an attacker could inject JavaScript handlers which do not require HTML characters. An example handler might look like: onmouseover=alert(1)
. HTML escaping will not mitigate this. The variable must be quoted to avoid this.
Example:
<div class=<%= classes %></div>
References:
Mitigation
Flag unquoted HTML attributes ERB expressions. Alternatively, Always use quotes around HTML attributes.
Semgrep rule
ruby.rails.security.audit.xss.templates.unquoted-attribute.unquoted-attribute4.B. Variable in href attribute
Template variables in a href
value could still accept the javascript:
URI. This could be a XSS vulnerability. HTML escaping will not prevent this. Use link_to
beginning with a literal forward slash to generate links.
Example:
<a href="<%= link %>"></a>
References
Mitigation
Flag template variables in href
attributes. Alternatively, Use url_for
to generate links.
Semgrep rule
ruby.rails.security.audit.xss.templates.var-in-href.var-in-href4.C. Using link_to with unrestricted URL scheme
Detected a template variable used in 'link_to'. This will generate dynamic data in the 'href' attribute. This allows a malicious actor to input the 'javascript:' URI and is subject to cross- site scripting (XSS) attacks. If using a relative URL, start with a literal forward slash and concatenate the URL, like this: <%= link_to "Here", "/"+@link %>
. You may also consider setting the Content Security Policy (CSP) header.
Example:
<%= link_to "Here", @link %>
References
Mitigation
Flag link_to
in templates. Alternatively, If you must use this, add a literal forward-slash at the beginning to create a relative url.
Semgrep rule
ruby.rails.security.audit.xss.templates.dangerous-link-to.dangerous-link-to4.D. Variable in <script> block
Template variables placed directly into JavaScript or similar are now directly in a code execution context. Normal HTML escaping will not prevent the possibility of code injection because code can be written without HTML characters. This creates the potential for XSS vulnerabilities, or worse.
Example:
<script>var name = <%= name %>;</script>
References
- Template engines: Why default encoders are not enough
- Protecting against XSS in Rails - JavaScript contexts
escape_javascript
documentation
Mitigation
Ban template variables in <script>
blocks. Alternatively, If necessary, use the the escape_javascript
function or its alias, j
. Review each usage carefully and exempt with # nosem
.
Semgrep rule
ruby.rails.security.audit.xss.templates.var-in-script-tag.var-in-script-tagNot finding what you need in this doc? Ask questions in our Community Slack group, or see Support for other ways to get help.