Skip to content

Commit dd9f41d

Browse files
committed
Security: Turn on protect_from_forgery for admin/
The Rails protect_from_forgery function helps protect against cross-site request forgery attacks, as described on Wikipedia: http://en.wikipedia.org/wiki/Cross-site_request_forgery These attacks involve a hostile site sending requests to a site where the user is logged in, exploiting the user's session cookie to do various bad things. The protect_from_forgery function works by requiring all POST (and PUT and UPDATE) requests to have an authenticity_token parameter that corresponds to a value in the user's session. This is automatically included in generated forms by the various form helpers, and checked in the controller. However, we still need to deal with some cases (specifically Ajax.Request) manually. We make several types of changes to get everything working again: - Some POST requests were changed to GET requests, when appropriate. - The token was added manually to other POST requests. This was done using the new init_mephisto_authenticity_token. - Forgery protection was disabled in the test environment. Note that we still need to review the authentication controller closely, and eliminate various XSS attacks against our application before this protection will do much good. I tested this code by manually using the admin/ interface, editing articles, adding users, and working with assets. There's probably still some breakage somewhere that I missed, so let me know if you have problems. I also updated the TODO list for Rails 2.2 and added security-auditing notes.
1 parent d558ba1 commit dd9f41d

File tree

8 files changed

+67
-21
lines changed

8 files changed

+67
-21
lines changed

RAILS-2.2-TODO.txt

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
11
This is a list of issues that we need to fix before making a Mephisto
22
release based on Rails 2.2.
33

4+
/ Try to upgrade to gem version of coderay
5+
Make sure both old- and new-style plugins work
6+
Security audit--see blow
47
Drop TZInfo completely--see app/models/site.rb (fix docs, too)
5-
Handle inactive users with named scopes, not acts_as_versioned
6-
SECURITY: Don't ship :session_key in environment.rb!
7-
Try to upgrade to gem version of coderay
88
We need to review our TODO comments
9-
Clean out the issue tracker: http://ar-code.lighthouseapp.com/projects/34-mephisto
10-
rake rails:update:javascripts => complicated because mephisto/application.js depends on older versions.
11-
12-
== Other issues
13-
14-
When running the unit tests, we need theme directories in themes/site-$ID.
15-
But this namespace is also used by the development and production
16-
databases. Are the test suites clobbering user themes accidentally? See
17-
spec/models/membership_spec.rb for one quick-and-dirty workaround. Perhaps
18-
for all non-production environments, we should prepend RAILS-ENV to the
19-
theme name, giving us themes/test-site-$ID?
209

2110
== Security
2211

@@ -26,13 +15,39 @@ We need to do a basic security audit.
2615
Make sure cookies are HTTP-only whenever possible
2716
Can we restrict admin cookies to /admin ?
2817
Make sure logging out clears all relevant cookies and tokens
18+
Check for session fixation attacks
19+
Expire sessions after a while?
2920
Cross-site scripting
21+
/ Turn on protect_against_forgery
3022
Check all fields in comments
23+
It looks like the failed comment error form has issues
3124
Check macro:* bodies and parameters
3225
filtered_column_code_macro
3326
filtered_column_flikr_macro
3427
Do we have trackback support to check?
3528
Do we need to upgrade to an industrial-strength HTML sanitizer?
3629
For now, we'll assume that users with access to /admin don't try XSS
30+
Password change
31+
Make change-password forms safe against CSRF
32+
Require the user to enter the old password when changing it
33+
Require password to change e-mail address
3734
Everything else
35+
/ Don't ship :session_key in environment.rb!
36+
Do we need to override verifiable_request_format?
37+
Review mass assignment in public controllers
3838
Review http://guides.rubyonrails.org/security.html
39+
Can we use SafeERB?
40+
41+
Admin only
42+
Filter file names for uploads
43+
Review mass assignment in admin controllers
44+
45+
== After next release
46+
47+
Handle inactive users with named scopes, not acts_as_versioned
48+
Clean out the issue tracker
49+
http://ar-code.lighthouseapp.com/projects/34-mephisto
50+
rake rails:update:javascripts
51+
(complicated because mephisto/application.js depends on older versions)
52+
Fix sidebar tabs to do something sensible with unsaved articles
53+

app/controllers/admin/base_controller.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ class Admin::BaseController < ApplicationController
77
before_filter :login_from_cookie
88
before_filter :login_required, :except => :feed
99

10+
# See ActionController::RequestForgeryProtection for details.
11+
protect_from_forgery
12+
1013
protected
1114
def protect_action
1215
if request.get?

app/helpers/application_helper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ def relative_url_root
4949
ActionController::Base.relative_url_root
5050
end
5151

52+
# Make our form_authenticity_token token available to JavaScript.
53+
def init_mephisto_authenticity_token
54+
return "" unless protect_against_forgery?
55+
"Mephisto.token = '#{form_authenticity_token}';"
56+
end
57+
5258
if RAILS_ENV == 'development'
5359
def gravatar_url_for(user, size = 80)
5460
'mephisto/avatar.gif'

