From 3627a9f13bf7a1178a9feb927e1ee6cbb75ea246 Mon Sep 17 00:00:00 2001 From: Maciej Moleda Date: Mon, 18 Jan 2016 11:40:35 +0000 Subject: [PATCH] Add Strict Transport Security protection --- rack-protection/README.md | 6 +++ rack-protection/lib/rack/protection.rb | 2 + .../lib/rack/protection/strict_transport.rb | 37 +++++++++++++++++++ .../rack/protection/strict_transport_spec.rb | 25 +++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 rack-protection/lib/rack/protection/strict_transport.rb create mode 100644 rack-protection/spec/lib/rack/protection/strict_transport_spec.rb diff --git a/rack-protection/README.md b/rack-protection/README.md index b2dbab5d..670641f2 100644 --- a/rack-protection/README.md +++ b/rack-protection/README.md @@ -74,6 +74,12 @@ Prevented by: * `Rack::Protection::IPSpoofing` +## Helps to protect against protocol downgrade attacks and cookie hijacking + +Prevented by: + +* `Rack::Protection::StrictTransport` (not included by `use Rack::Protection`) + # Installation gem install rack-protection diff --git a/rack-protection/lib/rack/protection.rb b/rack-protection/lib/rack/protection.rb index 604e749c..18871191 100644 --- a/rack-protection/lib/rack/protection.rb +++ b/rack-protection/lib/rack/protection.rb @@ -16,6 +16,7 @@ module Rack autoload :RemoteToken, 'rack/protection/remote_token' autoload :SessionHijacking, 'rack/protection/session_hijacking' autoload :XSSHeader, 'rack/protection/xss_header' + autoload :StrictTransport, 'rack/protection/strict_transport' def self.new(app, options = {}) # does not include: RemoteReferrer, AuthenticityToken and FormToken @@ -25,6 +26,7 @@ module Rack 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::StrictTransport, options if use_these.include? :strict_transport 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 diff --git a/rack-protection/lib/rack/protection/strict_transport.rb b/rack-protection/lib/rack/protection/strict_transport.rb new file mode 100644 index 00000000..fd54cfbf --- /dev/null +++ b/rack-protection/lib/rack/protection/strict_transport.rb @@ -0,0 +1,37 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Protects against against protocol downgrade attacks and cookie hijacking. + # Supported browsers:: all + # More infos:: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security + # + # browser will prevent any communications from being sent over HTTP + # to the specified domain and will instead send all communications over HTTPS. + # It also prevents HTTPS click through prompts on browsers. + # + # Options: + # + # max_age:: How long future requests to the domain should go over HTTPS; specified in seconds + # include_subdomains:: If all present and future subdomains will be HTTPS + + class StrictTransport < Base + default_options :max_age => 31_536_000, :include_subdomains => false + + def strict_transport + @strict_transport ||= begin + strict_transport = 'max-age=' + options[:max_age].to_s + strict_transport += '; includeSubDomains' if options[:include_subdomains] + strict_transport.to_str + end + end + + def call(env) + status, headers, body = @app.call(env) + headers['Strict-Transport-Security'] ||= strict_transport + [status, headers, body] + end + end + end +end diff --git a/rack-protection/spec/lib/rack/protection/strict_transport_spec.rb b/rack-protection/spec/lib/rack/protection/strict_transport_spec.rb new file mode 100644 index 00000000..dbb4fde8 --- /dev/null +++ b/rack-protection/spec/lib/rack/protection/strict_transport_spec.rb @@ -0,0 +1,25 @@ +describe Rack::Protection::StrictTransport do + it_behaves_like "any rack application" + + it 'should set the Strict-Transport-Security header' do + expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000") + end + + it 'should allow changing the max-age option' do + mock_app do + use Rack::Protection::StrictTransport, :max_age => 16_070_400 + run DummyApp + end + + expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=16070400") + end + + it 'should allow switching on the include_subdomains option' do + mock_app do + use Rack::Protection::StrictTransport, :include_subdomains => true + run DummyApp + end + + expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000; includeSubDomains") + end +end