ruby.rails.security.audit.xss.avoid-render-text.avoid-render-text

profile photo of semgrepsemgrep
Author
6,305
Download Count*

'render text: ...' actually sets the content-type to 'text/html'. If external data can reach here, this exposes your application to cross-site scripting (XSS) attacks. Instead, use 'render plain: ...' to render non-HTML text.

Run Locally

Run in CI

Defintion

rules:
  - id: avoid-render-text
    metadata:
      source-rule-url: https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman/checks/check_render_inline.rb
      owasp:
        - A07:2017 - Cross-Site Scripting (XSS)
        - A03:2021 - Injection
      cwe:
        - "CWE-79: Improper Neutralization of Input During Web Page Generation
          ('Cross-site Scripting')"
      references:
        - https://brakemanpro.com/2017/09/08/cross-site-scripting-in-rails#inline-renders---even-worse-than-xss
      category: security
      technology:
        - rails
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - audit
      likelihood: LOW
      impact: MEDIUM
      confidence: LOW
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Cross-Site-Scripting (XSS)
    message: "'render text: ...' actually sets the content-type to 'text/html'. If
      external data can reach here, this exposes your application to cross-site
      scripting (XSS) attacks. Instead, use 'render plain: ...' to render
      non-HTML text."
    languages:
      - ruby
    severity: WARNING
    pattern: "render text: ..."
    fix-regex:
      regex: "text:"
      replacement: "plain:"

Examples

avoid-render-text.rb

# cf. https://github.com/rails/rails/blob/939fe523126198d43ecedeacc05dd7fdb1eae3d9/actionpack/test/controller/action_pack_assertions_test.rb


# frozen_string_literal: true

require "abstract_unit"
require "controller/fake_controllers"

class ActionPackAssertionsController < ActionController::Base
  def nothing() head :ok end

  # ok: avoid-render-text
  def hello_xml_world() render template: "test/hello_xml_world"; end

  def assign_this
    @howdy = "ho"
    render inline: "Mr. Henke"
  end

  def render_based_on_parameters
    # ok: avoid-render-text
    render plain: "Mr. #{params[:name]}"
  end

  def render_url
    # ok: avoid-render-text
    render html: "<div>#{url_for(action: 'flash_me', only_path: true)}</div>"
  end

  def render_text_with_custom_content_type
    # ok: avoid-render-text
    render body: "Hello!", content_type: Mime[:rss]
  end

  def session_stuffing
    session["xmas"] = "turkey"
    # ruleid: avoid-render-text
    render text: "ho ho ho"
  end

  def raise_exception_on_get
    raise "get" if request.get?
    # ruleid: avoid-render-text
    render text: "request method: #{request.env['REQUEST_METHOD']}"
  end

  def raise_exception_on_post
    raise "post" if request.post?
    # ok: avoid-render-text
    render plain: "request method: #{request.env['REQUEST_METHOD']}"
  end

  def render_file_absolute_path
    # ok: avoid-render-text
    render file: File.expand_path("../../README.rdoc", __dir__)
  end

  def render_file_relative_path
    # ok: avoid-render-text
    render file: "README.rdoc"
  end
end

# Used to test that assert_response includes the exception message
# in the failure message when an action raises and assert_response
# is expecting something other than an error.
class AssertResponseWithUnexpectedErrorController < ActionController::Base
  def index
    raise "FAIL"
  end

  def show
    # ok: avoid-render-text
    render plain: "Boom", status: 500
  end
end