rack-protection-monkey 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/License +20 -0
- data/README.md +90 -0
- data/Rakefile +48 -0
- data/lib/rack-protection.rb +1 -0
- data/lib/rack/protection.rb +40 -0
- data/lib/rack/protection/authenticity_token.rb +31 -0
- data/lib/rack/protection/base.rb +121 -0
- data/lib/rack/protection/escaped_params.rb +87 -0
- data/lib/rack/protection/form_token.rb +23 -0
- data/lib/rack/protection/frame_options.rb +37 -0
- data/lib/rack/protection/http_origin.rb +34 -0
- data/lib/rack/protection/ip_spoofing.rb +23 -0
- data/lib/rack/protection/json_csrf.rb +35 -0
- data/lib/rack/protection/path_traversal.rb +47 -0
- data/lib/rack/protection/remote_referrer.rb +20 -0
- data/lib/rack/protection/remote_token.rb +22 -0
- data/lib/rack/protection/session_hijacking.rb +36 -0
- data/lib/rack/protection/version.rb +16 -0
- data/lib/rack/protection/xss_header.rb +25 -0
- data/rack-protection.gemspec +123 -0
- data/spec/lib/rack/protection/authenticity_token_spec.rb +46 -0
- data/spec/lib/rack/protection/base_spec.rb +38 -0
- data/spec/lib/rack/protection/escaped_params_spec.rb +41 -0
- data/spec/lib/rack/protection/form_token_spec.rb +31 -0
- data/spec/lib/rack/protection/frame_options_spec.rb +37 -0
- data/spec/lib/rack/protection/http_origin_spec.rb +40 -0
- data/spec/lib/rack/protection/ip_spoofing_spec.rb +33 -0
- data/spec/lib/rack/protection/json_csrf_spec.rb +56 -0
- data/spec/lib/rack/protection/path_traversal_spec.rb +39 -0
- data/spec/lib/rack/protection/protection_spec.rb +103 -0
- data/spec/lib/rack/protection/remote_referrer_spec.rb +29 -0
- data/spec/lib/rack/protection/remote_token_spec.rb +40 -0
- data/spec/lib/rack/protection/session_hijacking_spec.rb +53 -0
- data/spec/lib/rack/protection/xss_header_spec.rb +54 -0
- data/spec/spec_helper.rb +86 -0
- data/spec/support/dummy_app.rb +7 -0
- data/spec/support/not_implemented_as_pending.rb +23 -0
- data/spec/support/rack_monkey_patches.rb +21 -0
- data/spec/support/shared_examples.rb +65 -0
- data/spec/support/spec_helpers.rb +36 -0
- metadata +180 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
describe Rack::Protection::AuthenticityToken do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
it "denies post requests without any token" do
|
5
|
+
expect(post('/')).not_to be_ok
|
6
|
+
end
|
7
|
+
|
8
|
+
it "accepts post requests with correct X-CSRF-Token header" do
|
9
|
+
post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "a")
|
10
|
+
expect(last_response).to be_ok
|
11
|
+
end
|
12
|
+
|
13
|
+
it "denies post requests with wrong X-CSRF-Token header" do
|
14
|
+
post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "b")
|
15
|
+
expect(last_response).not_to be_ok
|
16
|
+
end
|
17
|
+
|
18
|
+
it "accepts post form requests with correct authenticity_token field" do
|
19
|
+
post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"})
|
20
|
+
expect(last_response).to be_ok
|
21
|
+
end
|
22
|
+
|
23
|
+
it "denies post form requests with wrong authenticity_token field" do
|
24
|
+
post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"})
|
25
|
+
expect(last_response).not_to be_ok
|
26
|
+
end
|
27
|
+
|
28
|
+
it "prevents ajax requests without a valid token" do
|
29
|
+
expect(post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")).not_to be_ok
|
30
|
+
end
|
31
|
+
|
32
|
+
it "allows for a custom authenticity token param" do
|
33
|
+
mock_app do
|
34
|
+
use Rack::Protection::AuthenticityToken, :authenticity_param => 'csrf_param'
|
35
|
+
run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] }
|
36
|
+
end
|
37
|
+
|
38
|
+
post('/', {"csrf_param" => "a"}, 'rack.session' => {:csrf => "a"})
|
39
|
+
expect(last_response).to be_ok
|
40
|
+
end
|
41
|
+
|
42
|
+
it "sets a new csrf token for the session in env, even after a 'safe' request" do
|
43
|
+
get('/', {}, {})
|
44
|
+
expect(env['rack.session'][:csrf]).not_to be_nil
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
describe Rack::Protection::Base do
|
2
|
+
|
3
|
+
subject { described_class.new(lambda {}) }
|
4
|
+
|
5
|
+
describe "#random_string" do
|
6
|
+
it "outputs a string of 32 characters" do
|
7
|
+
expect(subject.random_string.length).to eq(32)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#referrer" do
|
12
|
+
it "Reads referrer from Referer header" do
|
13
|
+
env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/valid"}
|
14
|
+
expect(subject.referrer(env)).to eq("bar.com")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "Reads referrer from Host header when Referer header is relative" do
|
18
|
+
env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "/valid"}
|
19
|
+
expect(subject.referrer(env)).to eq("foo.com")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "Reads referrer from Host header when Referer header is missing" do
|
23
|
+
env = {"HTTP_HOST" => "foo.com"}
|
24
|
+
expect(subject.referrer(env)).to eq("foo.com")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "Returns nil when Referer header is missing and allow_empty_referrer is false" do
|
28
|
+
env = {"HTTP_HOST" => "foo.com"}
|
29
|
+
subject.options[:allow_empty_referrer] = false
|
30
|
+
expect(subject.referrer(env)).to be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it "Returns nil when Referer header is invalid" do
|
34
|
+
env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/bad|uri"}
|
35
|
+
expect(subject.referrer(env)).to be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
describe Rack::Protection::EscapedParams do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
context 'escaping' do
|
5
|
+
it 'escapes html entities' do
|
6
|
+
mock_app do |env|
|
7
|
+
request = Rack::Request.new(env)
|
8
|
+
[200, {'Content-Type' => 'text/plain'}, [request.params['foo']]]
|
9
|
+
end
|
10
|
+
get '/', :foo => "<bar>"
|
11
|
+
expect(body).to eq('<bar>')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'leaves normal params untouched' do
|
15
|
+
mock_app do |env|
|
16
|
+
request = Rack::Request.new(env)
|
17
|
+
[200, {'Content-Type' => 'text/plain'}, [request.params['foo']]]
|
18
|
+
end
|
19
|
+
get '/', :foo => "bar"
|
20
|
+
expect(body).to eq('bar')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'copes with nested arrays' do
|
24
|
+
mock_app do |env|
|
25
|
+
request = Rack::Request.new(env)
|
26
|
+
[200, {'Content-Type' => 'text/plain'}, [request.params['foo']['bar']]]
|
27
|
+
end
|
28
|
+
get '/', :foo => {:bar => "<bar>"}
|
29
|
+
expect(body).to eq('<bar>')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'leaves cache-breaker params untouched' do
|
33
|
+
mock_app do |env|
|
34
|
+
[200, {'Content-Type' => 'text/plain'}, ['hi']]
|
35
|
+
end
|
36
|
+
|
37
|
+
get '/?95df8d9bf5237ad08df3115ee74dcb10'
|
38
|
+
expect(body).to eq('hi')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
describe Rack::Protection::FormToken do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
it "denies post requests without any token" do
|
5
|
+
expect(post('/')).not_to be_ok
|
6
|
+
end
|
7
|
+
|
8
|
+
it "accepts post requests with correct X-CSRF-Token header" do
|
9
|
+
post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "a")
|
10
|
+
expect(last_response).to be_ok
|
11
|
+
end
|
12
|
+
|
13
|
+
it "denies post requests with wrong X-CSRF-Token header" do
|
14
|
+
post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "b")
|
15
|
+
expect(last_response).not_to be_ok
|
16
|
+
end
|
17
|
+
|
18
|
+
it "accepts post form requests with correct authenticity_token field" do
|
19
|
+
post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"})
|
20
|
+
expect(last_response).to be_ok
|
21
|
+
end
|
22
|
+
|
23
|
+
it "denies post form requests with wrong authenticity_token field" do
|
24
|
+
post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"})
|
25
|
+
expect(last_response).not_to be_ok
|
26
|
+
end
|
27
|
+
|
28
|
+
it "accepts ajax requests without a valid token" do
|
29
|
+
expect(post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")).to be_ok
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
describe Rack::Protection::FrameOptions do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
it 'should set the X-Frame-Options' do
|
5
|
+
expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("SAMEORIGIN")
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should not set the X-Frame-Options for other content types' do
|
9
|
+
expect(get('/', {}, 'wants' => 'text/foo').headers["X-Frame-Options"]).to be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should allow changing the protection mode' do
|
13
|
+
# I have no clue what other modes are available
|
14
|
+
mock_app do
|
15
|
+
use Rack::Protection::FrameOptions, :frame_options => :deny
|
16
|
+
run DummyApp
|
17
|
+
end
|
18
|
+
|
19
|
+
expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("DENY")
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
it 'should allow changing the protection mode to a string' do
|
24
|
+
# I have no clue what other modes are available
|
25
|
+
mock_app do
|
26
|
+
use Rack::Protection::FrameOptions, :frame_options => "ALLOW-FROM foo"
|
27
|
+
run DummyApp
|
28
|
+
end
|
29
|
+
|
30
|
+
expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("ALLOW-FROM foo")
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should not override the header if already set' do
|
34
|
+
mock_app with_headers("X-Frame-Options" => "allow")
|
35
|
+
expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("allow")
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
describe Rack::Protection::HttpOrigin do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
before(:each) do
|
5
|
+
mock_app do
|
6
|
+
use Rack::Protection::HttpOrigin
|
7
|
+
run DummyApp
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
%w(GET HEAD POST PUT DELETE).each do |method|
|
12
|
+
it "accepts #{method} requests with no Origin" do
|
13
|
+
expect(send(method.downcase, '/')).to be_ok
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
%w(GET HEAD).each do |method|
|
18
|
+
it "accepts #{method} requests with non-whitelisted Origin" do
|
19
|
+
expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com')).to be_ok
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
%w(POST PUT DELETE).each do |method|
|
24
|
+
it "denies #{method} requests with non-whitelisted Origin" do
|
25
|
+
expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com')).not_to be_ok
|
26
|
+
end
|
27
|
+
|
28
|
+
it "accepts #{} requests with 'null' Origin" do
|
29
|
+
expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'null')).to be_ok
|
30
|
+
end
|
31
|
+
|
32
|
+
it "accepts #{method} requests with whitelisted Origin" do
|
33
|
+
mock_app do
|
34
|
+
use Rack::Protection::HttpOrigin, :origin_whitelist => ['http://www.friend.com']
|
35
|
+
run DummyApp
|
36
|
+
end
|
37
|
+
expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://www.friend.com')).to be_ok
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
describe Rack::Protection::IPSpoofing do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
it 'accepts requests without X-Forward-For header' do
|
5
|
+
get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_REAL_IP' => '4.3.2.1')
|
6
|
+
expect(last_response).to be_ok
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'accepts requests with proper X-Forward-For header' do
|
10
|
+
get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4',
|
11
|
+
'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1')
|
12
|
+
expect(last_response).to be_ok
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'denies requests where the client spoofs X-Forward-For but not the IP' do
|
16
|
+
get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '1.2.3.5')
|
17
|
+
expect(last_response).not_to be_ok
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'denies requests where the client spoofs the IP but not X-Forward-For' do
|
21
|
+
get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.5',
|
22
|
+
'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1')
|
23
|
+
expect(last_response).not_to be_ok
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'denies requests where IP and X-Forward-For are spoofed but not X-Real-IP' do
|
27
|
+
get('/', {},
|
28
|
+
'HTTP_CLIENT_IP' => '1.2.3.5',
|
29
|
+
'HTTP_X_FORWARDED_FOR' => '1.2.3.5',
|
30
|
+
'HTTP_X_REAL_IP' => '1.2.3.4')
|
31
|
+
expect(last_response).not_to be_ok
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
describe Rack::Protection::JsonCsrf do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
describe 'json response' do
|
5
|
+
before do
|
6
|
+
mock_app { |e| [200, {'Content-Type' => 'application/json'}, []]}
|
7
|
+
end
|
8
|
+
|
9
|
+
it "denies get requests with json responses with a remote referrer" do
|
10
|
+
expect(get('/', {}, 'HTTP_REFERER' => 'http://evil.com')).not_to be_ok
|
11
|
+
end
|
12
|
+
|
13
|
+
it "accepts requests with json responses with a remote referrer when there's an origin header set" do
|
14
|
+
expect(get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_ORIGIN' => 'http://good.com')).to be_ok
|
15
|
+
end
|
16
|
+
|
17
|
+
it "accepts requests with json responses with a remote referrer when there's an x-origin header set" do
|
18
|
+
expect(get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_X_ORIGIN' => 'http://good.com')).to be_ok
|
19
|
+
end
|
20
|
+
|
21
|
+
it "accepts get requests with json responses with a local referrer" do
|
22
|
+
expect(get('/', {}, 'HTTP_REFERER' => '/')).to be_ok
|
23
|
+
end
|
24
|
+
|
25
|
+
it "accepts get requests with json responses with no referrer" do
|
26
|
+
expect(get('/', {})).to be_ok
|
27
|
+
end
|
28
|
+
|
29
|
+
it "accepts XHR requests" do
|
30
|
+
expect(get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest')).to be_ok
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'not json response' do
|
36
|
+
|
37
|
+
it "accepts get requests with 304 headers" do
|
38
|
+
mock_app { |e| [304, {}, []]}
|
39
|
+
expect(get('/', {}).status).to eq(304)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'with drop_session as default reaction' do
|
45
|
+
it 'still denies' do
|
46
|
+
mock_app do
|
47
|
+
use Rack::Protection, :reaction => :drop_session
|
48
|
+
run proc { |e| [200, {'Content-Type' => 'application/json'}, []]}
|
49
|
+
end
|
50
|
+
|
51
|
+
session = {:foo => :bar}
|
52
|
+
get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'rack.session' => session)
|
53
|
+
expect(last_response).not_to be_ok
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
describe Rack::Protection::PathTraversal do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
context 'escaping' do
|
5
|
+
before do
|
6
|
+
mock_app { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO']]] }
|
7
|
+
end
|
8
|
+
|
9
|
+
%w[/foo/bar /foo/bar/ / /.f /a.x].each do |path|
|
10
|
+
it("does not touch #{path.inspect}") { expect(get(path).body).to eq(path) }
|
11
|
+
end
|
12
|
+
|
13
|
+
{ # yes, this is ugly, feel free to change that
|
14
|
+
'/..' => '/', '/a/../b' => '/b', '/a/../b/' => '/b/', '/a/.' => '/a/',
|
15
|
+
'/%2e.' => '/', '/a/%2E%2e/b' => '/b', '/a%2f%2E%2e%2Fb/' => '/b/',
|
16
|
+
'//' => '/', '/%2fetc%2Fpasswd' => '/etc/passwd'
|
17
|
+
}.each do |a, b|
|
18
|
+
it("replaces #{a.inspect} with #{b.inspect}") { expect(get(a).body).to eq(b) }
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should be able to deal with PATH_INFO = nil (fcgi?)' do
|
22
|
+
app = Rack::Protection::PathTraversal.new(proc { 42 })
|
23
|
+
expect(app.call({})).to eq(42)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if "".respond_to?(:encoding) # Ruby 1.9+ M17N
|
28
|
+
context "PATH_INFO's encoding" do
|
29
|
+
before do
|
30
|
+
@app = Rack::Protection::PathTraversal.new(proc { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO'].encoding.to_s]] })
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should remain unchanged as ASCII-8BIT' do
|
34
|
+
body = @app.call({ 'PATH_INFO' => '/'.encode('ASCII-8BIT') })[2][0]
|
35
|
+
expect(body).to eq('ASCII-8BIT')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
describe Rack::Protection do
|
2
|
+
it_behaves_like "any rack application"
|
3
|
+
|
4
|
+
it 'passes on options' do
|
5
|
+
mock_app do
|
6
|
+
use Rack::Protection, :track => ['HTTP_FOO']
|
7
|
+
run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] }
|
8
|
+
end
|
9
|
+
|
10
|
+
session = {:foo => :bar}
|
11
|
+
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a'
|
12
|
+
get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b'
|
13
|
+
expect(session[:foo]).to eq(:bar)
|
14
|
+
|
15
|
+
get '/', {}, 'rack.session' => session, 'HTTP_FOO' => 'BAR'
|
16
|
+
expect(session).to be_empty
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'passes errors through if :reaction => :report is used' do
|
20
|
+
mock_app do
|
21
|
+
use Rack::Protection, :reaction => :report
|
22
|
+
run proc { |e| [200, {'Content-Type' => 'text/plain'}, [e["protection.failed"].to_s]] }
|
23
|
+
end
|
24
|
+
|
25
|
+
session = {:foo => :bar}
|
26
|
+
post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com')
|
27
|
+
expect(last_response).to be_ok
|
28
|
+
expect(body).to eq("true")
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#react" do
|
32
|
+
it 'prevents attacks and warns about it' do
|
33
|
+
io = StringIO.new
|
34
|
+
mock_app do
|
35
|
+
use Rack::Protection, :logger => Logger.new(io)
|
36
|
+
run DummyApp
|
37
|
+
end
|
38
|
+
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
|
39
|
+
expect(io.string).to match(/prevented.*Origin/)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'reports attacks if reaction is to report' do
|
43
|
+
io = StringIO.new
|
44
|
+
mock_app do
|
45
|
+
use Rack::Protection, :reaction => :report, :logger => Logger.new(io)
|
46
|
+
run DummyApp
|
47
|
+
end
|
48
|
+
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
|
49
|
+
expect(io.string).to match(/reported.*Origin/)
|
50
|
+
expect(io.string).not_to match(/prevented.*Origin/)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'passes errors to reaction method if specified' do
|
54
|
+
io = StringIO.new
|
55
|
+
Rack::Protection::Base.send(:define_method, :special) { |*args| io << args.inspect }
|
56
|
+
mock_app do
|
57
|
+
use Rack::Protection, :reaction => :special, :logger => Logger.new(io)
|
58
|
+
run DummyApp
|
59
|
+
end
|
60
|
+
post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
|
61
|
+
expect(io.string).to match(/HTTP_ORIGIN.*malicious.com/)
|
62
|
+
expect(io.string).not_to match(/reported|prevented/)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#html?" do
|
67
|
+
context "given an appropriate content-type header" do
|
68
|
+
subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/html" }
|
69
|
+
it { is_expected.to be_truthy }
|
70
|
+
end
|
71
|
+
|
72
|
+
context "given an inappropriate content-type header" do
|
73
|
+
subject { Rack::Protection::Base.new(nil).html? 'content-type' => "image/gif" }
|
74
|
+
it { is_expected.to be_falsey }
|
75
|
+
end
|
76
|
+
|
77
|
+
context "given no content-type header" do
|
78
|
+
subject { Rack::Protection::Base.new(nil).html?({}) }
|
79
|
+
it { is_expected.to be_falsey }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#instrument" do
|
84
|
+
let(:env) { { 'rack.protection.attack' => 'base' } }
|
85
|
+
let(:instrumenter) { double('Instrumenter') }
|
86
|
+
|
87
|
+
after do
|
88
|
+
app.instrument(env)
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with an instrumenter specified' do
|
92
|
+
let(:app) { Rack::Protection::Base.new(nil, :instrumenter => instrumenter) }
|
93
|
+
|
94
|
+
it { expect(instrumenter).to receive(:instrument).with('rack.protection', env) }
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'with no instrumenter specified' do
|
98
|
+
let(:app) { Rack::Protection::Base.new(nil) }
|
99
|
+
|
100
|
+
it { expect(instrumenter).not_to receive(:instrument) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|