rack-protection-1.5.1/0000755000004100000410000000000012234224453014653 5ustar www-datawww-datarack-protection-1.5.1/License0000644000004100000410000000204412234224453016160 0ustar www-datawww-dataCopyright (c) 2011 Konstantin Haase Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rack-protection-1.5.1/rack-protection.gemspec0000644000004100000410000000571512234224453021334 0ustar www-datawww-data# Run `rake rack-protection.gemspec` to update the gemspec. Gem::Specification.new do |s| # general infos s.name = "rack-protection" s.version = "1.5.1" s.description = "You should use protection!" s.homepage = "http://github.com/rkh/rack-protection" s.summary = s.description s.license = 'MIT' # generated from git shortlog -sn s.authors = [ "Konstantin Haase", "Alex Rodionov", "Patrick Ellis", "Jeff Welling", "ITO Nobuaki", "Matteo Centenaro", "David Kellum", "Egor Homakov", "Florian Gilcher", "Fojas", "Mael Clerambault", "Martin Mauch", "SAKAI, Kazuaki", "Stanislav Savulchik", "Steve Agalloco", "TOBY", "Akzhan Abdulin", "brookemckim", "Bj\u00F8rge N\u00E6ss", "Chris Heald", "Chris Mytton", "Corey Ward", "Dario Cravero" ] # generated from git shortlog -sne s.email = [ "konstantin.mailinglists@googlemail.com", "p0deje@gmail.com", "patrick@soundcloud.com", "jeff.welling@gmail.com", "bugant@gmail.com", "daydream.trippers@gmail.com", "homakov@gmail.com", "florian.gilcher@asquera.de", "developer@fojasaur.us", "mael@clerambault.fr", "martin.mauch@gmail.com", "kaz.july.7@gmail.com", "s.savulchik@gmail.com", "steve.agalloco@gmail.com", "toby.net.info.mail+git@gmail.com", "akzhan.abdulin@gmail.com", "brooke@digitalocean.com", "bjoerge@bengler.no", "cheald@gmail.com", "self@hecticjeff.net", "coreyward@me.com", "dario@uxtemple.com", "dek-oss@gravitext.com" ] # generated from git ls-files s.files = [ "License", "README.md", "Rakefile", "lib/rack-protection.rb", "lib/rack/protection.rb", "lib/rack/protection/authenticity_token.rb", "lib/rack/protection/base.rb", "lib/rack/protection/escaped_params.rb", "lib/rack/protection/form_token.rb", "lib/rack/protection/frame_options.rb", "lib/rack/protection/http_origin.rb", "lib/rack/protection/ip_spoofing.rb", "lib/rack/protection/json_csrf.rb", "lib/rack/protection/path_traversal.rb", "lib/rack/protection/remote_referrer.rb", "lib/rack/protection/remote_token.rb", "lib/rack/protection/session_hijacking.rb", "lib/rack/protection/version.rb", "lib/rack/protection/xss_header.rb", "rack-protection.gemspec", "spec/authenticity_token_spec.rb", "spec/base_spec.rb", "spec/escaped_params_spec.rb", "spec/form_token_spec.rb", "spec/frame_options_spec.rb", "spec/http_origin_spec.rb", "spec/ip_spoofing_spec.rb", "spec/json_csrf_spec.rb", "spec/path_traversal_spec.rb", "spec/protection_spec.rb", "spec/remote_referrer_spec.rb", "spec/remote_token_spec.rb", "spec/session_hijacking_spec.rb", "spec/spec_helper.rb", "spec/xss_header_spec.rb" ] # dependencies s.add_dependency "rack" s.add_development_dependency "rack-test" s.add_development_dependency "rspec", "~> 2.0" end rack-protection-1.5.1/README.md0000644000004100000410000000343112234224453016133 0ustar www-datawww-dataYou should use protection! This gem protects against typical web attacks. Should work for all Rack apps, including Rails. # Usage Use all protections you probably want to use: ``` ruby # config.ru require 'rack/protection' use Rack::Protection run MyApp ``` Skip a single protection middleware: ``` ruby # config.ru require 'rack/protection' use Rack::Protection, :except => :path_traversal run MyApp ``` Use a single protection middleware: ``` ruby # config.ru require 'rack/protection' use Rack::Protection::AuthenticityToken run MyApp ``` # Prevented Attacks ## Cross Site Request Forgery Prevented by: * `Rack::Protection::AuthenticityToken` (not included by `use Rack::Protection`) * `Rack::Protection::FormToken` (not included by `use Rack::Protection`) * `Rack::Protection::JsonCsrf` * `Rack::Protection::RemoteReferrer` (not included by `use Rack::Protection`) * `Rack::Protection::RemoteToken` * `Rack::Protection::HttpOrigin` ## Cross Site Scripting Prevented by: * `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`) * `Rack::Protection::XSSHeader` (Internet Explorer only) ## Clickjacking Prevented by: * `Rack::Protection::FrameOptions` ## Directory Traversal Prevented by: * `Rack::Protection::PathTraversal` ## Session Hijacking Prevented by: * `Rack::Protection::SessionHijacking` ## IP Spoofing Prevented by: * `Rack::Protection::IPSpoofing` # Installation gem install rack-protection # Instrumentation Instrumentation is enabled by passing in an instrumenter as an option. ``` use Rack::Protection, instrumenter: ActiveSupport::Notifications ``` The instrumenter is passed a namespace (String) and environment (Hash). The namespace is 'rack.protection' and the attack type can be obtained from the environment key 'rack.protection.attack'. rack-protection-1.5.1/Rakefile0000644000004100000410000000250412234224453016321 0ustar www-datawww-data# encoding: utf-8 $LOAD_PATH.unshift File.expand_path('../lib', __FILE__) begin require 'bundler' Bundler::GemHelper.install_tasks rescue LoadError => e $stderr.puts e end desc "run specs" task(:spec) { ruby '-S rspec spec' } desc "generate gemspec" task 'rack-protection.gemspec' do require 'rack/protection/version' content = File.binread 'rack-protection.gemspec' # fetch data fields = { :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), :email => `git shortlog -sne`.force_encoding('utf-8').scan(/[^<]+@[^>]+/), :files => `git ls-files`.force_encoding('utf-8').split("\n").reject { |f| f =~ /^(\.|Gemfile)/ } } # double email :( fields[:email].delete("konstantin.haase@gmail.com") # insert data fields.each do |field, values| updated = " s.#{field} = [" updated << values.map { |v| "\n %p" % v }.join(',') updated << "\n ]" content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated) end # set version content.sub! /(s\.version.*=\s+).*/, "\\1\"#{Rack::Protection::VERSION}\"" # escape unicode content.gsub!(/./) { |c| c.bytesize > 1 ? "\\u{#{c.codepoints.first.to_s(16)}}" : c } File.open('rack-protection.gemspec', 'w') { |f| f << content } end task :gemspec => 'rack-protection.gemspec' task :default => :spec task :test => :spec rack-protection-1.5.1/spec/0000755000004100000410000000000012234224453015605 5ustar www-datawww-datarack-protection-1.5.1/spec/protection_spec.rb0000755000004100000410000000414612234224453021342 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection do it_behaves_like "any rack application" it 'passes on options' do mock_app do use Rack::Protection, :track => ['HTTP_FOO'] run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] } end session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b' session[:foo].should be == :bar get '/', {}, 'rack.session' => session, 'HTTP_FOO' => 'BAR' session.should be_empty end it 'passes errors through if :reaction => :report is used' do mock_app do use Rack::Protection, :reaction => :report run proc { |e| [200, {'Content-Type' => 'text/plain'}, [e["protection.failed"].to_s]] } end session = {:foo => :bar} post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com') last_response.should be_ok body.should == "true" end describe "#html?" do context "given an appropriate content-type header" do subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/html" } it { should be_true } end context "given an inappropriate content-type header" do subject { Rack::Protection::Base.new(nil).html? 'content-type' => "image/gif" } it { should be_false } end context "given no content-type header" do subject { Rack::Protection::Base.new(nil).html?({}) } it { should be_false } end end describe "#instrument" do let(:env) { { 'rack.protection.attack' => 'base' } } let(:instrumenter) { double('Instrumenter') } after do app.instrument(env) end context 'with an instrumenter specified' do let(:app) { Rack::Protection::Base.new(nil, :instrumenter => instrumenter) } it { instrumenter.should_receive(:instrument).with('rack.protection', env) } end context 'with no instrumenter specified' do let(:app) { Rack::Protection::Base.new(nil) } it { instrumenter.should_not_receive(:instrument) } end end end rack-protection-1.5.1/spec/frame_options_spec.rb0000644000004100000410000000241412234224453022012 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::FrameOptions do it_behaves_like "any rack application" it 'should set the X-Frame-Options' do get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "SAMEORIGIN" end it 'should not set the X-Frame-Options for other content types' do get('/', {}, 'wants' => 'text/foo').headers["X-Frame-Options"].should be_nil end it 'should allow changing the protection mode' do # I have no clue what other modes are available mock_app do use Rack::Protection::FrameOptions, :frame_options => :deny run DummyApp end get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "DENY" end it 'should allow changing the protection mode to a string' do # I have no clue what other modes are available mock_app do use Rack::Protection::FrameOptions, :frame_options => "ALLOW-FROM foo" run DummyApp end get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "ALLOW-FROM foo" end it 'should not override the header if already set' do mock_app with_headers("X-Frame-Options" => "allow") get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "allow" end end rack-protection-1.5.1/spec/remote_referrer_spec.rb0000644000004100000410000000172712234224453022342 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::RemoteReferrer do it_behaves_like "any rack application" it "accepts post requests with no referrer" do post('/').should be_ok end it "does not accept post requests with no referrer if allow_empty_referrer is false" do mock_app do use Rack::Protection::RemoteReferrer, :allow_empty_referrer => false run DummyApp end post('/').should_not be_ok end it "should allow post request with a relative referrer" do post('/', {}, 'HTTP_REFERER' => '/').should be_ok end it "accepts post requests with the same host in the referrer" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.com') last_response.should be_ok end it "denies post requests with a remote referrer" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') last_response.should_not be_ok end end rack-protection-1.5.1/spec/escaped_params_spec.rb0000644000004100000410000000231612234224453022115 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::EscapedParams do it_behaves_like "any rack application" context 'escaping' do it 'escapes html entities' do mock_app do |env| request = Rack::Request.new(env) [200, {'Content-Type' => 'text/plain'}, [request.params['foo']]] end get '/', :foo => "" body.should == '<bar>' end it 'leaves normal params untouched' do mock_app do |env| request = Rack::Request.new(env) [200, {'Content-Type' => 'text/plain'}, [request.params['foo']]] end get '/', :foo => "bar" body.should == 'bar' end it 'copes with nested arrays' do mock_app do |env| request = Rack::Request.new(env) [200, {'Content-Type' => 'text/plain'}, [request.params['foo']['bar']]] end get '/', :foo => {:bar => ""} body.should == '<bar>' end it 'leaves cache-breaker params untouched' do mock_app do |env| request = Rack::Request.new(env) [200, {'Content-Type' => 'text/plain'}, ['hi']] end get '/?95df8d9bf5237ad08df3115ee74dcb10' body.should == 'hi' end end end rack-protection-1.5.1/spec/json_csrf_spec.rb0000644000004100000410000000350612234224453021136 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::JsonCsrf do it_behaves_like "any rack application" describe 'json response' do before do mock_app { |e| [200, {'Content-Type' => 'application/json'}, []]} end it "denies get requests with json responses with a remote referrer" do get('/', {}, 'HTTP_REFERER' => 'http://evil.com').should_not be_ok end it "accepts requests with json responses with a remote referrer when there's an origin header set" do get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_ORIGIN' => 'http://good.com').should be_ok end it "accepts requests with json responses with a remote referrer when there's an x-origin header set" do get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_X_ORIGIN' => 'http://good.com').should be_ok end it "accepts get requests with json responses with a local referrer" do get('/', {}, 'HTTP_REFERER' => '/').should be_ok end it "accepts get requests with json responses with no referrer" do get('/', {}).should be_ok end it "accepts XHR requests" do get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest').should be_ok end end describe 'not json response' do it "accepts get requests with 304 headers" do mock_app { |e| [304, {}, []]} get('/', {}).status.should == 304 end end describe 'with drop_session as default reaction' do it 'still denies' do mock_app do use Rack::Protection, :reaction => :drop_session run proc { |e| [200, {'Content-Type' => 'application/json'}, []]} end session = {:foo => :bar} get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'rack.session' => session) last_response.should_not be_ok end end end rack-protection-1.5.1/spec/xss_header_spec.rb0000644000004100000410000000362212234224453021274 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::XSSHeader do it_behaves_like "any rack application" it 'should set the X-XSS-Protection' do get('/', {}, 'wants' => 'text/html;charset=utf-8').headers["X-XSS-Protection"].should == "1; mode=block" end it 'should set the X-XSS-Protection for XHTML' do get('/', {}, 'wants' => 'application/xhtml+xml').headers["X-XSS-Protection"].should == "1; mode=block" end it 'should not set the X-XSS-Protection for other content types' do get('/', {}, 'wants' => 'application/foo').headers["X-XSS-Protection"].should be_nil end it 'should allow changing the protection mode' do # I have no clue what other modes are available mock_app do use Rack::Protection::XSSHeader, :xss_mode => :foo run DummyApp end get('/', {}, 'wants' => 'application/xhtml').headers["X-XSS-Protection"].should == "1; mode=foo" end it 'should not override the header if already set' do mock_app with_headers("X-XSS-Protection" => "0") get('/', {}, 'wants' => 'text/html').headers["X-XSS-Protection"].should == "0" end it 'should set the X-Content-Type-Options' do get('/', {}, 'wants' => 'text/html').header["X-Content-Type-Options"].should == "nosniff" end it 'should set the X-Content-Type-Options for other content types' do get('/', {}, 'wants' => 'application/foo').header["X-Content-Type-Options"].should == "nosniff" end it 'should allow changing the nosniff-mode off' do mock_app do use Rack::Protection::XSSHeader, :nosniff => false run DummyApp end get('/').headers["X-Content-Type-Options"].should be_nil end it 'should not override the header if already set X-Content-Type-Options' do mock_app with_headers("X-Content-Type-Options" => "sniff") get('/', {}, 'wants' => 'text/html').headers["X-Content-Type-Options"].should == "sniff" end end rack-protection-1.5.1/spec/ip_spoofing_spec.rb0000644000004100000410000000233412234224453021462 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::IPSpoofing do it_behaves_like "any rack application" it 'accepts requests without X-Forward-For header' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_REAL_IP' => '4.3.2.1') last_response.should be_ok end it 'accepts requests with proper X-Forward-For header' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') last_response.should be_ok end it 'denies requests where the client spoofs X-Forward-For but not the IP' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '1.2.3.5') last_response.should_not be_ok end it 'denies requests where the client spoofs the IP but not X-Forward-For' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.5', 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') last_response.should_not be_ok end it 'denies requests where IP and X-Forward-For are spoofed but not X-Real-IP' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.5', 'HTTP_X_FORWARDED_FOR' => '1.2.3.5', 'HTTP_X_REAL_IP' => '1.2.3.4') last_response.should_not be_ok end end rack-protection-1.5.1/spec/base_spec.rb0000644000004100000410000000040212234224453020052 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::Base do describe "#random_string" do it "outputs a string of 32 characters" do described_class.new(lambda {}).random_string.length.should == 32 end end end rack-protection-1.5.1/spec/session_hijacking_spec.rb0000644000004100000410000000373512234224453022646 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::SessionHijacking do it_behaves_like "any rack application" it "accepts a session without changes to tracked parameters" do session = {:foo => :bar} get '/', {}, 'rack.session' => session get '/', {}, 'rack.session' => session session[:foo].should == :bar end it "denies requests with a changing User-Agent header" do session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'b' session.should be_empty end it "denies requests with a changing Accept-Encoding header" do session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b' session.should be_empty end it "denies requests with a changing Accept-Language header" do session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'b' session.should be_empty end it "accepts requests with the same Accept-Language header" do session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a' session.should_not be_empty end it "comparison of Accept-Language header is not case sensitive" do session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'A' session.should_not be_empty end it "accepts requests with a changing Version header"do session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.0' get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.1' session[:foo].should == :bar end end rack-protection-1.5.1/spec/http_origin_spec.rb0000644000004100000410000000216012234224453021471 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::HttpOrigin do it_behaves_like "any rack application" before(:each) do mock_app do use Rack::Protection::HttpOrigin run DummyApp end end %w(GET HEAD POST PUT DELETE).each do |method| it "accepts #{method} requests with no Origin" do send(method.downcase, '/').should be_ok end end %w(GET HEAD).each do |method| it "accepts #{method} requests with non-whitelisted Origin" do send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com').should be_ok end end %w(POST PUT DELETE).each do |method| it "denies #{method} requests with non-whitelisted Origin" do send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com').should_not be_ok end it "accepts #{method} requests with whitelisted Origin" do mock_app do use Rack::Protection::HttpOrigin, :origin_whitelist => ['http://www.friend.com'] run DummyApp end send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://www.friend.com').should be_ok end end end rack-protection-1.5.1/spec/authenticity_token_spec.rb0000644000004100000410000000314312234224453023057 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::AuthenticityToken do it_behaves_like "any rack application" it "denies post requests without any token" do post('/').should_not be_ok end it "accepts post requests with correct X-CSRF-Token header" do post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "a") last_response.should be_ok end it "denies post requests with wrong X-CSRF-Token header" do post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "b") last_response.should_not be_ok end it "accepts post form requests with correct authenticity_token field" do post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}) last_response.should be_ok end it "denies post form requests with wrong authenticity_token field" do post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}) last_response.should_not be_ok end it "prevents ajax requests without a valid token" do post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest").should_not be_ok end it "allows for a custom authenticity token param" do mock_app do use Rack::Protection::AuthenticityToken, :authenticity_param => 'csrf_param' run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] } end post('/', {"csrf_param" => "a"}, 'rack.session' => {:csrf => "a"}) last_response.should be_ok end it "sets a new csrf token for the session in env, even after a 'safe' request" do get('/', {}, {}) env['rack.session'][:csrf].should_not be_nil end end rack-protection-1.5.1/spec/spec_helper.rb0000644000004100000410000000741112234224453020426 0ustar www-datawww-datarequire 'rack/protection' require 'rack/test' require 'rack' require 'forwardable' require 'stringio' if defined? Gem.loaded_specs and Gem.loaded_specs.include? 'rack' version = Gem.loaded_specs['rack'].version.to_s else version = Rack.release + '.0' end if version == "1.3" Rack::Session::Abstract::ID.class_eval do private def prepare_session(env) session_was = env[ENV_SESSION_KEY] env[ENV_SESSION_KEY] = SessionHash.new(self, env) env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options) env[ENV_SESSION_KEY].merge! session_was if session_was end end end unless Rack::MockResponse.method_defined? :header Rack::MockResponse.send(:alias_method, :header, :headers) end module DummyApp def self.call(env) Thread.current[:last_env] = env body = (env['REQUEST_METHOD'] == 'HEAD' ? '' : 'ok') [200, {'Content-Type' => env['wants'] || 'text/plain'}, [body]] end end module TestHelpers extend Forwardable def_delegators :last_response, :body, :headers, :status, :errors def_delegators :current_session, :env_for attr_writer :app def app @app || mock_app(DummyApp) end def mock_app(app = nil, &block) app = block if app.nil? and block.arity == 1 if app klass = described_class mock_app do use Rack::Head use(Rack::Config) { |e| e['rack.session'] ||= {}} use klass run app end else @app = Rack::Lint.new Rack::Builder.new(&block).to_app end end def with_headers(headers) proc { [200, {'Content-Type' => 'text/plain'}.merge(headers), ['ok']] } end def env Thread.current[:last_env] end end # see http://blog.101ideas.cz/posts/pending-examples-via-not-implemented-error-in-rspec.html module NotImplementedAsPending def self.included(base) base.class_eval do alias_method :__finish__, :finish remove_method :finish end end def finish(reporter) if @exception.is_a?(NotImplementedError) from = @exception.backtrace[0] message = "#{@exception.message} (from #{from})" @pending_declared_in_example = message metadata[:pending] = true @exception = nil end __finish__(reporter) end RSpec::Core::Example.send :include, self end RSpec.configure do |config| config.expect_with :rspec, :stdlib config.include Rack::Test::Methods config.include TestHelpers end shared_examples_for 'any rack application' do it "should not interfere with normal get requests" do get('/').should be_ok body.should == 'ok' end it "should not interfere with normal head requests" do head('/').should be_ok end it 'should not leak changes to env' do klass = described_class detector = Struct.new(:app) detector.send(:define_method, :call) do |env| was = env.dup res = app.call(env) was.each do |k,v| next if env[k] == v fail "env[#{k.inspect}] changed from #{v.inspect} to #{env[k].inspect}" end res end mock_app do use Rack::Head use(Rack::Config) { |e| e['rack.session'] ||= {}} use detector use klass run DummyApp end get('/..', :foo => '').should be_ok end it 'allows passing on values in env' do klass = described_class detector = Struct.new(:app) changer = Struct.new(:app) detector.send(:define_method, :call) do |env| res = app.call(env) env['foo.bar'].should == 42 res end changer.send(:define_method, :call) do |env| env['foo.bar'] = 42 app.call(env) end mock_app do use Rack::Head use(Rack::Config) { |e| e['rack.session'] ||= {}} use detector use klass use changer run DummyApp end get('/').should be_ok end end rack-protection-1.5.1/spec/path_traversal_spec.rb0000644000004100000410000000256412234224453022172 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::PathTraversal do it_behaves_like "any rack application" context 'escaping' do before do mock_app { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO']]] } end %w[/foo/bar /foo/bar/ / /.f /a.x].each do |path| it("does not touch #{path.inspect}") { get(path).body.should == path } end { # yes, this is ugly, feel free to change that '/..' => '/', '/a/../b' => '/b', '/a/../b/' => '/b/', '/a/.' => '/a/', '/%2e.' => '/', '/a/%2E%2e/b' => '/b', '/a%2f%2E%2e%2Fb/' => '/b/', '//' => '/', '/%2fetc%2Fpasswd' => '/etc/passwd' }.each do |a, b| it("replaces #{a.inspect} with #{b.inspect}") { get(a).body.should == b } end it 'should be able to deal with PATH_INFO = nil (fcgi?)' do app = Rack::Protection::PathTraversal.new(proc { 42 }) app.call({}).should be == 42 end end if "".respond_to?(:encoding) # Ruby 1.9+ M17N context "PATH_INFO's encoding" do before do @app = Rack::Protection::PathTraversal.new(proc { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO'].encoding.to_s]] }) end it 'should remain unchanged as ASCII-8BIT' do body = @app.call({ 'PATH_INFO' => '/'.encode('ASCII-8BIT') })[2][0] body.should == 'ASCII-8BIT' end end end end rack-protection-1.5.1/spec/form_token_spec.rb0000644000004100000410000000213612234224453021311 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::FormToken do it_behaves_like "any rack application" it "denies post requests without any token" do post('/').should_not be_ok end it "accepts post requests with correct X-CSRF-Token header" do post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "a") last_response.should be_ok end it "denies post requests with wrong X-CSRF-Token header" do post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "b") last_response.should_not be_ok end it "accepts post form requests with correct authenticity_token field" do post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}) last_response.should be_ok end it "denies post form requests with wrong authenticity_token field" do post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}) last_response.should_not be_ok end it "accepts ajax requests without a valid token" do post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest").should be_ok end end rack-protection-1.5.1/spec/remote_token_spec.rb0000644000004100000410000000324712234224453021645 0ustar www-datawww-datarequire File.expand_path('../spec_helper.rb', __FILE__) describe Rack::Protection::RemoteToken do it_behaves_like "any rack application" it "accepts post requests with no referrer" do post('/').should be_ok end it "accepts post requests with a local referrer" do post('/', {}, 'HTTP_REFERER' => '/').should be_ok end it "denies post requests with a remote referrer and no token" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') last_response.should_not be_ok end it "accepts post requests with a remote referrer and correct X-CSRF-Token header" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "a") last_response.should be_ok end it "denies post requests with a remote referrer and wrong X-CSRF-Token header" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "b") last_response.should_not be_ok end it "accepts post form requests with a remote referrer and correct authenticity_token field" do post('/', {"authenticity_token" => "a"}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => {:csrf => "a"}) last_response.should be_ok end it "denies post form requests with a remote referrer and wrong authenticity_token field" do post('/', {"authenticity_token" => "a"}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => {:csrf => "b"}) last_response.should_not be_ok end end rack-protection-1.5.1/checksums.yaml.gz0000444000004100000410000000041412234224453020140 0ustar www-datawww-datadRe;n@{p‚tsO4>Ҽq.x\}ޯslŸ|\++`vy''715p cGyV ɺ8^ ̣Zi b6,MQd$5ݍrCfXN>:%Q0i~O-ee&K9YJoX&sSw7qZcH3vaB5ymTdяEbU۸屵[rack-protection-1.5.1/metadata.yml0000644000004100000410000000755112234224453017166 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: rack-protection version: !ruby/object:Gem::Version version: 1.5.1 platform: ruby authors: - Konstantin Haase - Alex Rodionov - Patrick Ellis - Jeff Welling - ITO Nobuaki - Matteo Centenaro - David Kellum - Egor Homakov - Florian Gilcher - Fojas - Mael Clerambault - Martin Mauch - SAKAI, Kazuaki - Stanislav Savulchik - Steve Agalloco - TOBY - Akzhan Abdulin - brookemckim - Bjørge Næss - Chris Heald - Chris Mytton - Corey Ward - Dario Cravero autorequire: bindir: bin cert_chain: [] date: 2013-10-21 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rack requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rack-test requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '2.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '2.0' description: You should use protection! email: - konstantin.mailinglists@googlemail.com - p0deje@gmail.com - patrick@soundcloud.com - jeff.welling@gmail.com - bugant@gmail.com - daydream.trippers@gmail.com - homakov@gmail.com - florian.gilcher@asquera.de - developer@fojasaur.us - mael@clerambault.fr - martin.mauch@gmail.com - kaz.july.7@gmail.com - s.savulchik@gmail.com - steve.agalloco@gmail.com - toby.net.info.mail+git@gmail.com - akzhan.abdulin@gmail.com - brooke@digitalocean.com - bjoerge@bengler.no - cheald@gmail.com - self@hecticjeff.net - coreyward@me.com - dario@uxtemple.com - dek-oss@gravitext.com executables: [] extensions: [] extra_rdoc_files: [] files: - License - README.md - Rakefile - lib/rack-protection.rb - lib/rack/protection.rb - lib/rack/protection/authenticity_token.rb - lib/rack/protection/base.rb - lib/rack/protection/escaped_params.rb - lib/rack/protection/form_token.rb - lib/rack/protection/frame_options.rb - lib/rack/protection/http_origin.rb - lib/rack/protection/ip_spoofing.rb - lib/rack/protection/json_csrf.rb - lib/rack/protection/path_traversal.rb - lib/rack/protection/remote_referrer.rb - lib/rack/protection/remote_token.rb - lib/rack/protection/session_hijacking.rb - lib/rack/protection/version.rb - lib/rack/protection/xss_header.rb - rack-protection.gemspec - spec/authenticity_token_spec.rb - spec/base_spec.rb - spec/escaped_params_spec.rb - spec/form_token_spec.rb - spec/frame_options_spec.rb - spec/http_origin_spec.rb - spec/ip_spoofing_spec.rb - spec/json_csrf_spec.rb - spec/path_traversal_spec.rb - spec/protection_spec.rb - spec/remote_referrer_spec.rb - spec/remote_token_spec.rb - spec/session_hijacking_spec.rb - spec/spec_helper.rb - spec/xss_header_spec.rb homepage: http://github.com/rkh/rack-protection licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.0.7 signing_key: specification_version: 4 summary: You should use protection! test_files: [] has_rdoc: rack-protection-1.5.1/lib/0000755000004100000410000000000012234224453015421 5ustar www-datawww-datarack-protection-1.5.1/lib/rack/0000755000004100000410000000000012234224453016341 5ustar www-datawww-datarack-protection-1.5.1/lib/rack/protection/0000755000004100000410000000000012234224453020527 5ustar www-datawww-datarack-protection-1.5.1/lib/rack/protection/session_hijacking.rb0000644000004100000410000000230412234224453024545 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: Session Hijacking # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Session_hijacking # # Tracks request properties like the user agent in the session and empties # the session if those properties change. This essentially prevents attacks # from Firesheep. Since all headers taken into consideration might be # spoofed, too, this will not prevent all hijacking attempts. class SessionHijacking < Base default_reaction :drop_session default_options :tracking_key => :tracking, :encrypt_tracking => true, :track => %w[HTTP_USER_AGENT HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE] def accepts?(env) session = session env key = options[:tracking_key] if session.include? key session[key].all? { |k,v| v == encrypt(env[k]) } else session[key] = {} options[:track].each { |k| session[key][k] = encrypt(env[k]) } end end def encrypt(value) value = value.to_s.downcase options[:encrypt_tracking] ? super(value) : value end end end end rack-protection-1.5.1/lib/rack/protection/remote_token.rb0000644000004100000410000000113412234224453023546 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # # Only accepts unsafe HTTP requests if a given access token matches the token # included in the session *or* the request comes from the same origin. # # Compatible with Rails and rack-csrf. class RemoteToken < AuthenticityToken default_reaction :deny def accepts?(env) super or referrer(env) == Request.new(env).host end end end end rack-protection-1.5.1/lib/rack/protection/version.rb0000644000004100000410000000050512234224453022541 0ustar www-datawww-datamodule Rack module Protection def self.version VERSION end SIGNATURE = [1, 5, 1] VERSION = SIGNATURE.join('.') VERSION.extend Comparable def VERSION.<=>(other) other = other.split('.').map { |i| i.to_i } if other.respond_to? :split SIGNATURE <=> Array(other) end end end rack-protection-1.5.1/lib/rack/protection/base.rb0000755000004100000410000000621312234224453021773 0ustar www-datawww-datarequire 'rack/protection' require 'digest' require 'logger' require 'uri' module Rack module Protection class Base DEFAULT_OPTIONS = { :reaction => :default_reaction, :logging => true, :message => 'Forbidden', :encryptor => Digest::SHA1, :session_key => 'rack.session', :status => 403, :allow_empty_referrer => true, :report_key => "protection.failed", :html_types => %w[text/html application/xhtml] } attr_reader :app, :options def self.default_options(options) define_method(:default_options) { super().merge(options) } end def self.default_reaction(reaction) alias_method(:default_reaction, reaction) end def default_options DEFAULT_OPTIONS end def initialize(app, options = {}) @app, @options = app, default_options.merge(options) end def safe?(env) %w[GET HEAD OPTIONS TRACE].include? env['REQUEST_METHOD'] end def accepts?(env) raise NotImplementedError, "#{self.class} implementation pending" end def call(env) unless accepts? env warn env, "attack prevented by #{self.class}" instrument env result = react env end result or app.call(env) end def react(env) result = send(options[:reaction], env) result if Array === result and result.size == 3 end def warn(env, message) return unless options[:logging] l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors']) l.warn(message) end def instrument(env) return unless i = options[:instrumenter] env['rack.protection.attack'] = self.class.name.split('::').last.downcase i.instrument('rack.protection', env) end def deny(env) [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]] end def report(env) env[options[:report_key]] = true end def session?(env) env.include? options[:session_key] end def session(env) return env[options[:session_key]] if session? env fail "you need to set up a session middleware *before* #{self.class}" end def drop_session(env) session(env).clear if session? env end def referrer(env) ref = env['HTTP_REFERER'].to_s return if !options[:allow_empty_referrer] and ref.empty? URI.parse(ref).host || Request.new(env).host end def origin(env) env['HTTP_ORIGIN'] || env['HTTP_X_ORIGIN'] end def random_string(secure = defined? SecureRandom) secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1) rescue NotImplementedError random_string false end def encrypt(value) options[:encryptor].hexdigest value.to_s end alias default_reaction deny def html?(headers) return false unless header = headers.detect { |k,v| k.downcase == 'content-type' } options[:html_types].include? header.last[/^\w+\/\w+/] end end end end rack-protection-1.5.1/lib/rack/protection/json_csrf.rb0000644000004100000410000000210112234224453023034 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://flask.pocoo.org/docs/security/#json-security # # JSON GET APIs are vulnerable to being embedded as JavaScript while the # Array prototype has been patched to track data. Checks the referrer # even on GET requests if the content type is JSON. class JsonCsrf < Base alias react deny def call(env) request = Request.new(env) status, headers, body = app.call(env) if has_vector? request, headers warn env, "attack prevented by #{self.class}" react(env) or [status, headers, body] else [status, headers, body] end end def has_vector?(request, headers) return false if request.xhr? return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/ origin(request.env).nil? and referrer(request.env) != request.host end end end end rack-protection-1.5.1/lib/rack/protection/ip_spoofing.rb0000644000004100000410000000130112234224453023363 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: IP spoofing # Supported browsers:: all # More infos:: http://blog.c22.cc/2011/04/22/surveymonkey-ip-spoofing/ # # Detect (some) IP spoofing attacks. class IPSpoofing < Base default_reaction :deny def accepts?(env) return true unless env.include? 'HTTP_X_FORWARDED_FOR' ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/) return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP'] return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP'] true end end end end rack-protection-1.5.1/lib/rack/protection/http_origin.rb0000644000004100000410000000203112234224453023376 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: Google Chrome 2, Safari 4 and later # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # http://tools.ietf.org/html/draft-abarth-origin # # Does not accept unsafe HTTP requests when value of Origin HTTP request header # does not match default or whitelisted URIs. class HttpOrigin < Base DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } default_reaction :deny def base_url(env) request = Rack::Request.new(env) port = ":#{request.port}" unless request.port == DEFAULT_PORTS[request.scheme] "#{request.scheme}://#{request.host}#{port}" end def accepts?(env) return true if safe? env return true unless origin = env['HTTP_ORIGIN'] return true if base_url(env) == origin Array(options[:origin_whitelist]).include? origin end end end end rack-protection-1.5.1/lib/rack/protection/path_traversal.rb0000644000004100000410000000241312234224453024073 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: Directory traversal # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Directory_traversal # # Unescapes '/' and '.', expands +path_info+. # Thus GET /foo/%2e%2e%2fbar becomes GET /bar. class PathTraversal < Base def call(env) path_was = env["PATH_INFO"] env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty? app.call env ensure env["PATH_INFO"] = path_was end def cleanup(path) if path.respond_to?(:encoding) # Ruby 1.9+ M17N encoding = path.encoding dot = '.'.encode(encoding) slash = '/'.encode(encoding) else # Ruby 1.8 dot = '.' slash = '/' end parts = [] unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash) unescaped.split(slash).each do |part| next if part.empty? or part == dot part == '..' ? parts.pop : parts << part end cleaned = slash + parts.join(slash) cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$} cleaned end end end end rack-protection-1.5.1/lib/rack/protection/authenticity_token.rb0000644000004100000410000000161712234224453024773 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # # Only accepts unsafe HTTP requests if a given access token matches the token # included in the session. # # Compatible with Rails and rack-csrf. # # Options: # # authenticity_param: Defines the param's name that should contain the token on a request. # class AuthenticityToken < Base default_options :authenticity_param => 'authenticity_token' def accepts?(env) session = session env token = session[:csrf] ||= session['_csrf_token'] || random_string safe?(env) || env['HTTP_X_CSRF_TOKEN'] == token || Request.new(env).params[options[:authenticity_param]] == token end end end end rack-protection-1.5.1/lib/rack/protection/frame_options.rb0000644000004100000410000000234412234224453023724 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: Clickjacking # Supported browsers:: Internet Explorer 8, Firefox 3.6.9, Opera 10.50, # Safari 4.0, Chrome 4.1.249.1042 and later # More infos:: https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header # # Sets X-Frame-Options header to tell the browser avoid embedding the page # in a frame. # # Options: # # frame_options:: Defines who should be allowed to embed the page in a # frame. Use :deny to forbid any embedding, :sameorigin # to allow embedding from the same origin (default). class FrameOptions < Base default_options :frame_options => :sameorigin def frame_options @frame_options ||= begin frame_options = options[:frame_options] frame_options = options[:frame_options].to_s.upcase unless frame_options.respond_to? :to_str frame_options.to_str end end def call(env) status, headers, body = @app.call(env) headers['X-Frame-Options'] ||= frame_options if html? headers [status, headers, body] end end end end rack-protection-1.5.1/lib/rack/protection/escaped_params.rb0000644000004100000410000000442212234224453024025 0ustar www-datawww-datarequire 'rack/protection' require 'rack/utils' begin require 'escape_utils' rescue LoadError end module Rack module Protection ## # Prevented attack:: XSS # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting # # Automatically escapes Rack::Request#params so they can be embedded in HTML # or JavaScript without any further issues. Calls +html_safe+ on the escaped # strings if defined, to avoid double-escaping in Rails. # # Options: # escape:: What escaping modes to use, should be Symbol or Array of Symbols. # Available: :html (default), :javascript, :url class EscapedParams < Base extend Rack::Utils class << self alias escape_url escape public :escape_html end default_options :escape => :html, :escaper => defined?(EscapeUtils) ? EscapeUtils : self def initialize(*) super modes = Array options[:escape] @escaper = options[:escaper] @html = modes.include? :html @javascript = modes.include? :javascript @url = modes.include? :url if @javascript and not @escaper.respond_to? :escape_javascript fail("Use EscapeUtils for JavaScript escaping.") end end def call(env) request = Request.new(env) get_was = handle(request.GET) post_was = handle(request.POST) rescue nil app.call env ensure request.GET.replace get_was if get_was request.POST.replace post_was if post_was end def handle(hash) was = hash.dup hash.replace escape(hash) was end def escape(object) case object when Hash then escape_hash(object) when Array then object.map { |o| escape(o) } when String then escape_string(object) else nil end end def escape_hash(hash) hash = hash.dup hash.each { |k,v| hash[k] = escape(v) } hash end def escape_string(str) str = @escaper.escape_url(str) if @url str = @escaper.escape_html(str) if @html str = @escaper.escape_javascript(str) if @javascript str end end end end rack-protection-1.5.1/lib/rack/protection/form_token.rb0000644000004100000410000000134112234224453023216 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # # Only accepts submitted forms if a given access token matches the token # included in the session. Does not expect such a token from Ajax request. # # This middleware is not used when using the Rack::Protection collection, # since it might be a security issue, depending on your application # # Compatible with Rails and rack-csrf. class FormToken < AuthenticityToken def accepts?(env) env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super end end end end rack-protection-1.5.1/lib/rack/protection/xss_header.rb0000644000004100000410000000154412234224453023205 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: Non-permanent XSS # Supported browsers:: Internet Explorer 8 and later # More infos:: http://blogs.msdn.com/b/ie/archive/2008/07/01/ie8-security-part-iv-the-xss-filter.aspx # # Sets X-XSS-Protection header to tell the browser to block attacks. # # Options: # xss_mode:: How the browser should prevent the attack (default: :block) class XSSHeader < Base default_options :xss_mode => :block, :nosniff => true def call(env) status, headers, body = @app.call(env) headers['X-XSS-Protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers headers['X-Content-Type-Options'] ||= 'nosniff' if options[:nosniff] [status, headers, body] end end end end rack-protection-1.5.1/lib/rack/protection/remote_referrer.rb0000644000004100000410000000076212234224453024250 0ustar www-datawww-datarequire 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # # Does not accept unsafe HTTP requests if the Referer [sic] header is set to # a different host. class RemoteReferrer < Base default_reaction :deny def accepts?(env) safe?(env) or referrer(env) == Request.new(env).host end end end end rack-protection-1.5.1/lib/rack/protection.rb0000644000004100000410000000426712234224453021065 0ustar www-datawww-datarequire 'rack/protection/version' require 'rack' module Rack module Protection autoload :AuthenticityToken, 'rack/protection/authenticity_token' autoload :Base, 'rack/protection/base' autoload :EscapedParams, 'rack/protection/escaped_params' autoload :FormToken, 'rack/protection/form_token' autoload :FrameOptions, 'rack/protection/frame_options' autoload :HttpOrigin, 'rack/protection/http_origin' autoload :IPSpoofing, 'rack/protection/ip_spoofing' autoload :JsonCsrf, 'rack/protection/json_csrf' autoload :PathTraversal, 'rack/protection/path_traversal' autoload :RemoteReferrer, 'rack/protection/remote_referrer' autoload :RemoteToken, 'rack/protection/remote_token' autoload :SessionHijacking, 'rack/protection/session_hijacking' autoload :XSSHeader, 'rack/protection/xss_header' def self.new(app, options = {}) # does not include: RemoteReferrer, AuthenticityToken and FormToken except = Array options[:except] use_these = Array options[:use] Rack::Builder.new do use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer use ::Rack::Protection::AuthenticityToken,options if use_these.include? :authenticity_token use ::Rack::Protection::FormToken, options if use_these.include? :form_token use ::Rack::Protection::FrameOptions, options unless except.include? :frame_options use ::Rack::Protection::HttpOrigin, options unless except.include? :http_origin use ::Rack::Protection::IPSpoofing, options unless except.include? :ip_spoofing use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header run app end.to_app end end end rack-protection-1.5.1/lib/rack-protection.rb0000644000004100000410000000003212234224453021045 0ustar www-datawww-datarequire "rack/protection"