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

Rails' Insecure Defaults

Rails' Insecure Defaults

Out of the box, Rails provides facilities for preventing attacks like SQL injection, cross-site scripting (XSS), cross-site request forgery (CSRF), and more. As a result, it's considered one of the most secure web application frameworks available.

Digging deeper, however, you can find a number of places where Rails' default behavior is not as secure as it could be. This talk will focus on longstanding, known weak spots that create risks for your application and business if you are not aware of them.

Bryan Helmkamp

April 29, 2013
Tweet

More Decks by Bryan Helmkamp

Other Decks in Programming

Transcript

  1. @brynary Rails’ Secure Defaults • SQL Generation (SQLi) • Cross-site

    Request forgery (CSRF) • HTML Sanitization (XSS)
  2. @brynary HTTP/1.1 200 OK Last-Modified: Sun, 10 Mar 2013 18:32:18

    GMT Content-Type: text/html Content-Length: 5906 Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20) Date: Tue, 12 Mar 2013 18:08:41 GMT Connection: Keep-Alive
  3. @brynary HTTP/1.1 200 OK Last-Modified: Sun, 10 Mar 2013 18:32:18

    GMT Content-Type: text/html Content-Length: 5906 Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20) Date: Tue, 12 Mar 2013 18:08:41 GMT Connection: Keep-Alive
  4. @brynary HTTP/1.1 200 OK Last-Modified: Sun, 10 Mar 2013 18:32:18

    GMT Content-Type: text/html Content-Length: 5906 Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20) Date: Tue, 12 Mar 2013 18:08:41 GMT Connection: Keep-Alive
  5. @brynary Who cares? • Exposes Ruby patchlevel • Spray and

    prey scanners • Tailor attack payload • Avoid setting off any alerts
  6. @brynary Best Practice • Avoid WEBrick in production • Use

    Passenger, Unicorn, Thin or Puma • Heroku users: WEBrick is the default
  7. @brynary Who cares? • Production data loaded onto laptop •

    San. Francisco. Coffee. Shops. • IP forgery
  8. @brynary Best Practice • Use -b option to bind to

    127.0.0.1 • Avoid loading real data into development environments • If you must, delete it as soon as you’re done
  9. @brynary Started POST "/users" for 127.0.0.1 at 2013-03-12 14:26:28 -0400

    Processing by UsersController#create as HTML Parameters: {"utf8"=>"✓œ“", "authenticity_token"=>"...", "user"=>{"name"=>"Name", "password"=>"[FILTERED]"}, "commit"=>"Create User"} (0.1ms) begin transaction SQL (7.2ms) INSERT INTO "users" ("created_at", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00], ["name", "Name"], ["password_digest", "$2a$10$r/ XGSY9zJr62IpedC1m4Jes8slRRNn8tkikn5.0kE2izKNMlPsqvC"], ["updated_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00]] (1.1ms) commit transaction Redirected to http://localhost:3000/users/1 Completed 302 Found in 91ms (ActiveRecord: 8.8ms)
  10. @brynary Started POST "/users" for 127.0.0.1 at 2013-03-12 14:26:28 -0400

    Processing by UsersController#create as HTML Parameters: {"utf8"=>"✓œ“", "authenticity_token"=>"...", "user"=>{"name"=>"Name", "password"=>"[FILTERED]"}, "commit"=>"Create User"} (0.1ms) begin transaction SQL (7.2ms) INSERT INTO "users" ("created_at", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00], ["name", "Name"], ["password_digest", "$2a$10$r/ XGSY9zJr62IpedC1m4Jes8slRRNn8tkikn5.0kE2izKNMlPsqvC"], ["updated_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00]] (1.1ms) commit transaction Redirected to http://localhost:3000/users/1 Completed 302 Found in 91ms (ActiveRecord: 8.8ms)
  11. @brynary Started POST "/users" for 127.0.0.1 at 2013-03-12 14:26:28 -0400

    Processing by UsersController#create as HTML Parameters: {"utf8"=>"✓œ“", "authenticity_token"=>"...", "user"=>{"name"=>"Name", "password"=>"[FILTERED]"}, "commit"=>"Create User"} (0.1ms) begin transaction SQL (7.2ms) INSERT INTO "users" ("created_at", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00], ["name", "Name"], ["password_digest", "$2a$10$r/ XGSY9zJr62IpedC1m4Jes8slRRNn8tkikn5.0kE2izKNMlPsqvC"], ["updated_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00]] (1.1ms) commit transaction Redirected to http://localhost:3000/users/1 Completed 302 Found in 91ms (ActiveRecord: 8.8ms)
  12. @brynary Started POST "/users" for 127.0.0.1 at 2013-03-12 14:26:28 -0400

    Processing by UsersController#create as HTML Parameters: {"utf8"=>"✓œ“", "authenticity_token"=>"...", "user"=>{"name"=>"Name", "password"=>"[FILTERED]"}, "commit"=>"Create User"} (0.1ms) begin transaction SQL (7.2ms) INSERT INTO "users" ("created_at", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00], ["name", "Name"], ["password_digest", "$2a$10$r/ XGSY9zJr62IpedC1m4Jes8slRRNn8tkikn5.0kE2izKNMlPsqvC"], ["updated_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00]] (1.1ms) commit transaction Redirected to http://localhost:3000/users/1 Completed 302 Found in 91ms (ActiveRecord: 8.8ms)
  13. @brynary Started POST "/users" for 127.0.0.1 at 2013-03-12 14:26:28 -0400

    Processing by UsersController#create as HTML Parameters: {"utf8"=>"✓œ“", "authenticity_token"=>"...", "user"=>{"name"=>"Name", "password"=>"[FILTERED]"}, "commit"=>"Create User"} (0.1ms) begin transaction SQL (7.2ms) INSERT INTO "users" ("created_at", "name", "password_digest", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00], ["name", "Name"], ["password_digest", "$2a$10$r/ XGSY9zJr62IpedC1m4Jes8slRRNn8tkikn5.0kE2izKNMlPsqvC"], ["updated_at", Tue, 12 Mar 2013 18:26:28 UTC +00:00]] (1.1ms) commit transaction Redirected to http://localhost:3000/users/1 Completed 302 Found in 91ms (ActiveRecord: 8.8ms)
  14. @brynary Who cares? • Database gets sensitive values, like params

    • filtered_params_logging doesn’t apply • Production log level turned up to debug
  15. @brynary Best Practice • Be aware of what you’re logging

    in production • If you must increase the log level temporarily, remove that data when it’s no longer needed
  16. @brynary Possible Solutions • Introduce config.filter_logs and filter SQL values

    when possible • Redact entire SQL statement if it contains any references to filtered fields
  17. @brynary Who cares? • Secret token == application root key

    • Encryption is only as good as key management
  18. @brynary Best Practice • Use a different secret token in

    production • Inject secrets in ENV vars (Heroku, envdir) • Or symlink secret_token.rb on deploy
  19. @brynary Proposed Fix • Minimum: Git-ignore secret_token.rb in generated app

    skeleton • Better: Introduce a “secret store” to solve this problem for the Rails ecosystem
  20. @brynary class SignupsController < ApplicationController def create # ... if

    params[:destination].present? redirect_to params[:destination] else redirect_to dashboard_path end end end
  21. @brynary Who cares? • Phishing • Send user to malicious

    site • Looks like you sent them there • OWASP Top 10 Critical Web Application Security Risks #A10
  22. @brynary Best Practice • When passing a hash to redirect_to

    use the “only_path: true” option • When passing a string, extract the path via URI.parse
  23. @brynary Proposed Fix • Rails should only allow redirects within

    the same domain by default • Add an “external: true” option to opt-in to unsafe behavior
  24. @brynary Who cares? • Not immediately obvious you can put

    JavaScript in there • Cross-site scripting (XSS) risk
  25. @brynary Best Practice • Don’t allow unsafe input to control

    HREFs • Run the input through URI.parse and sanity check protocol and host
  26. @brynary Proposed Fix • Rails should only allow http://, https://

    and mailto: HREFs by default • Developers can opt-in to risky behavior with a new option
  27. @brynary becomes params[:column] = "age) FROM users WHERE name =

    'Bob';" Order.calculate(:sum, params[:column]) SELECT SUM(age) FROM users WHERE name = 'Bob';) AS sum_id FROM "orders"
  28. Affected APIs • #calculate • #delete_all • #destroy_all • #exists?

    • #find • :from • :group • :having • .pluck • .order • .reorder • etc. @brynary
  29. @brynary Who cares? • You. It’s SQL injection. :) •

    Not obvious which APIs are vulnerable • Some APIs violate Principle of Least Surprise and Least Privilege
  30. @brynary Best Practices • Understand which APIs are dangerous •

    Use the safest AR API possible for your operations • Whitelisted expected inputs (e.g. column names)
  31. @brynary Proposed Fix • Only permit SQL fragments where they

    are commonly used • Avoid attribute names that imply safety improperly • Provide alternative, more-risky APIs for less common cases
  32. @brynary Who cares? • Looks safe to the untrained eye

    • Obvious today, but what about someone who learns Rails tomorrow? • Springboard database injection into full scale site ownage • Least Privilege violation
  33. @brynary Best Practices • Use JSON serialization instead of YAML

    for #serialize and #store • Be cautious of storing YAML-encoded data in queues, caches, etc. • (Note: Ruby marshal-ed data has the same problems as YAML)
  34. @brynary Proposed Fix • Change the default serialization format from

    YAML to JSON • Provide YAML behavior as an option or extract into a Gem