XSS vulnerability in Rails (merged params) - ruby-on-rails

I recently discovered (thanks to a bug bounty user) that my site has XSS vulnerabilities. After some playing around, I discovered the cause is in our locale switch in the navbar, such as:
<%= link_to "日本語", params.merge(locale: "ja", only_path: true), target:"_self", onClick:"ga('send','event','Navbar','Link Clicked','Language setting ja');" %>
This allows malicious code to be freely injected but I cannot for the life of me work out how to fix it without either breaking the locale switch or the creation of alternative issues.
I have checked other XSS related posts on here but I think the issue is a little bit above my ability to even comprehend, let alone fix right now. If anyone has any advice on how to prevent this XSS problem, I will be extremely grateful as it has become quite stressful!

The answer ended up being ridiculously simple so I will post here just in case anyone else ever searches for a similar problem.
As per http://guides.rubyonrails.org/i18n.html#setup-the-rails-application-for-internationalization I started by adding this to the application_controller.rb file:
def default_url_options(options={})
{ locale: I18n.locale }
end
With that, I could simply delete most of the original (vulnerable) locale switch and now have:
<li><%= link_to "日本語", locale: "ja" %></li>
Vulnerability removed yet functionality retained. Really was that simple. Dealing with legacy code can be a pain at times, thankfully this wasn't anywhere near as complicated as I initially assumed.

Related

Why is my view being flagged as an XSS vulnerability?

I have a show route that displays the contents of my article
Controller:
def show
#article = Article.find(params[:id])
end
View:
...
<li class="content"><%= #article.content.html_safe %></li>
...
When running Brakeman, it flags the above as a potential Cross-Site Scripting (XSS) vulnerability
Unescaped model attribute near line 34: Article.find(params[:article_id]).content
I'm trying to figure out what XSS really is and what makes this vulnerable? If someone injected some malicious text or input into the params[:id] field in the route (e.g. /articles/BAD_INPUT) then Article.find() would not find the article and raise an error
The only way the view renders is if a valid Article record is found, right? How else can the user manipulate this?
Thanks!
Edit: I should definitely protect agains the case when an Article is not found and an error is raised, but I figured that's more of a bad design rather than a security vulnerability
Brakeman is warning because the code is taking information from the database and outputting it in a view without escaping it. By default, Brakeman treats values from the database as potentially dangerous. In this case, you probably know the article content is intended to be HTML and is safe to output without escaping it. If you wish to not warn about XSS with values from the database, you can use the --ignore-model-output option.
(The issue you linked in your answer is not really related. Brakeman is expected to warn about uses of raw/html_safe with potentially dangerous values.)
Ok found the answer after some digging.
It apparently has to do with html_safe and raw (which is just an alias for html_safe). The issue is specific to Brakeman and outlined here
That thread says the issue is acknowledged and solved, but it still didn't work for me using the latest version.
I solved it as follows
Controller:
def show
#article = Article.find(params[:id])
#article_content = view_context.raw(#article.content)
end
View:
...
<li class="content"><%= #article_content %></li>
...
Essentially we're marking the Article content as html_safe (using the alias raw()) beforehand so it doesn't cause an issue in the view.
Messier than I'd like, but it works
If you are storing html on your model and you are on Rails 4.2++, you could consider using the sanitize helper (docs).
For example, you can allow specific tags (e.g. links):
<%= sanitize #article.content, tags: %w(a), attributes: %w(href) %>
The docs have a lot of good examples.
Here's another write-up if you want some more information.

How to prevent spam in rails?

Does anyone know how to stop spam in rails? I've tried many solutions, which all has failed.
I have tried:
Captcha: I am currently not a fan of captcha since it interrupts when the user is signing up but upon putting captcha on signup page bots still managed to get passed it.
Honeypot: I've created a hidden field set the max character value to 0 and push the form -9999px off the screen and for some reason that does not stop the spam.
askimet: While this works well with wordpress it comes with a monthly fee so I am not interested in something like this.
Is there anyway to stop the spam bots in rails from signing up?
For a honeypot solution, you can use invisible_captcha.
It works pretty nice for small and medium sites, with a simple and configurable approach.
More or less:
In your form:
<%= form_tag(create_topic_path) %>
<%= invisible_captcha %>
...
<% end %>
In your controller:
class TopicsController < ApplicationController
invisible_captcha only: [:create, :update]
...
end
Aggressive Spam Bots are using (most of the times) the same tld or selected names, strings or numbers.
You can try Filters Spam to filter out Words etc. you don't like in your App.
And if you have a Really Big Problem with Spam just go with Rakismet (Ruby Akismet), for only 4.99/Month. I dont think this is to much to ask.
Try projecthoneypot gem https://github.com/cmaxw/project-honeypot. It uses Http:BL service which maintains a list of suspicious IP's.Check this out http://www.projecthoneypot.org/httpbl.php

How to secure link_to #variable cross site scripting vulnerabilities

