Upgrade to Pro — share decks privately, control downloads, hide ads and more …

20 actionable tips to secure your Rails application - Rails Israel '15

20 actionable tips to secure your Rails application - Rails Israel '15

Daniel Lobato García

November 24, 2015
Tweet

More Decks by Daniel Lobato García

Other Decks in Technology

Transcript

  1. RAILS ISRAEL 2015 4 NEVER USE GET TO MODIFY PROBLEM

    – CROSS SITE SCRIPTING Any request that modifies a resource should be triggered GET is loaded automatically. <img src="http://example.com/projects/1/destroy“ />
  2. RAILS ISRAEL 2015 5 NEVER USE GET TO MODIFY PROBLEM

    – CROSS SITE SCRIPTING Any request that modifies a resource should be triggered GET is loaded automatically. <img src="http://example.com/projects/1/destroy“ /> <img src="http://example.com/users/admin/edit?password=lol“ />
  3. RAILS ISRAEL 2015 6 NEVER USE GET TO MODIFY PROBLEM

    – CROSS SITE SCRIPTING Real example – traffic in LA - bit.ly/1r03DpC <img src="http://latraffic.com/light/329?color=green“ />
  4. RAILS ISRAEL 2015 7 NEVER USE GET TO MODIFY SOLUTION

    – USE PUT, PATCH, POST If any of these routes use PUT, PATCH or POST nothing happens <img src="http://example.com/projects/destroy“/> <img src="http://example.com/users/admin“/> <img src="http://example.com/light/392/on“/>
  5. RAILS ISRAEL 2015 8 PROTECT PUT, POST, DELETE PROBLEM –

    UNVERIFIED REQUESTS <!---- victim browser loads our bait link ----> <img width=”300000” height=”300000” onmouseover=" var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'PUT'; f.data = '{ \'password\' : 'hacked!' }'; f.action = 'http://example.com/user/admin'; f.submit(); return false; ">
  6. RAILS ISRAEL 2015 9 PROTECT PUT, POST, DELETE SOLUTION –

    UNVERIFIED REQUESTS class ApplicationController protect_from_forgery :with => :exception rescue_from ActionController::InvalidAuthenticityToken do |exception| sign_out_user end
  7. RAILS ISRAEL 2015 10 PROTECT PUT, POST, DELETE verified_request? protect_from_forgery

    verify_authenticity_token execute request handle_unverified_request false true
  8. RAILS ISRAEL 2015 11 JSONP LEAKS PROBLEM – SENSITIVE DATA

    SERVED OVER JSON Rails scaffolding generates by default XML & JSON renderers curl http://example.com/users/1.json
  9. RAILS ISRAEL 2015 12 JSONP LEAKS PROBLEM – SENSITIVE DATA

    SERVED OVER JSON When here the JavaScript renderer emits e.g. JQuery fragments like: $("#messages").html(.. # load inbox from JSON
  10. RAILS ISRAEL 2015 13 JSONP LEAKS PROBLEM – SENSITIVE DATA

    SERVED OVER JSON When here the JavaScript renderer emits e.g. JQuery fragments like: $("#messages").html(.. # load inbox from JSON Attackers can just <script src="http://example/inbox/messages?format=js"> And do whatever they want with the inbox messages
  11. RAILS ISRAEL 2015 14 JSONP LEAKS PROBLEM – SENSITIVE DATA

    SERVED OVER JSON Phishing with JSONP leaks easy as pie → # Hi @poorSoul, have you seen what they say about you here? Link
  12. RAILS ISRAEL 2015 15 JSONP LEAKS PROBLEM – SENSITIVE DATA

    SERVED OVER JSON Phishing with JSONP leaks easy as pie → # Hi @poorSoul, have you seen what they say about you here? Link # * @poorSoul clicks *
  13. RAILS ISRAEL 2015 16 JSONP LEAKS PROBLEM – SENSITIVE DATA

    SERVED OVER JSON Phishing with JSONP leaks easy as pie → # Hi @poorSoul, have you seen what they say about you here? Link # * @poorSoul clicks * # Thanks for your inbox!
  14. RAILS ISRAEL 2015 17 USE SECURE HEADERS CSP, HSTS, etc...

    SOLUTION – SECURE HEADERS • github.com/twitter/secureheaders Alternatively: ActionDispatch::Response.default_headers = { 'X-Frame-Options' => 'DENY', 'X-Content-Type-Options' => 'nosniff', 'X-XSS-Protection' => '1;' }
  15. RAILS ISRAEL 2015 18 RENDERERS PROBLEM – XSS & ARBITRARY

    CODE EXECUTION render “Goodbye user!”
  16. RAILS ISRAEL 2015 19 RENDERERS PROBLEM – XSS & ARBITRARY

    CODE EXECUTION render “Goodbye #{params[:name]}”
  17. RAILS ISRAEL 2015 20 RENDERERS PROBLEM – XSS & ARBITRARY

    CODE EXECUTION Plain 'render' takes an 'inline' argument to render ERB render params[:name] + “, see you!”
  18. RAILS ISRAEL 2015 21 RENDERERS PROBLEM – XSS & ARBITRARY

    CODE EXECUTION Plain 'render' takes an 'inline' argument to render ERB def render(*args) render :text render :inline
  19. RAILS ISRAEL 2015 22 RENDERERS PROBLEM – XSS & ARBITRARY

    CODE EXECUTION Plain 'render' takes an 'inline' argument to render ERB render params[:name] curl 'example.com?name%5Binline%5D%3D%3C%25%3D%60rm+-rf %60%25%3E
  20. RAILS ISRAEL 2015 23 RENDERERS PROBLEM – XSS & ARBITRARY

    CODE EXECUTION Plain 'render' takes an 'inline' argument to render ERB render params[:name] curl 'example.com?name[inline]=<%=`rm -rf`%>
  21. RAILS ISRAEL 2015 24 RENDERERS SOLUTION – AVOID PLAIN RENDER

    I'd love to know the use case for plain render. Just use render :text => params[:name]
  22. RAILS ISRAEL 2015 26 CONVERT PARAMS TO TYPES PROBLEM –

    SQL TYPECASTING MATCHES WRONG RECORDS PUT /users/reset '{"user":{"token":”23a982”,"pass":"hacked","pass_confirm":"hacked"}}'
  23. RAILS ISRAEL 2015 27 CONVERT PARAMS TO TYPES PROBLEM –

    SQL TYPECASTING MATCHES WRONG RECORDS PUT /users/reset '{"user":{"token":”23a982”,"pass":"hacked","pass_confirm":"hacked"}}' '{"user":{"token":”0”,"pass":"hacked","pass_confirm":"hacked"}}'
  24. RAILS ISRAEL 2015 28 CONVERT PARAMS TO TYPES PROBLEM –

    SQL TYPECASTING MATCHES WRONG RECORDS PUT /users/reset '{"user":{"token":”23a982”,"pass":"hacked","pass_confirm":"hacked"}}' '{"user":{"token":”0”,"pass":"hacked","pass_confirm":"hacked"}}' '{"user":{"token":0,"pass":"hacked","pass_confirm":"hacked"}}'
  25. RAILS ISRAEL 2015 29 CONVERT PARAMS TO TYPES PROBLEM –

    SQL TYPECASTING MATCHES WRONG RECORDS Attack vector in all versions up to 4.2 '{"user":{"token":0,"pass":"hacked","pass_confirm":"hacked"}}' '{"user":{"token":[0],"pass":"hacked","pass_confirm":"hacked"}}' def reset user = User.find_by_token(params[:user][:token]) if user.present? user.reset_password(params[:user][:pass]) end end
  26. RAILS ISRAEL 2015 30 CONVERT PARAMS TO TYPES PROBLEM –

    SQL TYPECASTING MATCHES WRONG RECORDS String vs String comparison mysql> SELECT reset_token FROM users WHERE reset_token=”0”; 0 row in set (0.00 sec)
  27. RAILS ISRAEL 2015 31 CONVERT PARAMS TO TYPES PROBLEM –

    SQL TYPECASTING MATCHES WRONG RECORDS “1aC” is casted to 0 (invalid integer) 0=0 retrieves all records mysql> SELECT reset_token FROM users WHERE reset_token=0; +-------------+ | reset_token | +-------------+ | 1aC | +-------------+ 1 row in set (0.00 sec)
  28. RAILS ISRAEL 2015 32 CONVERT PARAMS TO TYPES PROBLEM –

    SQL TYPECASTING MATCHES WRONG RECORDS “1aC” is casted to 0 (invalid integer) 0=0 retrieves all records mysql> SELECT reset_token FROM users WHERE 0=0; +-------------+ | reset_token | +-------------+ | 1aC | +-------------+ 1 row in set (0.00 sec)
  29. RAILS ISRAEL 2015 33 CONVERT PARAMS TO TYPES SOLUTION –

    UPGRADE TO RAILS 4.2+ WORKAROUND – CAST MANUALLY user = User.find_by_token( params[:user][:token].to_s)
  30. RAILS ISRAEL 2015 34 SQL INJECTION PROBLEM – USER INPUT

    NOT ESCAPED Using params directly on SQL queries – huge mistake. Hardly a problem on most Rails apps thanks to sane defaults. User.where("name = '#{params[:name]}'")
  31. RAILS ISRAEL 2015 35 SQL INJECTION PROBLEM – USER INPUT

    NOT ESCAPED Using params directly on SQL queries – huge mistake. Hardly a problem on most Rails apps thanks to sane defaults. User.where("name = '#{params[:name]}'") params[:name] = “' OR 1 --“ SELECT * FROM users WHERE name = '' OR 1 –'
  32. RAILS ISRAEL 2015 36 SQL INJECTION PROBLEM – USER INPUT

    NOT ESCAPED Using params directly on SQL queries – huge mistake. Hardly a problem on most Rails apps thanks to sane defaults. User.where("name = '#{params[:name]}'") params[:name] = “'; DELETE FROM employees --“ SELECT * FROM users WHERE name = ''; DELETE FROM employees ' –
  33. RAILS ISRAEL 2015 37 SQL INJECTION SOLUTION – USE RAILS

    BUILT-IN FILTERS Rails auto sanitizes input in SQL queries User.where(:name => params[:name]) User.where('name = ? AND password = ?', params[:name], params[:pass])
  34. RAILS ISRAEL 2015 38 SQL INJECTION VULNERABLE METHODS .calculate .order

    .delete_all .all .destroy_all .pluck .exists? .reorder .find .first .find_by .where .from .update_all .group .having .joins .lock
  35. RAILS ISRAEL 2015 39 SQL INJECTION VULNERABLE METHODS .calculate .order

    .delete_all .all .destroy_all .pluck .exists? .reorder .find .first .find_by .where .from .update_all .group .having .joins Check out rails-sqli.org .lock
  36. RAILS ISRAEL 2015 41 DON'T STORE DATA IN COOKIES PROBLEM

    – DECODING A SESSION COOKIE In Rails 2 and 3, decoding a session cookie is this simple: Marshal.load( Base64.decode64( URI.decode( cookie.split('--').first )))
  37. RAILS ISRAEL 2015 42 DON'T STORE DATA IN COOKIES PROBLEM

    – DECODING A SESSION COOKIE In Rails 2 and 3, decoding a session cookie is this simple: Marshal.load( Base64.decode64( URI.decode( cookie.split('--').first )))
  38. RAILS ISRAEL 2015 43 DON'T STORE DATA IN COOKIES PROBLEM

    – DECODING A SESSION COOKIE In Rails 4 it's a bit harder, 1000 iterations of PBKDF2 with a salt: bit.ly/1M1suyK
  39. RAILS ISRAEL 2015 44 DON'T STORE DATA IN COOKIES PROBLEM

    – DECODING A SESSION COOKIE With session data in our hands, we can alter it trivially.
  40. RAILS ISRAEL 2015 45 DON'T STORE DATA IN COOKIES PROBLEM

    – DECODING A SESSION COOKIE With session data in our hands, we can alter it trivially.
  41. RAILS ISRAEL 2015 46 DON'T STORE DATA IN COOKIES SOLUTION

    – USE ALTERNATIVE STORAGE rails/activerecord-session_store (ActiveRecord) redis-store/redis-rails (Redis) mperham/dalli (memcached)
  42. RAILS ISRAEL 2015 47 EXPIRE SESSIONS PROBLEM – LONG-RUNNING SESSIONS

    Stolen session IDs should be rendered unusable.
  43. RAILS ISRAEL 2015 48 EXPIRE SESSIONS PROBLEM – LONG-RUNNING SESSIONS

    Stolen session IDs should be rendered unusable. Sniffing, XSS, etc…
  44. RAILS ISRAEL 2015 49 EXPIRE SESSIONS SOLUTION – USE “EXPIRES”

    & SWEEP Set the “Expires” HTTP header – client side Run a cron job – server side
  45. RAILS ISRAEL 2015 51 INSECURE FILE REFERENCES PROBLEM – INDIRECT

    FILE REFERENCE send_file “docs/#{params[:doc_name]}.pdf”
  46. RAILS ISRAEL 2015 52 INSECURE FILE REFERENCES PROBLEM – INDIRECT

    FILE REFERENCE What happens if doc_name is '../'? send_file “docs/#{params[:doc_name]}.pdf” Attackers can freely traverse the server filesystem.
  47. RAILS ISRAEL 2015 53 INSECURE FILE REFERENCES SOLUTION – JUST

    DON'T Whitelist params[:doc_name] # sanitize params[:doc_name] send_file “docs/#{params[:doc_name]}.pdf”
  48. RAILS ISRAEL 2015 54 PROTECT LINK_TO HREF PROBLEM – HREF

    IS HTML_SAFE <%= link_to "Personal blog", user.blog_url %>
  49. RAILS ISRAEL 2015 55 PROTECT LINK_TO HREF PROBLEM – HREF

    IS HTML_SAFE Users can set a blog URL to be displayed on their profiles <%= link_to "Personal blog", user.blog_url %>
  50. RAILS ISRAEL 2015 56 PROTECT LINK_TO HREF PROBLEM – HREF

    IS HTML_SAFE Users can set a blog URL to be displayed on their profiles <%= link_to "Personal blog", user.blog_url %> equivalent to <%= link_to "Personal blog", user.blog_url.html_safe %>
  51. RAILS ISRAEL 2015 57 PROTECT LINK_TO HREF PROBLEM – HREF

    IS HTML_SAFE Users can set a blog URL to be displayed on their profiles <%= link_to "Personal blog", user.blog_url %> Nothing stops an user from setting blog_url to "javascript:alert('hacked!')"
  52. RAILS ISRAEL 2015 58 PROTECT LINK_TO HREF SOLUTION – CHECK

    THE PROTOCOL Run URI.parse on user.blog_url and check the protocol validate :blog_url, :format => { :with => HTTP_REGEX } <%= link_to "Personal blog", user.blog_url %>
  53. RAILS ISRAEL 2015 60 MULTILINE REGEX PROBLEM – JS INJECTION

    Ruby matches regexes by default on multi-line mode. If you're trying to validate, a user name, or an URL: validates :blog_url, :format => { :with => /^http(s)*\S+$/i }
  54. RAILS ISRAEL 2015 61 MULTILINE REGEX PROBLEM – JS INJECTION

    Ruby matches regexes by default on multi-line mode. If you're trying to validate, a user name, or an URL: validates :blog_url, :format => { :with => /^http(s)*\S+$/i } $ in the above regex would stop at \n
  55. RAILS ISRAEL 2015 62 MULTILINE REGEX PROBLEM – JS INJECTION

    Multi-line regex validations can easily be bypassed javascript:alert('hacked!')/*\nhttp://*/ validates :blog_url, :format => { :with => /^http(s)*?:+$/i }
  56. RAILS ISRAEL 2015 63 MULTILINE REGEX PROBLEM – JS INJECTION

    Multi-line regex validations can easily be bypassed javascript:alert('hacked!')/*\nhttp://*/ validates :blog_url, :format => { :with => /^http(s)*?:+$/i } link_to "User blog", @user.blog_url # renders XSS
  57. RAILS ISRAEL 2015 64 MULTILINE REGEX SOLUTION – MATCH START/END

    OF EXPRESSION Rails 4 warns you about this validates :blog_url, :format => { :with => / \Ahttp(s)*?:+ \z/i }
  58. RAILS ISRAEL 2015 65 REFLECTIONS PROBLEM – SEND/EVAL ANY PARAMETERS

    Imagine you have an API that depends on other API Example: power operations on servers api :PUT, "/hosts/:id/power" @host.power.send(params[:power_action])
  59. RAILS ISRAEL 2015 66 REFLECTIONS PROBLEM – SEND/EVAL ANY PARAMETERS

    Attacker supplies power_action= eval&b=whatever%20ruby%20code%20we%20like api :PUT, "/hosts/:id/power" @host.power.send(params[:power_action])
  60. RAILS ISRAEL 2015 67 REFLECTIONS PROBLEM – SEND/EVAL ANY PARAMETERS

    Attacker supplies power_action= eval&b=FileUtils.rm_rf(%22%2f%22) # rm -rf / api :PUT, "/hosts/:id/power" @host.power.send(params[:power_action])
  61. RAILS ISRAEL 2015 68 REFLECTIONS PROBLEM – SEND/EVAL ANY PARAMETERS

    Real example, on Spree (popular Rails e-commerce framework) api/orders.json?search[instance_eval]= Kernel.fork%20do%60#{command}%60end
  62. RAILS ISRAEL 2015 69 REFLECTIONS PROBLEM – SEND/EVAL ANY PARAMETERS

    Attacker supplies power_action= eval&b=FileUtils.rm_rf(%22%2f%22) # rm -rf / api :PUT, "/hosts/:id/power" @host.power.send(params[:power_action])
  63. RAILS ISRAEL 2015 70 REFLECTIONS SOLUTION – AVOID OR WHITELIST

    Note commands can be chained (&&, ||, etc...) if PowerManager::SUPPORTED_ACTIONS.include? params[:power_action] @host.power.send(params[:power_action]) else 422, 'available methods are…'
  64. RAILS ISRAEL 2015 74 FORCE TLS PROBLEM - POODLE attack

    When TLS is unavailable, browser downgrades to SSLv3 96.9% of top 1M websites are vulnerable Man-in-the-middle can read HTTPS requests!
  65. RAILS ISRAEL 2015 75 FORCE TLS PROBLEM - POODLE attack

    When TLS is unavailable, browser downgrades to SSLv3 96.9% of top 1M websites are vulnerable Man-in-the-middle can read HTTPS requests! PROBLEM – TLS DOWNGRADE
  66. RAILS ISRAEL 2015 76 FORCE TLS PROBLEM - POODLE attack

    When TLS is unavailable, browser downgrades to SSLv3 96.9% of top 1M websites are vulnerable Man-in-the-middle can read HTTPS requests! PROBLEM – TLS DOWNGRADE Client and server support TLSv1.2. Active man-in-the-middle forces a downgrade. TLSv1.1 TLSv1.0 SSLv3 → → → POODLE
  67. RAILS ISRAEL 2015 77 FORCE TLS SOLUTION - POODLE attack

    class ApplicationController force_ssl end request.options = OpenSSL::SSL::OP_NO_SSLv2 + OpenSSL::SSL::OP_NO_SSLv3 Apache vHost SSLProtocol +TLSv1.1 +TLSv1.2 Nginx.conf ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  68. RAILS ISRAEL 2015 78 VERIFY REDIRECTS PROBLEM – ARBITRARY URL

    REDIRECT Usual 'def login' code if login_user(user) session[:user] = user.id uri = session[:original_uri] redirect_to (uri || index)
  69. RAILS ISRAEL 2015 79 VERIFY REDIRECTS PROBLEM – ARBITRARY URL

    REDIRECT Usual 'def login' code if login_user(user) session[:user] = user.id uri = session[:original_uri] redirect_to (uri || index) https://example.com/login attacker.com →
  70. RAILS ISRAEL 2015 80 VERIFY REDIRECTS SOLUTION – ARBITRARY URL

    REDIRECT Don't allow users to set redirect URLs Whitelist valid URLs if absolutely necessary
  71. RAILS ISRAEL 2015 81 THROTTLE SOME REQUESTS PROBLEM – BRUTE

    FORCE LOGIN ATTEMPTS Your application gets hundreds of requests per second trying to brute force a password for a certain user.
  72. RAILS ISRAEL 2015 82 THROTTLE SOME REQUESTS PROBLEM – BRUTE

    FORCE LOGIN ATTEMPTS Your application gets hundreds of requests per second trying to brute force a password for a certain user. PROBLEM – DOS An attacker wants to throw down your site after you published something you should have not. Or just for kicks.
  73. RAILS ISRAEL 2015 83 THROTTLE SOME REQUESTS SOLUTION – USE

    RACK::ATTACK On config/initializers/rack-attack.rb # Throttle high volumes of requests by IP address throttle('req/ip', limit: 20, period: 20.seconds) do |req| req.ip unless req.path.starts_with?('/assets') end
  74. RAILS ISRAEL 2015 84 HIDE YOUR HEADERS PROBLEM – SERVER

    VERSION IS EXPOSED The default server header in Webrick exposes you – Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20) Other servers suffer from the same issue – 'Server' in Apache, Thin and Webrick – 'X-Powered-By' on Nginx
  75. RAILS ISRAEL 2015 85 HIDE YOUR HEADERS SOLUTION – OVERRIDE

    IT IN THE APPLICATION config.action_dispatch. default_headers.merge!('Server' => '')
  76. RAILS ISRAEL 2015 87 SCAN YOUR APP Vulnerabilities scanners for

    Rails applications Brakeman – github.com/presidentbeef/brakeman Dawnscanner – github.com/thesp0nge/dawnscanner
  77. RAILS ISRAEL 2015 88 SCAN YOUR DEPENDENCIES With Bundler, the

    danger lies within Bundler-audit – github.com/rubysec/bundler-audit Gemnasium – gemnasium.com
  78. 89 RAILS ISRAEL 2015 Writing about web security is always

    a dangerous exercise. There’s a good chance I’ve gotten at least one or more facts wrong in this talk despite taking a good deal of time to research the topic from various angles.