Introduction
I recently did a web application penetration testing assessment for an application that used Ruby on Rails. Besides checking for all of the common web application vulnerabilities, such as the OWASP Top 10 and other issues that could exist on any web platform I also wanted to dive deeply into the framework of Rails and see what issues I could identify at that layer.
In this blog post I’ll walk you through my findings and give you a checklist of common Ruby on Rails issues that we’ve seen. If you’re a developer this is a great guideline to make sure you’ve done everything at the framework level you can. If you’re a tester this can be a checklist to find issues that may have slipped through the cracks.
Of course, any application should be externally pen tested by an expert. Since Security Engineers live and breathe security they often understand vulnerabilities and edge cases that would be difficult for a developer or tester to keep abreast of. If you’d like us to take a look at your application, check out our services page or contact us for more information.
Issues:
Ensure that Rails is patched. Identify the version of rails that is currently being used. Ensure that it is not vulnerable to any known exploits. The current version of Rails at the time of this writing is 4.2.1, but you can always check rubyonrails.org for the latest.
Identify versions of all the other gems that are used. The best way to do this is by looking at the file “Gemfile” that is located by default in RAILS_ROOT. Many gems might add some great functionality for the application but have behavior that could potentially be vulnerable as well. For example: The act_as_paranoid gem has a nice feature that does not permanently delete records, allowing for easy restoration. However, this might have legal or compliance consequences for PII or other sensitive data that should be destroyed after specific amount of time, it could also increase your risk of damage if a data breach were to occur. You can browse through the issues list of most gems on github or perform your own code review on critical gems.
SQL Injection. A lot of what Rails does with databases is secure by default. For example: By default the find () and the multiple find_by_<whatever> are all mostly secure by default and internally parameterize user input. However there are ways to write insecure queries. The find_by_sql() and execute() methods are 2 ways this can happen. These are good terms to grep through gems for, by the way, we once found an exploitable sql injection vulnerability in a required gem by searching through the source for find_by_sql(). It’s a good idea to look at the site http://rails-sqli.org/ which explains a number of ways in which insecure SQL queries are written. Notice that the entire page deals with examples written the 'wrong' way. So, be careful while comparing your code with the examples on that page.
Secrets in initialization files. Rails reads a number of files during the initialization process. It is possible that there are secrets hard-coded in many of them or code that is not as secure as that written in the other, more visible parts of the application. While auditing code, make sure that you also look at all the initialization files. A good reference to understand what files Rails looks at, during initialization is http://guides.rubyonrails.org/initialization.html. Do not hard code secrets in the code.
There are a couple of options to secure secrets. They could be stored in user-specific or PID-specific environment variables. This is what Rails recommends by default. Alternatively, they could be stored in a configuration file secured by setting strong OS level permissions. Ideally, the configuration file should be encrypted and the key stored in a keyring protected by the OS. On Windows, DPAPI (https://msdn.microsoft.com/en-us/library/ms995355.aspx) can be used to protect the key.
Cross-site scripting. Rails escapes content by default since Rails version 2.2, when rendered in a view between the <%= and %> tags. However if html_safe is used instead on user input, you are allowlisting the data and telling Rails to NOT protect the user input. Doing this, bypasses the default protection, and is unsafe unless it is explicitly converted/encoded before being rendered. Using raw directly on user input has a similar affect, it is also is a bad practice and will cause JavaScript in user input to be executed. This is because the user input is rendered directly to the user’s browser without the user-input ever being output encoded.
Here are links to the official documentation for raw (http://apidock.com/rails/ActionView/Helpers/OutputSafetyHelper/r aw) and html_safe (http://apidock.com/rails/String/html_safe). Here is a great RailsCast on the subject that you should definitely see - http://railscasts.com/episodes/204-xss-protection-in-rails-3.
If you are rendering JSON to the browser, use the render json call – render json: user_input_variable. Here is a good read about the same: http://blog.bigbinary.com/2012/05/10/xss-and- rails.html.
Here is a link to the official documentation as well - http://guides.rubyonrails.org/layouts_and_rendering.html#using-render.
Cross-Site Request Forgery. Most web applications will have actions that the user can perform and change server state. Hence automatically, CSRF becomes a valid attack for an attacker to try and perform. Rails has built-in protection against CSRF. If you see that application.rb has a line which says protect_from_forgery, and it’s being run, you're protected for all the POST requests that make state changes in the application.
For PUT and DELETE, invariably a token will be added as a custom header like X-CSRF-TOKEN with every request. This is usually done inside some common Javascript file, which reads a response...and pulls the server-set token from a hidden field and sets it in every request. It is highly possible though, that all these tokens never expire and are used across sessions even after days – as Rails doesn’t do
that by default – so make sure you do it.
Lastly, focus on GET requests. In general GET should not be used to perform a state change on the backend. If there any GETs which perform a state change in the backend these will have to be protected from CSRF. By default protection is not available for any GET requests. Ensure you don’t use GET to change state. Here is a good reference about this - http://guides.rubyonrails.org/security.html#csrf- countermeasures
Session Fixation. The session ID is generated usually when a user logs in to the application. By default though, the session ID doesn't change after the user logs in successfully. An attacker can misuse this behavior and gain access to a user’s session and eventually data as well. So, make sure that you call reset_session as soon as the user logs in successfully. Here is a good reference about this - http://guides.rubyonrails.org/security.html#session-fixation-countermeasures
Mass assignment. Mass assignment basically means setting the values of a number of database records via user input. Most times, the application will intend for the user to change only very specific fields in the database via user input (like say changing your date of birth - should change only your DOB and the user shouldn't be able to change the date the record was modified - for example.). If the defenses against mass-assignment aren't there – this kind of attack is possible.
Rails 3 defended against this using attr_accessible but Rails 4 has something called strong parameters with the :permit keyword which defends against this type of an attack. A developer can explicitly mention the parameters whose values can be changed, using strong parameters. Ensure that every action in every controller defends against this type of attack. Here is a nice reference that explains this attack - http://code.tutsplus.com/tutorials/mass-assignment-rails-and-you--net-31695
Server headers. Adding security headers is a nice additional layer of security. Ensure that the default_headers directive is set centrally. Alternatively, ensure you have the Strict-Transport-Security, X-XSS-Protection, X- Content-Type-Options, X-Frame-Options and X-Content-Security-Policy headers configured with the most secure values. Here is a reference to learn more about this subject - https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet#Security-related_headers
If you allow a site other than your own to make changes to the content you host, ensure that you have configured CORS properly as well. Do not use the “*” wildcard in the Access-Control-Allow-Origin header, thus allowing any other website to modify content on your site. Here is a reference that explains this subject in greater detail - https://www.owasp.org/index.php/Ruby_on_Rail s_Cheatsheet#Cross_Origin_Resource_Sharing
Arbitrary URL redirects. This test is basically checking for arbitrary URL redirects to websites on the internet or even other intranet websites. Check if the render and redirect_to methods used in any ruby file, usually the controller – depend on user input. If yes, ensure that all the user input is canonicalized, and users are redirected only to sites that are allowlisted. Here are a couple of good references:
https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsh eet#Dynamic_Render_Paths
http://guides.rubyonrails.org/v2.3.11/layouts_and_rendering.html#using-render
Insecure File uploads. The public files and folders in Rails are in the public/ directory under the application root. Users should not be able to upload arbitrary files to the public/ directory or any other directory that is directly accessible by an end-user and then be able to view/execute their uploaded files. Here is a good reference that discusses this in greater detail- http://guides.rubyonrails.org/security.html#executable-code-in-file-uploads
Logging sensitive information. Applications usually have auditing and logging enabled and at times log all the values that users send to the application as part of their requests. Care should be taken that values that contain sensitive information should never ever be logged. Ensure that config.filter_parameters in one of the initialization files – usually application.rb (Rails 3) OR config/initializers/filter_parameters.rb (Rails 4). All the parameters that should NEVER be logged must be values. Here is a good reference - http://brettu.com/rails-daily-ruby-tips-160-rails-4-filter-parameter-logging-moved-to-initializers/.
Authorization bypass. It is possible that certain SQL queries when made are insecure. The form Model.find or Model.find_by_<whatever>(params[:id]) is used, which causes every value to be returned. Instead, only records for the current user must be returned. Either use something like current_user.<models>.find OR add a where clause to the query that returns only specific records.
Public controller actions. Methods that are public in the controller can be directly invoked. Let us assume that there were 2 methods like f1() and f2() in the controller. If the only way that f2() was supposed to be called...was inside f1()... then f2 ()must definitely be private or protected.
If f2() is declared as public an attacker could directly invoke f2() using a REST URL like https://host/controller/f2. That could be fairly dangerous, depending on what f2() actually does. The code must be audited to identify all public methods. To actually exploit this though, the routes in config/routes.rb needs to be understood and the actual URL needed to invoke any of these public methods needs to be identified. Here is a good reference - https://www.ruby-forum.com/topic/210344.
Using default routes. Ensure that there are no default routes present there. Using default routes could generate many routes that users should not ever access as they could end up modifying content without having the requisite authorization. Here is a good blog that discusses this problem – https://ihower.tw/blog/archives/3265.
Accessing these routes could allow a user to modify the backend database. Here is a nice reference that explains a bit more on this topic - http://guides.rubyonrails.org/v2.3.11/routing.html#default-routes.
Conclusion:
Overall though, I quite like Rails’ commitment to security. They attempt to do many things right – out-of-the-box which makes shooting one-self in the foot while developing stuff much harder. This blog discusses a number of the issues that affect Rails applications. If you address all the issues referred in this blog, in your RoR application, you are on your way towards making it more secure for all your users.