I've just started using the brakeman gem to explore my rails app for security vulnerabilities.
I've managed to get everything tidy except for several cross site scripting warnings.
These all share the following in common:
They're all link_to tags
They all have instance variables in the class, alt or title
attributes
The instance variables all represent an active record query that
includes associated models
The instance variables are all "commentable". This describes a polymorphic association for user generated comments, similar in approach to the revised version of this Railscast.
e.g
<%= link_to "Click" , :class=> #model.association.attribute, :alt=> #model.association.attribute, :title=> #model.association.attribute, #model.association %>
where
#model = #commentable = Model.includes(:association1, association2: {:nested-association1, :nested-association2}).find(params[:id])
Is this something I need to be concerned about/ take action for? I thought Rails 3.2 escapes these by default.
I'd welcome advice to help me understand this issue better, and identify what steps I should take, if any.
I was unable to reproduce any warnings from the code you provided. What version of Brakeman are you using? What was the actual warning (redacted as necessary)?
I suspect you are getting warnings because user input is being detected in the href value of the link. See this pull request for more information about why this can be dangerous.
Unfortunately, without more information, I cannot tell if this is a false positive that needs to be fixed or a legitimate warning.
Edit:
Okay, now I am seeing the warning when testing with #model = #commentable = ... This is a problem with how Brakeman is handling the assignment.
If you are linking to an instance of a model, there should be no warning. If you are linking to a model attribute then this is counted as user input.
Yes, Rails will escape HTML, but it does not deal with links beginning with javascript: or data: which can be used for XSS.

What is best strategy to handle exceptions & errors in Rails?

