Neil
Neil

Reputation: 5178

Rails: How to escape_javascript with client-side AJAX request

How do I escape a collection when I send it via to_json from the controller action directly client-side?

Short Example:

def some_action
  @blogs = Blog.all
  respond_to do |format|
    format.json {render json: @blogs} # currently dangerous, @blogs is not sanitized.
  end
end

As an example: assume one of the @blogs records in that collection has this data input from a hacker:

@blogs.first.title
  => <script>alert('Site now hacked if this javascript runs!')</script>

When the @blogs get rendered in the browser: I want to escape the @blogs content so that the javascript will not get triggered from that hacked entry.

Longer Example:

For the code: the controller action code above would be exactly the same. Below is the client-side javascript:

app/assets/javascripts/blogs.js

$("body").on('change', '[data-action="blogger_sel"]', function() {
  var blogs_selection = $(this).closest('[data-parent-for="blogs_sel"]').find('[data-action="blogs_sel"]');
  $.ajax({
    url: "/blogs",
    type: "GET",
    data: {blogger_id: $(this).val()},
    success: function (data) {
      blogs_selection.children().remove();
      $.each(data, function (index, item) {
        blogs_selection.append('<option value=' + item.id + '>' + item.title + '</option>');
       });
     }
   })
 });

So up above: the part that I am concerned about is value.id and value.title. Those are things that could be dangerous if I do not escape them. I want to make sure it is escaped so that any dangerous input will be rendered harmless.

Upvotes: 1

Views: 905

Answers (2)

Neil
Neil

Reputation: 5178

Below is a solution. Keep in mind that it is often times a good idea to sanitize data before it gets persisted into the database as well. Also: it is preferable to sanitize server-side before sending the response to the requester:

app/assets/javascripts/blogs.js

$("body").on('change', '[data-action="blogger_sel"]', function() {
  var blog_sel = $(this).closest('[data-parent-for="blog_sel"]').find('[data-action="blog_sel"]');
  $.ajax({
    url: "/blogs",
    type: "GET",
    data: {blogger_id: $(this).val()},
    success: function (data) {
      blog_sel.children().remove();
      $.each(data, function (index, item) {
        blog_sel.append($('<option>', {
          value: item.id,
          text : item.title
        }));
      });
    }
  })
});

Do not append options the following way because it will execute dangerous hacks:

blogs_selection.append('<option value=' + value.id + '>' + value.title + '</option>');

Upvotes: 1

struthersneil
struthersneil

Reputation: 2750

You're dealing with an unsanitized HTML string like any other. You need to make it safe before inserting it on your page. All you really need to do is replace the < and > characters with &lt; and &gt;.

Using this controller as an example:

class SomethingController < ApplicationController
  def jsontest
    render json: { blogs: ["<script>alert(1);</script>"] }
  end
end

jQuery can do this for you, if you don't mind the clunkiness:

$.ajax({
  type: 'GET',
  url: '/something/jsontest', 
  dataType: 'json',
  success: function (data) { 
    console.log($('<div>').text(data['blogs'][0]).html()); 
  }
})

> &lt;script&gt;alert(1);&lt;/script&gt;

You could also look at using ActionView::Helpers::SanitizeHelper#sanitize on the controller side, which will clobber any HTML tags it finds. (Normally this is only available to views, so you could either make a view to render your JSON or include ActionView::Helpers::SanitizeHelper in your controller). Or do both!

Upvotes: 0

Related Questions