added content security policies

This commit is contained in:
Christian Meier 2014-02-21 11:50:44 +00:00
parent 3eec7838ec
commit e46535ba67
4 changed files with 136 additions and 24 deletions

View File

@ -51,6 +51,7 @@ Prevented by:
* `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`)
* `Rack::Protection::XSSHeader` (Internet Explorer only)
* `Rack::Protection::ContentSecurityPolicy`
## Clickjacking

View File

@ -3,36 +3,38 @@ 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'
autoload :AuthenticityToken, 'rack/protection/authenticity_token'
autoload :Base, 'rack/protection/base'
autoload :ContentSecurityPolicy, 'rack/protection/content_security_policy'
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
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::ContentSecurityPolicy, options unless except.include? :frame_options
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

View File

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
require 'rack/protection'
module Rack
module Protection
##
# Prevented attack:: XSS and others
# Supported browsers:: Firefox 23+, Safari 7+, Chrome 25+, Opera 15+
# http://caniuse.com/contentsecuritypolicy
# More infos:: http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# http://content-security-policy.com/
#
# Sets Content-Security-Policy-Report(-Only) header to tell the browser what resource are allowed to load from which domain.
#
# Options:
# (descriptions taken from http://www.html5rocks.com/en/tutorials/security/content-security-policy/)
#
# connect_src:: limits the origins to which you can connect (via XHR,
# WebSockets, and EventSource).
#
# font_src:: specifies the origins that can serve web fonts. Googles
# Web Fonts could be enabled via font-src
# https://themes.googleusercontent.com
#
# frame_src:: lists the origins that can be embedded as frames. For
# example: frame-src https://youtube.com would enable
# embedding YouTube videos, but no other origins.
#
# img_src:: defines the origins from which images can be loaded.
#
# media_src:: restricts the origins allowed to deliver video and audio.
#
# object_src:: allows control over Flash and other plugins.
#
# style_src:: is script-srcs counterpart for stylesheets.
#
# report_uri:: instruct the browser to POST JSON-formatted violation
# reports to a location specified in a report-uri directive.
#
# report_only:: ask the browser to monitor a policy, reporting violations,
# but not enforcing the restrictions.
#
# sandbox:: if the sandbox directive is present, the page will be
# treated as though it was loaded inside of an iframe with
# a sandbox attribute.
class ContentSecurityPolicy < Base
default_options :default_src => :none, :script_src => :self, :img_src => :self, :style_src => :self, :connect_src => :self, :report_only => false
KEYS = [:default_src, :script_src, :connect_src, :font_src, :frame_src, :media_src, :style_src, :object_src, :report_uri, :sandbox]
def collect_options
KEYS.collect do |k|
"#{k.to_s.sub(/_/, '-')} #{options[k]}" if options.key?(k)
end.select{ |i| i }.join('; ')
end
def call(env)
status, headers, body = @app.call(env)
header = options[:report_only] ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
headers[header] ||= collect_options if html? headers
[status, headers, body]
end
end
end
end

View File

@ -0,0 +1,44 @@
require File.expand_path('../spec_helper.rb', __FILE__)
describe Rack::Protection::ContentSecurityPolicy do
it_behaves_like "any rack application"
it 'should set the Content Security Policy' do
get('/', {}, 'wants' => 'text/html').headers["Content-Security-Policy"].should == "default-src none; script-src self; connect-src self; style-src self"
end
it 'should not set the Content Security Policy for other content types' do
headers = get('/', {}, 'wants' => 'text/foo').headers
headers["Content-Security-Policy"].should be_nil
headers["Content-Security-Policy-Report-Only"].should be_nil
end
it 'should allow changing the protection settings' do
mock_app do
use Rack::Protection::ContentSecurityPolicy, :default_src => 'none', :script_src => 'https://cdn.mybank.net', :style_src => 'https://cdn.mybank.net', :img_src => 'https://cdn.mybank.net', :connect_src => 'https://api.mybank.com', :frame_src => 'self', :font_src => 'https://cdn.mybank.net', :object_src => 'https://cdn.mybank.net', :media_src => 'https://cdn.mybank.net', :report_uri => '/my_amazing_csp_report_parser', :sandbox => 'allow-scripts'
run DummyApp
end
headers = get('/', {}, 'wants' => 'text/html').headers
headers["Content-Security-Policy"].should == "default-src none; script-src https://cdn.mybank.net; connect-src https://api.mybank.com; font-src https://cdn.mybank.net; frame-src self; media-src https://cdn.mybank.net; style-src https://cdn.mybank.net; object-src https://cdn.mybank.net; report-uri /my_amazing_csp_report_parser; sandbox allow-scripts"
headers["Content-Security-Policy-Report-Only"].should be_nil
end
it 'should allow changing report only' do
# I have no clue what other modes are available
mock_app do
use Rack::Protection::ContentSecurityPolicy, :report_uri => '/my_amazing_csp_report_parser', :report_only => true
run DummyApp
end
headers = get('/', {}, 'wants' => 'text/html').headers
headers["Content-Security-Policy"].should be_nil
headers["Content-Security-Policy-Report-Only"].should == "default-src none; script-src self; connect-src self; style-src self; report-uri /my_amazing_csp_report_parser"
end
it 'should not override the header if already set' do
mock_app with_headers("Content-Security-Policy" => "default-src: none")
get('/', {}, 'wants' => 'text/html').headers["Content-Security-Policy"].should == "default-src: none"
end
end