CVE-2018-3769 (Ruby, published 2018-07-05T16:29Z)
Description: value: ruby-grape ruby gem suffers from a cross-site scripting (XSS) vulnerability via "format" parameter.
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/ruby-grape/grape
Files changed:
CHANGELOG.md
lib/grape/middleware/error.rb
spec/grape/api_spec.rb
spec/grape/middleware/exception_spec.rb
Commit message: When returning an HTML error, make sure it's safe (#1763)

* When calling into an API specifying a crafted format that is HTML,
the returned error renders the HTML back to the user, causing a potential XSS
issue.  For example:

http://example.com/api/endpoint?format=%3Cscript%3Ealert(document.cookie)%3C/script%3E

Renders as html:

The requested format '<script>alert(document.cookie)</script>' is not supported.

When an error generates html back to the user, make sure it's properly escaped.

Fixes issue #1762

* Add changelog entry

* Use a method that also works in rails3

* Add spec formatting for older rails/activesupport version


Vulnerable code:
      expect(last_response.body).to eq("The requested format 'txt' is not supported.")
      expect(last_response.body).to eq("{\"error\":\"The requested format 'txt' is not supported.\"}")
      expect(last_response.body).to eq('{"error":"rain!"}')
      expect(['{"error":"rain!","detail":"missing widget"}',
              '{"detail":"missing widget","error":"rain!"}']).to include(last_response.body)
      expect(last_response.body).to eq('{:custom_formatter=>"rain!"}')

Fixed code:

* [#1762](https://github.com/ruby-grape/grape/pull/1763): Fix unsafe HTML rendering on errors - [@ctennis](https://github.com/ctennis).
require 'active_support/core_ext/string/output_safety'
        if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
          message = ERB::Util.html_escape(message)
        end
      if ActiveSupport::VERSION::MAJOR == 3
        expect(last_response.body).to eq('The requested format &#x27;txt&#x27; is not supported.')
      else
        expect(last_response.body).to eq('The requested format &#39;txt&#39; is not supported.')
      end
      if ActiveSupport::VERSION::MAJOR == 3
        expect(last_response.body).to eq('{&quot;error&quot;:&quot;The requested format &#x27;txt&#x27; is not supported.&quot;}')
      else
        expect(last_response.body).to eq('{&quot;error&quot;:&quot;The requested format &#39;txt&#39; is not supported.&quot;}')
      end
    end
  end

  context 'with unsafe HTML format specified' do
    it 'escapes the HTML' do
      subject.content_type :json, 'application/json'
      subject.get '/something' do
        'foo'
      end
      get '/something?format=<script>blah</script>'
      expect(last_response.status).to eq(406)
      if ActiveSupport::VERSION::MAJOR == 3
        expect(last_response.body).to eq('The requested format &#x27;&lt;script&gt;blah&lt;/script&gt;&#x27; is not supported.')
      else
        expect(last_response.body).to eq('The requested format &#39;&lt;script&gt;blah&lt;/script&gt;&#39; is not supported.')
      end
      expect(last_response.body).to eq('{&quot;error&quot;:&quot;rain!&quot;}')
      expect(['{&quot;error&quot;:&quot;rain!&quot;,&quot;detail&quot;:&quot;missing widget&quot;}',
              '{&quot;detail&quot;:&quot;missing widget&quot;,&quot;error&quot;:&quot;rain!&quot;}']).to include(last_response.body)
      expect(last_response.body).to eq('{:custom_formatter=&gt;&quot;rain!&quot;}')