Josh Lane Code that I felt like sharing

Sinatra ERB XSS escaping

While Rails escapes HTML by default, Sinatra requires some handholding to produce a similar protection. The following describes two simple solutions.

Setup

Given a basic Sinatra application

# lib/app.rb
class App < Sinatra::Base
  get '/users' do
    @users = User.all

    erb :users
  end
end

and a corresponding ERB template

<%# lib/views/users.erb %>
<% @users.each do |user| %>
  <tr>
    <td><%= user.id %></td>
    <td><%= user.name %></td>
  </tr>
<% end %>

any user-defined input will show the unescaped value. This setup is immediately vulnerable to persistent XSS attacks. For instance, a User with a name like <script src=http://www.example.com/malicious-code.js></script> will appear as:

<tr>
  <td>1</td>
  <td><script src=http://www.example.com/malicious-code.js></script></td>
</tr>

Offensive solution

Use ERB::Util#h on the specific inputs that should be escaped.

  • include ERB::Util
# lib/app.rb
class App < Sinatra::Base
  include ERB::Util
end
  • Explicitly escape desired inputs
-    <td><%= user.id %></td>
+    <td><%= h user.name %></td>

This produces the desired result of:

<tr>
  <td>1</td>
  <td>&lt;script src=http://www.example.com/malicious-code.js&gt;</script></td>
</tr>

While this solution is very simple, if a developer forgets to escape a value the site becomes vulnerable.

Defensive solution

Assume all inputs are possible sources of attack and escape by default.

  • Add erubis to your Gemfile
gem 'erubis'
  • Add escape_html ERB setting in Sinatra
class App < Sinatra::Base
  set :erb, { escape_html: true }
end

This produces the desired result of:

<tr>
  <td>1</td>
  <td>&lt;script src=http://www.example.com/malicious-code.js&gt;</script></td>
</tr>

without alterations to the ERB file.

Note: There are a few cases when you absolutely do not want to render inputs as escaped. In order to get raw inputs use ==.

  • With a yield in a layout
# lib/views/layouts/application.erb
<body>
  <%== yield %>
</body>
  • Partial templates
<% users.each do |user| %>
  <%== erb ':user/show', user: user %>
<% end %>