mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
Merge pull request #75 from mkristian/content-security-policy
added content security policies
This commit is contained in:
commit
c419868ca3
4 changed files with 136 additions and 24 deletions
|
@ -53,6 +53,7 @@ Prevented by:
|
|||
|
||||
* `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`)
|
||||
* `Rack::Protection::XSSHeader` (Internet Explorer only)
|
||||
* `Rack::Protection::ContentSecurityPolicy`
|
||||
|
||||
## Clickjacking
|
||||
|
||||
|
|
|
@ -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? :content_security_policy
|
||||
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
|
||||
|
|
|
@ -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. Google’s
|
||||
# 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-src’s 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.compact.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
|
44
rack-protection/spec/content_security_policy_spec.rb
Normal file
44
rack-protection/spec/content_security_policy_spec.rb
Normal 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
|
Loading…
Reference in a new issue