#27 Cross Site Scripting
Cross-site scripting is another common security issue to consider when developing web applications. It happens when you allow the users of your site to enter HTML or JavaScript directly. In the website below we’re not escaping the input that’s entered in the comments textbox, which leaves our site vulnerable.
If we enter JavaScript into the comment box, surrounded by <script> tags, that script will be executed when the page reloads and every time it it viewed afterwards. For example if we entered <script>alert(’hello’)</script> into the comments box and clicked the button then every subsquent time the page is viewed the user would see an alert box.
If we look at the source of the page we can see the JavaScript embedded in it.
<h2>Comments</h2> <p>I'll start on this now - Paul</p> <p><script>alert('hello!');</script></p> <hr/>
The JavaScript embedded in the page.
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 h
.
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 CGI::escapeHTML()
.
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 h
.