– CROSS SITE SCRIPTING Any request that modifies a resource should be triggered GET is loaded automatically. <img src="http://example.com/projects/1/destroy“ />
– 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“ />
– 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“/>
UNVERIFIED REQUESTS class ApplicationController protect_from_forgery :with => :exception rescue_from ActionController::InvalidAuthenticityToken do |exception| sign_out_user end
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
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!
SQL TYPECASTING MATCHES WRONG RECORDS PUT /users/reset '{"user":{"token":”23a982”,"pass":"hacked","pass_confirm":"hacked"}}' '{"user":{"token":”0”,"pass":"hacked","pass_confirm":"hacked"}}'
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"}}'
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
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)
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)
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]}'")
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 –'
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 ' –
– 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 )))
– 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 )))
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 %>
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!')"
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 %>
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 }
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
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])
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
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
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.
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
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
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.