added XPermittedCrossDomainPolicies header support

This commit is contained in:
Steve Agalloco 2014-11-15 16:57:44 -05:00
Родитель 97e1751c31
Коммит f3739695e9
11 изменённых файлов: 164 добавлений и 3 удалений

Просмотреть файл

@ -6,6 +6,7 @@ The gem will automatically apply several headers that are related to security.
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options draft](https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02)
- X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](http://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
- X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(v=vs.85\).aspx)
- X-Permitted-Cross-Domain-Policies - [Restrict Adobe Flash Player's access to data](https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html)
This gem has integration with Rails, but works for any Ruby code. See the sinatra example section.
@ -48,6 +49,7 @@ The following methods are going to be called, unless they are provided in a `ski
* `:set_x_frame_options_header`
* `:set_x_xss_protection_header`
* `:set_x_content_type_options_header`
* `:set_x_permitted_cross_domain_policies_header`
### Bonus Features
@ -65,6 +67,7 @@ This gem makes a few assumptions about how you will use some features. For exam
config.x_frame_options = 'DENY'
config.x_content_type_options = "nosniff"
config.x_xss_protection = {:value => 1, :mode => 'block'}
config.x_permitted_cross_domain_policies = 'none'
config.csp = {
:default_src => "https: self",
:frame_src => "https: http:.twimg.com http://itunes.apple.com",
@ -106,6 +109,7 @@ This configuration will likely work for most applications without modification.
:x_frame_options => {:value => 'SAMEORIGIN'}
:x_xss_protection => {:value => 1, :mode => 'block'} # set the :mode option to false to use "warning only" mode
:x_content_type_options => {:value => 'nosniff'}
:x_permitted_cross_domain_policies => {:value => 'none'}
```
### Content Security Policy (CSP)
@ -335,6 +339,7 @@ require 'secure_headers'
config.x_frame_options = 'DENY'
config.x_content_type_options = "nosniff"
config.x_xss_protection = {:value => 1, :mode => false}
config.x_permitted_cross_domain_policies = 'none'
config.csp = {
:default_src => "https: inline eval",
:report_uri => '//example.com/uri-directive',
@ -390,6 +395,7 @@ def before_load
config.x_frame_options = 'DENY'
config.x_content_type_options = "nosniff"
config.x_xss_protection = {:value => '1', :mode => false}
config.x_permitted_cross_domain_policies = 'none'
config.csp = {
:default_src => "https: inline eval",
:report_uri => '//example.com/uri-directive',

Просмотреть файл

@ -3,6 +3,7 @@
config.x_frame_options = 'SAMEORIGIN'
config.x_content_type_options = "nosniff"
config.x_xss_protection = {:value => 1, :mode => 'block'}
config.x_permitted_cross_domain_policies = 'none'
csp = {
:default_src => "self",
:script_src => "self nonce",

Просмотреть файл

@ -67,6 +67,11 @@ describe OtherThingsController, :type => :controller do
expect(@env['X-Content-Type-Options']).to eq("nosniff")
end
it "sets the X-Permitted-Cross-Domain-Policies" do
get '/'
expect(@env['X-Permitted-Cross-Domain-Policies']).to eq("none")
end
context "using IE" do
it "sets the X-Content-Type-Options header" do
@env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"

Просмотреть файл

@ -38,6 +38,11 @@ describe ThingsController, :type => :controller do
expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
end
it "sets the X-Permitted-Cross-Domain-Policies" do
get :index
expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
end
context "using IE" do
it "sets the X-Content-Type-Options header" do
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"

Просмотреть файл

@ -34,6 +34,11 @@ describe OtherThingsController, :type => :controller do
expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
end
it "sets the X-Permitted-Cross-Domain-Policies" do
get :index
expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
end
context "using IE" do
it "sets the X-Content-Type-Options header" do
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"

Просмотреть файл

@ -38,6 +38,11 @@ describe ThingsController, :type => :controller do
expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
end
it "sets the X-Permitted-Cross-Domain-Policies" do
get :index
expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
end
context "using IE" do
it "sets the X-Content-Type-Options header" do
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"

Просмотреть файл

@ -5,7 +5,8 @@ module SecureHeaders
module Configuration
class << self
attr_accessor :hsts, :x_frame_options, :x_content_type_options,
:x_xss_protection, :csp, :x_download_options, :script_hashes
:x_xss_protection, :csp, :x_download_options, :script_hashes,
:x_permitted_cross_domain_policies
def configure &block
instance_eval &block
@ -46,6 +47,7 @@ module SecureHeaders
before_filter :set_x_xss_protection_header
before_filter :set_x_content_type_options_header
before_filter :set_x_download_options_header
before_filter :set_x_permitted_cross_domain_policies_header
end
# we can't use ||= because I'm overloading false => disable, nil => default
@ -63,6 +65,7 @@ module SecureHeaders
set_x_xss_protection_header(options[:x_xss_protection])
set_x_content_type_options_header(options[:x_content_type_options])
set_x_download_options_header(options[:x_download_options])
set_x_permitted_cross_domain_policies_header(options[:x_permitted_cross_domain_policies])
end
# set_csp_header - uses the request accessor and SecureHeader::Configuration settings
@ -137,6 +140,10 @@ module SecureHeaders
set_a_header(:x_download_options, XDownloadOptions, options)
end
def set_x_permitted_cross_domain_policies_header(options=self.class.secure_headers_options[:x_permitted_cross_domain_policies])
set_a_header(:x_permitted_cross_domain_policies, XPermittedCrossDomainPolicies, options)
end
private
def set_a_header(name, klass, options=nil)
@ -167,6 +174,7 @@ require "secure_headers/headers/strict_transport_security"
require "secure_headers/headers/x_xss_protection"
require "secure_headers/headers/x_content_type_options"
require "secure_headers/headers/x_download_options"
require "secure_headers/headers/x_permitted_cross_domain_policies"
require "secure_headers/railtie"
require "secure_headers/hash_helper"
require "secure_headers/view_helper"

Просмотреть файл

@ -0,0 +1,40 @@
module SecureHeaders
class XPCDPBuildError < StandardError; end
class XPermittedCrossDomainPolicies < Header
module Constants
XPCDP_HEADER_NAME = "X-Permitted-Cross-Domain-Policies"
DEFAULT_VALUE = 'none'
VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
end
include Constants
def initialize(config = nil)
@config = config
validate_config unless @config.nil?
end
def name
XPCDP_HEADER_NAME
end
def value
case @config
when NilClass
DEFAULT_VALUE
when String
@config
else
@config[:value]
end
end
private
def validate_config
value = @config.is_a?(Hash) ? @config[:value] : @config
unless VALID_POLICIES.include?(value.downcase)
raise XPCDPBuildError.new("Value can only be one of #{VALID_POLICIES.join(', ')}")
end
end
end
end

Просмотреть файл

@ -0,0 +1,64 @@
module SecureHeaders
describe XPermittedCrossDomainPolicies do
specify { expect(XPermittedCrossDomainPolicies.new.name).to eq(XPermittedCrossDomainPolicies::Constants::XPCDP_HEADER_NAME)}
specify { expect(XPermittedCrossDomainPolicies.new.value).to eq("none")}
specify { expect(XPermittedCrossDomainPolicies.new('master-only').value).to eq('master-only')}
specify { expect(XPermittedCrossDomainPolicies.new(:value => 'master-only').value).to eq('master-only') }
context "valid configuration values" do
it "accepts 'all'" do
expect {
XPermittedCrossDomainPolicies.new("all")
}.not_to raise_error
expect {
XPermittedCrossDomainPolicies.new(:value => "all")
}.not_to raise_error
end
it "accepts 'by-ftp-filename'" do
expect {
XPermittedCrossDomainPolicies.new("by-ftp-filename")
}.not_to raise_error
expect {
XPermittedCrossDomainPolicies.new(:value => "by-ftp-filename")
}.not_to raise_error
end
it "accepts 'by-content-type'" do
expect {
XPermittedCrossDomainPolicies.new("by-content-type")
}.not_to raise_error
expect {
XPermittedCrossDomainPolicies.new(:value => "by-content-type")
}.not_to raise_error
end
it "accepts 'master-only'" do
expect {
XPermittedCrossDomainPolicies.new("master-only")
}.not_to raise_error
expect {
XPermittedCrossDomainPolicies.new(:value => "master-only")
}.not_to raise_error
end
it "accepts nil" do
expect {
XPermittedCrossDomainPolicies.new
}.not_to raise_error
end
end
context 'invlaid configuration values' do
it "doesn't accept invalid values" do
expect {
XPermittedCrossDomainPolicies.new("open")
}.to raise_error
end
end
end
end

Просмотреть файл

@ -18,7 +18,7 @@ describe SecureHeaders do
allow(subject).to receive(:request).and_return(request)
end
ALL_HEADERS = Hash[[:hsts, :csp, :x_frame_options, :x_content_type_options, :x_xss_protection].map{|header| [header, false]}]
ALL_HEADERS = Hash[[:hsts, :csp, :x_frame_options, :x_content_type_options, :x_xss_protection, :x_permitted_cross_domain_policies].map{|header| [header, false]}]
def stub_user_agent val
allow(request).to receive_message_chain(:env, :[]).and_return(val)
@ -36,6 +36,7 @@ describe SecureHeaders do
config.x_xss_protection = nil
config.csp = nil
config.x_download_options = nil
config.x_permitted_cross_domain_policies = nil
end
end
@ -46,6 +47,7 @@ describe SecureHeaders do
subject.set_x_content_type_options_header
subject.set_x_xss_protection_header
subject.set_x_download_options_header
subject.set_x_permitted_cross_domain_policies_header
end
describe "#set_header" do
@ -64,7 +66,7 @@ describe SecureHeaders do
USER_AGENTS.each do |name, useragent|
it "sets all default headers for #{name} (smoke test)" do
stub_user_agent(useragent)
number_of_headers = 6
number_of_headers = 7
expect(subject).to receive(:set_header).exactly(number_of_headers).times # a request for a given header
subject.set_csp_header
subject.set_x_frame_options_header
@ -72,6 +74,7 @@ describe SecureHeaders do
subject.set_x_xss_protection_header
subject.set_x_content_type_options_header
subject.set_x_download_options_header
subject.set_x_permitted_cross_domain_policies_header
end
end
@ -96,6 +99,11 @@ describe SecureHeaders do
subject.set_x_frame_options_header(false)
end
it "does not set the X-Permitted-Cross-Domain-Policies header if disabled" do
should_not_assign_header(XPCDP_HEADER_NAME)
subject.set_x_permitted_cross_domain_policies_header(false)
end
it "does not set the HSTS header if disabled" do
should_not_assign_header(HSTS_HEADER_NAME)
subject.set_hsts_header(false)
@ -133,6 +141,7 @@ describe SecureHeaders do
config.x_xss_protection = false
config.csp = false
config.x_download_options = false
config.x_permitted_cross_domain_policies = false
end
expect(subject).not_to receive(:set_header)
set_security_headers(subject)
@ -249,4 +258,16 @@ describe SecureHeaders do
end
end
end
describe "#set_x_permitted_cross_domain_policies_header" do
it "sets the X-Permitted-Cross-Domain-Policies header" do
should_assign_header(XPCDP_HEADER_NAME, SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
subject.set_x_permitted_cross_domain_policies_header
end
it "allows a custom X-Permitted-Cross-Domain-Policies header" do
should_assign_header(XPCDP_HEADER_NAME, "master-only")
subject.set_x_permitted_cross_domain_policies_header(:value => 'master-only')
end
end
end

Просмотреть файл

@ -13,6 +13,7 @@ include ::SecureHeaders::XFrameOptions::Constants
include ::SecureHeaders::XXssProtection::Constants
include ::SecureHeaders::XContentTypeOptions::Constants
include ::SecureHeaders::XDownloadOptions::Constants
include ::SecureHeaders::XPermittedCrossDomainPolicies::Constants
USER_AGENTS = {
:firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',