bchiu-merb_forgery_protection 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README +71 -0
- data/Rakefile +55 -0
- data/TODO +1 -0
- data/lib/merb_forgery_protection.rb +23 -0
- data/lib/merb_forgery_protection/forgery_protection.rb +65 -0
- data/lib/merb_forgery_protection/form_helpers.rb +57 -0
- data/lib/merb_forgery_protection/merbtasks.rb +6 -0
- data/spec/merb_forgery_protection_spec.rb +7 -0
- data/spec/spec_helper.rb +2 -0
- metadata +73 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 YOUR NAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
= merb_forgery_protection
|
2
|
+
|
3
|
+
Merb plugin that provides forgery protection against css attacks.
|
4
|
+
|
5
|
+
Protect a controller's actions from CSRF attacks by ensuring that all forms
|
6
|
+
are coming from the current web application, not a forged link from another
|
7
|
+
site. This is done by embedding a token based on the session (which an
|
8
|
+
attacker wouldn't know) in all forms and Ajax requests generated by Merb
|
9
|
+
and then verifying the authenticity of that token in the controller. Only
|
10
|
+
HTML/JavaScript requests are checked, so this will not protect your XML API
|
11
|
+
(presumably you'll have a different authentication scheme there anyway).
|
12
|
+
Also, GET requests are not protected as these should be indempotent anyway.
|
13
|
+
|
14
|
+
You turn this on with the #protect_from_forgery method, which will perform
|
15
|
+
the check and raise a InvalidAuthenticityToken exception if the token doesn't
|
16
|
+
match what was expected. And it will add an authenticity_token parameter to
|
17
|
+
all forms that are automatically generated by Merb. You can customize the
|
18
|
+
error message given through public/422.html.
|
19
|
+
|
20
|
+
Learn more about CSRF (Cross-Site Request Forgery) attacks:
|
21
|
+
|
22
|
+
* http://isc.sans.org/diary.html?storyid=1750
|
23
|
+
* http://en.wikipedia.org/wiki/Cross-site_request_forgery
|
24
|
+
|
25
|
+
Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security
|
26
|
+
blanket for your merb app. There are a few guidelines you should follow:
|
27
|
+
|
28
|
+
* Keep your GET requests safe and idempotent. More reading material:
|
29
|
+
* http://www.xml.com/pub/a/2002/04/24/deviant.html
|
30
|
+
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
|
31
|
+
* Make sure the session cookies that Rails creates are non-persistent.
|
32
|
+
Check in Firefox and look for "Expires: at end of session"
|
33
|
+
|
34
|
+
If you need to construct a request yourself, but still want to take advantage
|
35
|
+
of forgery protection, you can grab the authenticity_token using the
|
36
|
+
authenticity_token helper method and make it part of the parameters yourself.
|
37
|
+
|
38
|
+
== Installation
|
39
|
+
|
40
|
+
git clone git://github.com/bchiu/merb_forgery_protection.git
|
41
|
+
cd merb_forgery_protection
|
42
|
+
rake install
|
43
|
+
|
44
|
+
== Example
|
45
|
+
|
46
|
+
class Foo < Application
|
47
|
+
# uses the cookie session store (then you don't need a separate :secret)
|
48
|
+
protect_from_forgery :exclude => :index
|
49
|
+
|
50
|
+
# uses one of the other session stores that uses a session_id value.
|
51
|
+
protect_from_forgery :secret => 'my-little-pony', :exclude => :index
|
52
|
+
|
53
|
+
# you can disable csrf protection on controller-by-controller basis:
|
54
|
+
protect_from_forgery :enable => false
|
55
|
+
end
|
56
|
+
|
57
|
+
== Configuration
|
58
|
+
|
59
|
+
To disable forgery protection globally put this in your init.rb:
|
60
|
+
Merb::Plugins.config[:forgery_protection] = { :enable => false }
|
61
|
+
|
62
|
+
=== Global Options:
|
63
|
+
:secret - salt used to generate the token (default :session_secret_key)
|
64
|
+
:enable - enable/disable protection for all controllers (default true)
|
65
|
+
:digest - message digest used for hashing (default 'SHA1')
|
66
|
+
:token_name - form field name for token (default :authenticity_token)
|
67
|
+
|
68
|
+
=== Controller Options:
|
69
|
+
:only/:exclude - set which controller actions are protected from forgery
|
70
|
+
:enable - enable/disable protection for this controller (default true)
|
71
|
+
:secret - salt used to generate the token (default :session_secret_key)
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'date'
|
5
|
+
require 'merb_rake_helper'
|
6
|
+
|
7
|
+
PLUGIN = "merb_forgery_protection"
|
8
|
+
NAME = "merb_forgery_protection"
|
9
|
+
GEM_VERSION = "0.0.1"
|
10
|
+
AUTHOR = "Ben Chiu"
|
11
|
+
EMAIL = "bchiu@yahoo.com"
|
12
|
+
HOMEPAGE = "http://github.com/bchiu/merb_forgery_protection"
|
13
|
+
SUMMARY = "Merb plugin that provides forgery protection against css attacks"
|
14
|
+
|
15
|
+
spec = Gem::Specification.new do |s|
|
16
|
+
s.name = NAME
|
17
|
+
s.version = GEM_VERSION
|
18
|
+
s.platform = Gem::Platform::RUBY
|
19
|
+
s.has_rdoc = true
|
20
|
+
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
21
|
+
s.summary = SUMMARY
|
22
|
+
s.description = s.summary
|
23
|
+
s.author = AUTHOR
|
24
|
+
s.email = EMAIL
|
25
|
+
s.homepage = HOMEPAGE
|
26
|
+
s.add_dependency('merb', '>= 0.9.0')
|
27
|
+
s.require_path = 'lib'
|
28
|
+
s.autorequire = PLUGIN
|
29
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
30
|
+
end
|
31
|
+
|
32
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
33
|
+
pkg.gem_spec = spec
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "install the plugin locally"
|
37
|
+
task :install => [:package] do
|
38
|
+
sh %{#{sudo} #{gemx} install pkg/#{NAME}-#{GEM_VERSION} --local --no-update-sources}
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "create a gemspec file"
|
42
|
+
task :make_spec do
|
43
|
+
File.open("#{NAME}.gemspec", "w") do |file|
|
44
|
+
file.puts spec.to_ruby
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
namespace :jruby do
|
49
|
+
|
50
|
+
desc "Run :package and install the resulting .gem with jruby"
|
51
|
+
task :install => :package do
|
52
|
+
sh %{#{sudo} jruby -S gem install pkg/#{NAME}-#{GEM_VERSION}.gem --no-rdoc --no-ri}
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
if defined?(Merb::Plugins)
|
2
|
+
require 'merb_forgery_protection/forgery_protection'
|
3
|
+
require 'merb_forgery_protection/form_helpers'
|
4
|
+
|
5
|
+
DEFAULT_CONFIG = {
|
6
|
+
:secret => Merb::Config[:session_secret_key],
|
7
|
+
:enable => true,
|
8
|
+
:digest => 'SHA1',
|
9
|
+
:token_name => :authenticity_token
|
10
|
+
}
|
11
|
+
|
12
|
+
Merb::Plugins.config[:merb_forgery_protection] = DEFAULT_CONFIG.update(
|
13
|
+
Merb::Plugins.config[:merb_forgery_protection]||{}
|
14
|
+
)
|
15
|
+
|
16
|
+
Merb::BootLoader.before_app_loads do
|
17
|
+
end
|
18
|
+
|
19
|
+
Merb::BootLoader.after_app_loads do
|
20
|
+
end
|
21
|
+
|
22
|
+
Merb::Plugins.add_rakefiles "merb_forgery_protection/merbtasks"
|
23
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Merb::ControllerExceptions
|
2
|
+
class InvalidAuthenticityToken < ClientError
|
3
|
+
self.status = 401
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class Merb::Controller
|
8
|
+
attr_accessor :protection_options
|
9
|
+
@protection_options = {}
|
10
|
+
|
11
|
+
def self.protect_from_forgery(opts = {})
|
12
|
+
with = {}
|
13
|
+
[:secret,:enable].each {|k| with[k] = opts.delete(k) if opts.key?(k) }
|
14
|
+
before :verify_token, opts.merge(:with => [with])
|
15
|
+
end
|
16
|
+
|
17
|
+
# controller instance methods
|
18
|
+
|
19
|
+
def authenticity_token
|
20
|
+
token_name = @protection_options[:token_name].to_s
|
21
|
+
if @protection_options[:secret]
|
22
|
+
{ :name => token_name, :value => token_from_session_id }.to_mash
|
23
|
+
elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
|
24
|
+
{ :name => token_name, :value => token_from_cookie_session }.to_mash
|
25
|
+
elsif session.nil?
|
26
|
+
raise Merb::ControllerExceptions::InvalidAuthenticityToken, "Forgery Protection requires a valid session. Please disable it or use a valid session."
|
27
|
+
else
|
28
|
+
raise Merb::ControllerExceptions::InvalidAuthenticityToken, "No :secret given. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def protect_against_forgery?
|
33
|
+
@protection_options && @protection_options[:enable]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def verify_token(opts = {})
|
39
|
+
@protection_options ||= Merb::Plugins.config[:merb_forgery_protection].update(opts)
|
40
|
+
verified_request? || raise(Merb::ControllerExceptions::InvalidAuthenticityToken)
|
41
|
+
end
|
42
|
+
|
43
|
+
def verified_request?
|
44
|
+
request.method == :get ||
|
45
|
+
!@protection_options[:enable] ||
|
46
|
+
![:html,:js].include?(content_type) ||
|
47
|
+
params[@protection_options[:token_name]] == authenticity_token
|
48
|
+
end
|
49
|
+
|
50
|
+
def token_from_session_id
|
51
|
+
if @protection_options[:secret].respond_to?(:call)
|
52
|
+
key = @protection_options[:secret].call(session)
|
53
|
+
else
|
54
|
+
key = @protection_options[:secret]
|
55
|
+
end
|
56
|
+
digest = @protection_options[:digest]
|
57
|
+
session_id = cookies[_session_id_key]
|
58
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session_id)
|
59
|
+
end
|
60
|
+
|
61
|
+
def token_from_cookie_session
|
62
|
+
session[:csrf_id] ||= rand_uuid
|
63
|
+
session.dbman.generate_digest(session[:csrf_id])
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Merb::Helpers::Form
|
2
|
+
|
3
|
+
# Generates a form tag, which accepts a block that is not directly based on resource attributes
|
4
|
+
#
|
5
|
+
# <% form_tag(:action => url(:controller => "foo", :action => "bar", :id => 1)) do %>
|
6
|
+
# <%= text_field :name => 'first_name', :label => 'First Name' %>
|
7
|
+
# <%= submit_button 'Create' %>
|
8
|
+
# <% end %>
|
9
|
+
#
|
10
|
+
# The HTML generated for this would be:
|
11
|
+
#
|
12
|
+
# <form action="/foo/bar/1" method="post">
|
13
|
+
# <label for="first_name">First Name</label><input id="first_name" name="first_name" size="30" type="text" />
|
14
|
+
# <input name="commit" type="submit" value="Create" />
|
15
|
+
# </form>
|
16
|
+
def form_tag(attrs = {}, &block)
|
17
|
+
set_multipart_attribute!(attrs)
|
18
|
+
fake_form_method = set_form_method(attrs)
|
19
|
+
concat(open_tag("form", attrs), block.binding)
|
20
|
+
concat(generate_fake_form_method(fake_form_method), block.binding) if fake_form_method
|
21
|
+
concat(capture(&block), block.binding)
|
22
|
+
concat(token_field, block.binding) if attrs[:method] == :post
|
23
|
+
concat("</form>", block.binding)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Generates a resource specific form tag which accepts a block, this also provides automatic resource routing.
|
27
|
+
# <% form_for :person, :action => url(:people) do %>
|
28
|
+
# <%= text_control :first_name, :label => 'First Name' %>
|
29
|
+
# <%= text_control :last_name, :label => 'Last Name' %>
|
30
|
+
# <%= submit_button 'Create' %>
|
31
|
+
# <% end %>
|
32
|
+
#
|
33
|
+
# The HTML generated for this would be:
|
34
|
+
#
|
35
|
+
# <form action="/people/create" method="post">
|
36
|
+
# <label for="person[first_name]">First Name</label><input id="person_first_name" name="person[first_name]" size="30" type="text" />
|
37
|
+
# <label for="person[last_name]">Last Name</label><input id="person_last_name" name="person[last_name]" size="30" type="text" />
|
38
|
+
# <input name="commit" type="submit" value="Create" />
|
39
|
+
# </form>
|
40
|
+
def form_for(obj, attrs={}, &block)
|
41
|
+
set_multipart_attribute!(attrs)
|
42
|
+
obj = obj_from_ivar_or_sym(obj)
|
43
|
+
fake_form_method = set_form_method(attrs, obj)
|
44
|
+
concat(open_tag("form", attrs), block.binding)
|
45
|
+
concat(generate_fake_form_method(fake_form_method), block.binding) if fake_form_method
|
46
|
+
fields_for(obj, attrs, &block)
|
47
|
+
concat(token_field, block.binding) if attrs[:method] == :post
|
48
|
+
concat("</form>", block.binding)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def token_field
|
54
|
+
return '' if !protect_against_forgery?
|
55
|
+
hidden_field(:name => authenticity_token[:name], :value => authenticity_token[:value])
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bchiu-merb_forgery_protection
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Chiu
|
8
|
+
autorequire: merb_forgery_protection
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-06-02 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: merb
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.9.0
|
23
|
+
version:
|
24
|
+
description: Merb plugin that provides forgery protection against css attacks
|
25
|
+
email: bchiu@yahoo.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README
|
32
|
+
- LICENSE
|
33
|
+
- TODO
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README
|
37
|
+
- Rakefile
|
38
|
+
- TODO
|
39
|
+
- lib/merb_forgery_protection
|
40
|
+
- lib/merb_forgery_protection/forgery_protection.rb
|
41
|
+
- lib/merb_forgery_protection/form_helpers.rb
|
42
|
+
- lib/merb_forgery_protection/merbtasks.rb
|
43
|
+
- lib/merb_forgery_protection.rb
|
44
|
+
- spec/merb_forgery_protection_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/bchiu/merb_forgery_protection
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.0.1
|
69
|
+
signing_key:
|
70
|
+
specification_version: 2
|
71
|
+
summary: Merb plugin that provides forgery protection against css attacks
|
72
|
+
test_files: []
|
73
|
+
|