27: Cross-site Scripting
<h2>Comments</h2> <p>I'll start on this now - Paul</p> <p><script>alert('hello!');</script></p> <hr/>
Causing an alert box to be shown on a page is annoying, but cross-site scripting can be used for much more malicious purposes. For example it could be used to read the cookies for the site’s other users. We’ll just alert the cookie, but it could just as easily be sent to a remote server where the session id information in the cookie could be used to hijack another user’s session1.
The session key in the cookie.
Stopping The Attacks
To stop these attacks you need to escape any user input before you display it on the screen. We’re currently taking the comment’s content directly from the database and outputting it into the HTML stream. Rails provides a method simply called h to escape the content before it is output.
<% @task.comments.each do |comment| %> <p><%= h(comment.content) %></p> <% end %>
The text entered in to the comments box is now safely escaped.
Now, when we reload the page we can see that the script is no longer executed. The
h command escapes the angle brackets so that the comments entered by the user are displayed safely.
Rails also provides a
sanitize method which allows certain tags to be included via a white list. To be on the safe side and escape any HTML that may be entered by a user it is safer to stick to
An Alternative Approach
Instead of escaping the user’s input when sending it out to the browser we could escape it when we store it in the database. The
h method isn’t available in a controller so instead we’ll use
def create @task = Task.find(params[:task_id]) @comment = @task.comments.new(params[:comment]) @comment.content = CGI::escapeHTML(params[:comment][:content]) @comment.save redirect_to task_path(@task) end
Escaping the comment’s HTML before it is stored in the database.
Storing escaped HTML in the database is fine as long as you only ever want to display that data in a browser. If you think you might need it in its unescaped form as well then it’s better to escape the output with