secureRubyonRails.ppt

advertisement
Securing Ruby on Rails
CIS 6939 Web Engineering with Ruby on Rails
University of North Florida
Stephen Jones 8 July 2007
Securing Ruby on Rails
SANS Top-20 Internet Security Attack Targets (2006 Annual Update)
Top of the list for the Cross-platform Applications category is:
C1 Web Applications
C1.1 Description
Applications such as Content Management Systems (CMS), Wikis, Portals, Bulletin Boards,
and discussion forums are being used by small and large organizations. Every week
hundreds of vulnerabilities are being reported in these web applications, and are being
actively exploited. The number of attempted attacks every day for some of the large web
hosting farms range from hundreds of thousands to even millions.
All web frameworks (PHP, .NET, J2EE, Ruby on Rails, ColdFusion, Perl, etc) and all types of
web applications are at risk from web application security defects, ranging from insufficient
validation through to application logic errors.
Securing Ruby on Rails
User Input
Regular form fields
Hidden form fields
Cookies
URL Parameters
POST data
HTTP headers
AJAX requests

Scoped Queries
Securing Ruby on Rails
Record IDs
used right in the URL?
class User < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :user
end
class ContactsController < ApplicationController
before_filter :require_signin
def new
@contact = Contact.new
end
def create
contact = Contact.new params[:contact]
contact.user_id = session[:user_id]
contact.save
redirect_to contact_url(contact)
end
def show
@contact = Contact.find params[:id]
end
# accessed in URL path like /contacts/42
private
def require_signin
return false unless session[:user_id]
end
end
Securing Ruby on Rails
class ContactsController < ApplicationController
# gives us a @current_user object
before_filter :require_signin
# safely looks up the contact
before_filter :find_contact, :except => [ :index, :new,
:create ]
def index
@contacts = @current_user.contacts.find :all
end
def new
@contact = @current_user.contacts.new
end
def create
@current_user.contacts.create params[:contact]
redirect_to contacts_url
end
def show
end
def edit
end
def update
@contact.update_attributes params[:contact]
redirect_to contact_url
end
def destroy
@contact.destroy
redirect_to contacts_url
end
private
def require_signin
@current_user = User.find session[:user_id]
redirect_to(home_url) and return false
unless@current_user
end
def find_contact
@contact = @current_user.contacts.find.params[:id]
end
end
Securing Ruby on Rails
Record IDs in URLs verified? (HTTP authentication) Is the ID guessable?
How about a token?
class User < ActiveRecord::Base
def before_create
token = Digest::SHA1.hexdigest("#{id}#{rand.to_s}")[0..15]
write_attribute 'token', token
end
end
class FeedsController < ApplicationController
def show
@user = User.find_by_token(params[:id]) or
raise ActiveRecord::RecordNotFound
end
end
Securing Ruby on Rails
Mass Assignment
contact = current_user.contacts.create params[:contact]
contact.update_attributes params[:contact]
class UsersController < ApplicationController
def edit
@user = current_user
end
def update
current_user.update_attributes params[:user]
redirect_to edit_user_url
end
end
edit.rhtml:
<% form_for :user, :url => user_url, :html =>
{ :method => :put } do |u| %>
<p>Login: <%= u.text_field :login %></p>
<p>Password: <%= u.password_field :password
%></p>
<p><%= submit_tag "Save Account Settings" %>
<% end %>
require 'net/http'
http = Net::HTTP.new 'localhost', 3000
http.post "/users/1", 'user[is_administrator]=1&_method=put',
{ 'Content-Type' => 'application/x-www-form-urlencoded' }
class User < ActiveRecord::Base
attr_protected :is_administrator
has_many :contacts
end
class User < ActiveRecord::Base
attr_accessible :login, :password
has_many :contacts
end
Securing Ruby on Rails
Form Validation
Client-side validation with javascript
immediate feedback
The data should still be validated on the
server side as well.
Securing Ruby on Rails
SQL Injection
passing input directly from user to database
malicious users hijack your queries
# unsafe
User.find(:first, :conditions => "login = '#{params[:login]}' AND
password = '#{params[:password]}'")
SELECT * FROM users WHERE (login='alice' and password='secret') LIMIT 1
' or login='bob' and password != ‘
SELECT * FROM users WHERE (login='' and
password='' or login='bob' and password !=‘ ‘ ) LIMIT 1 #Logs in as any user
Securing Ruby on Rails
SQL Injection
# safe (pass a hash to :conditions)
User.find(:first, :conditions => { :login => params[:login],
:password => params[:password] })
# safe (shorter form)
User.find(:first, :conditions =>
[ "login = ? AND password = ?", params[:login], params[:password] ])
Securing Ruby on Rails
Session Fixation
cross-site cooking
Mitigation
use reset_session in your sign-in and sign-out methods
# signin
def create
if u = User.find_by_login_and_password(params[:login],
params[:password])
reset_session # create a new sess id, to thwart fixation
session[:user_id] = u.id
redirect_to home_url
else
render :action => 'new'
end
end
Securing Ruby on Rails
Cross-site Scripting (XSS)
unescaped user data included in HTML output
What’s the problem? Javascript!
http://example.com/search?q=%3Cscript%3Ealert('XSS')%3B%3C%2Fscript%3E
Securing Ruby on Rails
Cross-site Scripting (XSS)
#unsafe
<%= start_form_tag search_url, :method => :get %>
<p><%= text_field_tag :q %> <%= submit_tag "Search" %>
<% end %>
class SearchController < ApplicationController
def index
@q = params[:q]
@posts = Post.find :all,
:conditions => ["body like :query",
{ :query => params[:q]}]
end
end
<p>Your search for <em><%= @q %></em>
returned <%= pluralize @posts.size, "result" %>:</p>
<% @posts.each do |post| %>
<li><%= link_to post.title, post_url(:id => post) %>:
<%= exerpt post.body, @q %></li>
<% end %>
Solution: h helper, also known as html
escape.
converts &, ", >, and < into &, "
>, and <
<p>Your search for <em><%= h @q %></em>
<%= link_to h(@user.name), user_url(@user)
%>
Securing Ruby on Rails
Hashing Passwords
MD5 or SHA1
require 'digest/sha1'
class User < ActiveRecord::Base
attr_accessor :password
validates_uniqueness_of :login
validates_presence_of :password, :if => :password_required?
validates_confirmation_of :password, :if => :password_required?
before_save :hash_password
# Authenticates a user by login/password. Returns the user or nil.
def self.authenticate login, password
find_by_login_and_hashed_password(login,
Digest::SHA1.hexdigest(login+password))
end
protected
def hash_password
return if password.blank?
self.hashed_password = Digest::SHA1.hexdigest(login+password)
end
def password_required?
hashed_password.blank? || !password.blank?
end
end
Securing Ruby on Rails
Silencing Logs
class OrdersController < ApplicationController
filter_parameter_logging :cc_number, :cvv, :cc_date
# ...
end
Securing Ruby on Rails
Advertising
Third party widgets
Securing Ruby on Rails
Attributation
1. The Ghost In The Browser: Analysis of Web-based Malware
Niels Provos, Dean McNamee, Panayiotis Mavrommatis, Ke Wang and Nagendra
Modadugu Google, Inc.
2. Ajax on Rails by Scott Raymond
3. Sans Institute Internet Security Attack Targets http://www.sans.org/top20/
4. Rails Security Mailing List: http://groups.google.com/group/rubyonrails-security
Download