I was wondering if people would share their best practices / strategies on handling exceptions & errors. Now I'm not asking when to throw an exception ( it has been throroughly answered here: SO: When to throw an Exception) . And I'm not using this for my application flow - but there are legitimate exceptions that happen all the time. For example the most popular one would be ActiveRecord::RecordNotFound. What would be the best way to handle it? The DRY way?
Right now I'm doing a lot of checking within my controller so if Post.find(5) returns Nil - I check for that and throw a flash message. However while this is very granular - it's a bit cumbersome in a sense that I need to check for exceptions like that in every controller, while most of them are essentially the same and have to do with record not found or related records not found - such as either Post.find(5) not found or if you are trying to display comments related to post that doesn't exist, that would throw an exception (something like Post.find(5).comments[0].created_at)
I know you can do something like this in ApplicationController and overwrite it later in a particular controller/method to get more granular support, however would that be a proper way to do it?
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordInvalid do |exception|
render :action => (exception.record.new_record? ? :new : :edit)
end
end
Also this would work in case Post.find(5) not found, but what about Post.find(5).comments[0].created_at - I meant I can't throw a full blown exception if the post exists but has not comments, right?
To summarize so far I was doing a lot of manual checking using if/else/unless or case/when ( and I confess occasionally begin/rescue) and checking for nil? or empty?, etc. , but there's got to be a better way it seems.
REPLIES:
#Milan:
Hi Milan
Thanks for a reply - I agree with what you said, and I think I misused the word exception. What I meant is that right now I do a lot of things like:
if Post.exists?(params[:post_id])
#p = Post.find(params[:post_id])
else
flash[:error] = " Can't find Blog Post"
end
And I do a lot of this kind of "exception handling", I try to avoid using begin/rescue. But it seems to me that this is a common enough result/verification/situation that there should be a DRYer way to do this, don't you?
How would you do this kind of check?
Also how would handle it in this case?
Let's say you want to display comment created date in your view:
Last comment for this post at : <%= #post.comments[0].created_at %>
And this post doesn't have any comments.
You can do
Last comment for this post at : <%= #post.comments.last.created_at unless #post.comments.empty? %>
You could do a check in controller. Etc. There are several ways to do it. But what is the "best" way to handle this?
The fact that you do a lot of manual checking for exceptions suggests that you are just not using them right. In fact, none of your examples is exceptional.
As for the non-existing post - you should expect your API users (eg. a user using your web via browser) to ask for non-existing posts.
Your second example(Post.find(5).comments[0].created_at) is not exceptional either. Some posts just don't have comments and you know it up front. So why should that throw an exception?
The same is the case with the ActiveRecord::RecordInvalid example. There's just no reason to handle this case by means of an exception. That a user enters some invalid data into a form is a pretty usual thing and there is nothing exceptional about it.
Using the exception mechanism for these kinds of situations might be very convenient in some situations, but it's incorrect for the reasons mentioned above.
With that said, it doesn't mean you can't DRY the code which encapsulates these situations. There's a pretty big chance that you can do it at least to some extent since these are pretty common situations.
So, what about the exceptions? Well, the first rule really is: use them as sparsely as possible.
If you really need to use them there are two kinds of exceptions in general (as I see it):
exceptions that don't break the user's general workflow inside your app (imagine an exception inside your profile picture thumbnail generation routine) and you can either hide them from the user or you just notify him about the problem and its consequences when neccessary
exceptions that preclude the user from using the app at all. These are the last resort and should be handled via the 500 internal server error in web applications.
I tend to use the rescue_from method in the ApplicationController only for the latter, since there are more appropriate places for the first kind and the ApplicationController as the topmost of the controller classes seems to be the right place to fall back to in such circumstances (although nowadays some kind of Rack middleware might be even more appropriate place to put such a thing).
-- EDIT --
The constructive part:
As for the first thing, my advice would be to start using find_by_id instead of find, since it it doesn't throw an exception but returns nil if unsuccessful. Your code would look something like this then:
unless #p = Post.find_by_id(params[:id])
flash[:error] = "Can't find Blog Post"
end
which is far less chatty.
Another common idiom for DRYing this kind of situations is to use the controller before_filters to set the often used variables (like #p in this case). After that, your controller might look as follows
controller PostsController
before_filter :set_post, :only => [:create, :show, :destroy, :update]
def show
flash[:error] = "Can't find Blog Post" unless #p
end
private
def set_post
#p = Post.find_by_id(params[:id])
end
end
As for the second situation (non-existing last comment), one obvious solution to this problem is to move the whole thing into a helper:
# This is just your way of finding out the time of the last comment moved into a
# helper. I'm not saying it's the best one ;)
def last_comment_datetime(post)
comments = post.comments
if comments.empty?
"No comments, yet."
else
"Last comment for this post at: #{comments.last.created_at}"
end
end
Then, in your views, you'd just call
<%= last_comment_datetime(post) %>
In this way the edge case (post without any comments) will be handled in it's own place and it won't clutter the view.
I know, none of these suggests any pattern for handling errors in Rails, but maybe with some refactorings such as these you'll find that a great deal of the need for some kind of strategy for exception/error handling just disappears.
Exceptions are for exceptional circumstances. Bad user input is typically not exceptional; if anything, it's quite common. When you do have an exceptional circumstance, you want to give yourself as much information as possible. In my experience, the best way to do that is to religiously improve your exception handling based on debugging experience. When you bump into an exception, the very first thing you should do is write a unit test for it. The second thing you should do is determine if there is more information that can be added to the exception. More information in this case usually takes the form of catching the exception higher up the stack and either handling it or throwing a new, more informative exception that has the benefit of additional context. My personal rule is that I don't like catching exceptions from much more than three levels up the stack. If an exception has to travel any further than that, you need to be catching it earlier.
As for exposing errors in the UI, if/case statements are totally OK as long as you don't nest them too deeply. That's when this kind of code gets hard to maintain. You can abstract this if it becomes a problem.
For instance:
def flash_assert(conditional, message)
return true if conditional
flash[:error] = message
return false
end
flash_assert(Post.exists?(params[:post_id]), "Can't find Blog Post") or return

Rails: blank page - no errors or stack trace

I've been trying to fix a bug in the Rails app I'm developing, and I keep getting a blank screen with no errors. I haven't found anything helpful in development.log, either (though it does show queries being run and such).
Finally, I started to wonder if it's somehow set not to show errors anymore. I tried commenting out a necessary route, and sure enough, I got a blank page instead of the error and stack trace I expected.
What might cause this?
(I wondered if maybe I'm accidentally running production mode and errors aren't supposed to show then, but development.log is being appended, and if I open script/console and echo ENV['RAILS_ENV'], it says development.)
How I broke it, how I fixed it, and how I learned my lesson
I figured out the problem while lying in bed this morning, and it boils down to "I did something stupid." (Doesn't it always?)
The smart part
First, the smart part: I followed some neat advice about setting up access control and permissions. It gives you some great a great syntax for saying what's allowed, like this:
<%= link_to 'Delete', #photo, :confirm => "Really delete this photo?", :method => :delete if current_user.can_delete?(#photo)%>
(The current_user bit comes from the Restful-authentication plugin.)
The author also shows how to handle the case where a user tries to type in a URL for which you haven't given them a link. It involves setting up a special exception class, which subclasses StandardError, and handling it with something like a 401.html - access denied.
The stupid part
What I did that was stupid was I followed his example blindly. He shows this:
def rescue_action(e)
case e
when SecurityTransgression
head :forbidden
end
end
...which handles the SecurityTransgression fine, but breaks the default error handling in Rails. (I'm sure the author knows this and dealt with it, but he didn't discuss it.)
The solution
The solution was to add two lines:
def rescue_action(e)
case e
when SecurityTransgression
head :forbidden
else
super
end
end
The "else super" part says "if I haven't specified anything here, let the inherited rescue_action method handle it."
Now I'm getting the correct stack trace for my original problem, and will proceed to troubleshoot it.
The lesson: be careful when you mess with exception handling, and make sure the default case still works!
Check your default route and make sure the view isn't just empty.

Resources