app/views/layouts/application.rhtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<title><%= site.title %>: Admin <%= controller.controller_name %></title>
77
<%= stylesheet_link_tag 'mephisto/mephisto' %>
88
<%= javascript_include_tag 'mephisto/prototype', 'mephisto/effects', 'mephisto/dragdrop', 'mephisto/lowpro', 'mephisto/application' %>
9-
<script type="text/javascript">Mephisto.root = '<%= relative_url_root %>';</script>
9+
<script type="text/javascript">Mephisto.root = '<%= relative_url_root %>'; <%= init_mephisto_authenticity_token %></script>
1010
<%= yield :head %>
1111
</head>
1212
<body>

config/environments/test.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33
config.action_controller.consider_all_requests_local = true
44
config.action_controller.perform_caching = true
55
config.action_controller.page_cache_directory = File.join(RAILS_ROOT, 'tmp/cache')
6-
config.action_mailer.delivery_method = :test
6+
config.action_mailer.delivery_method = :test
7+
8+
# Disable request forgery protection in test environment
9+
config.action_controller.allow_forgery_protection = false

lib/mephisto/routing.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def self.connect_with(map)
2828
map.overview 'admin/overview.xml', :controller => 'admin/overview', :action => 'feed'
2929
map.admin 'admin', :controller => 'admin/overview', :action => 'index'
3030
map.resources :assets, :path_prefix => '/admin', :controller => 'admin/assets', :member => { :add_bucket => :post },
31-
:collection => { :latest => :post, :search => :post, :upload => :post, :clear_bucket => :post }
31+
:collection => { :latest => [:get, :post], :search => [:get, :post], :upload => :post, :clear_bucket => :post }
3232

3333
# Where oh where is my xmlrpc code?
3434
# map.connect 'xmlrpc', :controller => 'backend', :action => 'xmlrpc'

public/javascripts/mephisto/application.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ TinyTab.callbacks ={
157157
'search-files': function(q) {
158158
if(!q) return;
159159
$('spinner').show();
160-
new Ajax.Request(Mephisto.root + '/admin/assets/search', {parameters: 'q=' + escape(q)});
160+
new Ajax.Request(Mephisto.root + '/admin/assets/search', {method: 'get', parameters: 'q=' + escape(q)});
161161
}
162162
};
163163

@@ -300,23 +300,27 @@ var ArticleForm = {
300300
var articleId = location.href.match(/\/([0-9]+)\/(edit|upload)/)[1];
301301
var attached = $('attached-widget-' + assetId);
302302
if(attached) return;
303-
new Ajax.Request('/admin/articles/attach/' + articleId + '/' + assetId);
303+
new Ajax.Request('/admin/articles/attach/' + articleId + '/' + assetId,
304+
{ parameters: 'authenticity_token=' + Mephisto.token });
304305
$$('.widget').each(function(asset) { if(assetId == asset.getAttribute('id').match(/-(\d+)$/)[1]) asset.addClassName('selected-widget'); });
305306
},
306307

307308
labelAsset: function(assetId) {
308309
var articleId = location.href.match(/\/([0-9]+)\/(edit|upload)/)[1];
309310
var attached = $('attached-widget-' + assetId);
310311
var label = $('attached-widget-version-' + assetId);
311-
new Ajax.Request('/admin/articles/label/' + articleId + '/' + assetId + '?label=' + escape(label.value));
312+
new Ajax.Request('/admin/articles/label/' + articleId + '/' + assetId,
313+
{ parameters: 'authenticity_token=' + Mephisto.token +
314+
'&label=' + escape(label.value) });
312315
if(attached) return;
313316
},
314317

315318
detachAsset: function(assetId) {
316319
var articleId = location.href.match(/\/([0-9]+)\/(edit|upload)/)[1];
317320
var attached = $('attached-widget-' + assetId);
318321
if(!attached) return;
319-
new Ajax.Request('/admin/articles/detach/' + articleId + '/' + assetId);
322+
new Ajax.Request('/admin/articles/detach/' + articleId + '/' + assetId,
323+
{ parameters: 'authenticity_token=' + Mephisto.token });
320324
new Effect.DropOut(attached, {afterFinish: function() { attached.remove(); }});
321325
$$('.widget').each(function(asset) { if(assetId == asset.getAttribute('id').match(/-(\d+)$/)[1]) asset.removeClassName('selected-widget'); });
322326
},
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
require File.dirname(__FILE__) + '/../../spec_helper'
2+
3+
describe Admin::ArticlesController do
4+
controller_name "admin/articles"
5+
integrate_views
6+
7+
it "should route /admin/articles/attach and friends correctly" do
8+
params = { :controller => "admin/articles", :action => "attach",
9+
:id => '1', :version => "2" }
10+
params_from(:post, "/admin/articles/attach/1/2").should == params
11+
params = { :controller => "admin/articles", :action => "detach",
12+
:id => '1', :version => "2" }
13+
params_from(:post, "/admin/articles/detach/1/2").should == params
14+
end
15+
end

0 commit comments

Comments
 (0)