rack-shield 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 91381e561f753758f33de1f296eaf6df921d131d8780ec5d3ca6d21a1cabc022
4
+ data.tar.gz: 737b223d0e6990ad9447f8f82b725d4a0e7bdfed9dc53d59085d78363c9d2e8b
5
+ SHA512:
6
+ metadata.gz: 31d9e273c7837fa8e6626247b1cc361e5f4138cf3061cc0c8fc605b717e962e48272d17b00ab3afd36374710422e4bb340322562835522c66ee7103c938b9870
7
+ data.tar.gz: cfe65f88d463b9874d3e7b01045171c909fa74bede02f98b79bf5f57a32b3b648e43ee82d7c897b817ee11b95ffa6742c6e55a2663acc31809cf6f3d147449d0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Matthias Grosser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ ![Shield](https://raw.githubusercontent.com/mtgrosser/rack-shield/master/doc/shield.svg)
2
+
3
+ # Rack::Shield
4
+
5
+ Simple frontend to block and unblock evil requests with `Rack::Attack`
6
+
7
+ ## Installation
8
+
9
+ In your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'rack-attack-shield'
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Check whether request is evil:
18
+
19
+ ```ruby
20
+ Rack::Shield.evil?(request)
21
+ ```
22
+
23
+ With `Rack::Attack::Fail2Ban`:
24
+
25
+ ```ruby
26
+ # After one blocked request in 10 minutes, block all requests from that IP for 5 minutes.
27
+ Rack::Attack.blocklist('fail2ban pentesters') do |req|
28
+ Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", maxretry: 1, findtime: 10.minutes, bantime: 5.minutes) do
29
+ Rack::Shield.evil?(req)
30
+ end
31
+ end
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ Adding to path matchers:
37
+
38
+ ```ruby
39
+ # Regexp will be matched
40
+ Rack::Shield.evil_paths << /\.sql\z/
41
+
42
+ # String will be checked for inclusion
43
+ Rack::Shield.evil_paths << '/wp-admin'
44
+ ```
45
+ Defaults are defined in `Rack::Shield::DEFAULT_EVIL_PATHS`.
46
+
47
+ ## Blocked response
48
+
49
+ By default, the blocked response is generated automatically:
50
+
51
+ ```ruby
52
+ # default
53
+ Rack::Shield.response = Rack::Shield::Response
54
+ ```
55
+
56
+ It can be set to any `call`able object which conforms to the `Rack` interface:
57
+
58
+ ```ruby
59
+ Rack::Shield.response = ->(env) { [403, { 'Content-Type' => 'text/html' }, ["Blocked!\n"]]
60
+ ```
61
+
62
+ In Rails apps, the blocked response will be generated from `app/views/layouts/shield.html`.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ t.warning = false
9
+ end
10
+
11
+ task :default => :test
12
+
13
+ namespace :rack do
14
+ namespace :shield do
15
+ task :install do
16
+ require 'rack/shield'
17
+ file = defined?(Rails) ? Rails.root.join('app', 'views', 'layouts', 'shield.html') : 'shield.html'
18
+ if file.exist?
19
+ puts "skipping #{file}"
20
+ else
21
+ file.write(Rack::Shield::Response.default_template.read)
22
+ puts "create #{file}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Rack
2
+ module Shield
3
+ module RequestExt
4
+ def raw_post_data
5
+ env['RAW_POST_DATA']
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ Rack::Attack::Request.include Rack::Shield::RequestExt
@@ -0,0 +1,51 @@
1
+ module Rack
2
+ module Shield
3
+ class Responder
4
+ attr_reader :request
5
+
6
+ class << self
7
+ attr_writer :template
8
+
9
+ def template
10
+ @template ||= if defined?(Rails)
11
+ Rails.root.join('app', 'views', 'layouts', 'shield.html')
12
+ else
13
+ default_template
14
+ end
15
+ end
16
+
17
+ def default_template
18
+ Pathname.new(__FILE__).dirname.join('..', '..', '..', '..', 'templates', 'shield.html')
19
+ end
20
+
21
+ def call(request)
22
+ new(request).render
23
+ end
24
+ end
25
+
26
+ def initialize(request)
27
+ @request = request
28
+ end
29
+
30
+ def env
31
+ request.env
32
+ end
33
+
34
+ def render
35
+ return [403, { 'Content-Type' => 'text/html' }, []] if head?
36
+ html = self.class.template.read.gsub('%REQUEST_IP%', request_ip.to_s)
37
+ [403, { 'Content-Type' => 'text/html' }, ["#{html}\n"]]
38
+ end
39
+
40
+ private
41
+
42
+ def request_ip
43
+ env['HTTP_X_REAL_IP'] || env['REMOTE_ADDR']
44
+ end
45
+
46
+ def head?
47
+ request.head?
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module Shield
3
+ VERSION = '1.1.0'
4
+ end
5
+ end
@@ -0,0 +1,106 @@
1
+ require 'pathname'
2
+ require 'rack/attack'
3
+ require_relative 'shield/version'
4
+ require_relative 'shield/responder'
5
+ require_relative 'shield/request_ext'
6
+
7
+ module Rack
8
+ module Shield
9
+ DEFAULT_PATHS = [/\/wp-(includes|content|admin|json|config)/,
10
+ /\.(php|cgi|asp|aspx|shtml|log|(my)?sql(\.tar)?(\.t?(gz|zip))?|cfm|py|lasso|e?rb|pl|jsp|do|action|sh)\z/i,
11
+ 'cgi-bin',
12
+ 'phpmyadmin',
13
+ '/pma/',
14
+ '/boaform/',
15
+ 'sqlbuddy',
16
+ /(my)?sql-backup/,
17
+ 'etc/passwd',
18
+ '/php/',
19
+ '.php/',
20
+ '/browsedisk',
21
+ '/mambo/',
22
+ '/jenkins/',
23
+ '/joomla/',
24
+ '/varien/js.js',
25
+ '/drupal.js',
26
+ 'RELEASE_NOTES.txt',
27
+ '/phpunit/',
28
+ '/magento/',
29
+ '/mage/',
30
+ '/magento_version',
31
+ '/mifs/',
32
+ '/js/varien/',
33
+ '/includes/',
34
+ '/HNAP1',
35
+ '/stalker_portal/',
36
+ '/nmaplowercheck',
37
+ '/solr/admin/',
38
+ '/axis2/axis2-admin',
39
+ '/telescope/requests',
40
+ '/RELEASE_NOTES.txt',
41
+ 'deployment-config.json',
42
+ 'ftpsync.settings',
43
+ '/_profiler/latest',
44
+ '/_ignition/execute-solution',
45
+ '/_wpeprivate/',
46
+ '/Config/SaveUploadedHotspotLogoFile',
47
+ 'ALFA_DATA',
48
+ 'cgialfa',
49
+ 'alfacgiapi',
50
+ /\A\/"/,
51
+ /\/\.(hg|git|svn|bzr|htaccess|ftpconfig|vscode|remote-sync|aws|env|DS_Store)/,
52
+ /\/old\/?\z/,
53
+ /\/\.env\z/,
54
+ /\A\/old-wp/,
55
+ /\A\/(wordpress|wp)(\/|\z)/]
56
+
57
+ DEFAULT_QUERIES = [/SELECT.+FROM.+/i,
58
+ /SELECT.+COUNT/i,
59
+ /SELECT.+UNION/i,
60
+ /UNION.+SELECT/i,
61
+ /INFORMATION_SCHEMA/i,
62
+ '--%20',
63
+ '-- ',
64
+ '%2Fscript%3E',
65
+ '<script>', '</script>',
66
+ '<php>', '</php>',
67
+ 'XDEBUG_SESSION_START',
68
+ 'phpstorm',
69
+ '<php>',
70
+ 'onload=confirm',
71
+ 'HelloThinkCMF',
72
+ 'XDEBUG_SESSION_START',
73
+ ]
74
+
75
+ class << self
76
+
77
+ attr_accessor :paths, :queries, :checks, :responder
78
+
79
+ def evil?(req)
80
+ (req.path && paths.any? { |matcher| match?(req.path, matcher) }) ||
81
+ (req.query_string && queries.any? { |matcher| match?(req.query_string, matcher) }) ||
82
+ (checks.any? { |matcher| match?(req, matcher) })
83
+ end
84
+
85
+ def template
86
+ Pathname.new(__FILE__).dirname.join('..', '..', '..', 'templates', 'shield.html')
87
+ end
88
+
89
+ private
90
+
91
+ def match?(obj, matcher)
92
+ case matcher
93
+ when String then obj.include?(matcher)
94
+ when Regexp then obj.match?(matcher)
95
+ when Proc then matcher.call(obj)
96
+ end
97
+ end
98
+ end
99
+
100
+ self.paths = DEFAULT_PATHS.dup
101
+ self.queries = DEFAULT_QUERIES.dup
102
+ self.checks = []
103
+ self.responder = Responder
104
+
105
+ end
106
+ end
@@ -0,0 +1 @@
1
+ require 'rack/shield'
@@ -0,0 +1,146 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html>
4
+
5
+ <head>
6
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
7
+ <title>Access blocked</title>
8
+ <link rel="icon" href="data:,">
9
+ <style type="text/css">
10
+ @import 'https://fonts.googleapis.com/css?family=Roboto:300,500';
11
+
12
+ body {
13
+ background-color: white;
14
+ color: #666;
15
+ text-align: center;
16
+ font-family: 'Roboto', sans-serif;
17
+ font-weight: 300;
18
+ font-size: 18px;
19
+ line-height: 1.5em;
20
+ }
21
+
22
+ div#centerbox {
23
+ display: block;
24
+ position: absolute;
25
+ width: 100%;
26
+ height: 1px;
27
+ top: 50%;
28
+ left: 0px;
29
+ overflow: visible;
30
+ visibility: visible;
31
+ }
32
+
33
+ div#centerbox #dialog {
34
+ position: absolute;
35
+ width: 600px;
36
+ height: 500px;
37
+ top: -250px;
38
+ left: 50%;
39
+ margin-left: -300px;
40
+ overflow:visible;
41
+ }
42
+
43
+ div.dialog {
44
+ width: 25em;
45
+ padding: 0 4em;
46
+ margin: auto;
47
+ //border: 1px solid #ccc;
48
+ border-right-color: #999;
49
+ border-bottom-color: #999;
50
+ }
51
+
52
+ h1 {
53
+ font-size: 40px;
54
+ font-weight: 100;
55
+ line-height: 1.5em;
56
+ }
57
+
58
+ a {
59
+ color: #428aff;
60
+ text-decoration: none;
61
+ }
62
+
63
+ #checkbox-unchecked, #checkbox-checked {
64
+ width: 40px;
65
+ height: 40px;
66
+ vertical-align: middle;
67
+ }
68
+
69
+ #checkbox-checked {
70
+ display: none;
71
+ }
72
+
73
+ .norobot {
74
+ line-height: 45px;
75
+ }
76
+ </style>
77
+ </head>
78
+
79
+ <body class="blocked-page">
80
+ <div id="centerbox">
81
+ <div id="dialog">
82
+ <img id="logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzJkNjhjNCIgc3Ryb2tlLXdpZHRoPSIyLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9ImZlYXRoZXIgZmVhdGhlci1zaGllbGQiPjxwYXRoIGQ9Ik0xMiAyMnM4LTQgOC0xMFY1bC04LTMtOCAzdjdjMCA2IDggMTAgOCAxMHoiPjwvcGF0aD48L3N2Zz4=" alt="Shield"/>
83
+ <br/><br/>
84
+ <div lang="de">
85
+ <h1>Diese Webseite ist geschützt.</h1>
86
+ <p>
87
+ Wir haben eine ungewöhnliche Aktivität von Ihrer IP-Adresse <strong>%REQUEST_IP%</strong> festgestellt und den Zugriff auf diese Webseite blockiert.<br/>
88
+ <br/>
89
+ <strong>Bitte bestätigen Sie, dass Sie kein Roboter sind:</strong>
90
+ </p>
91
+ </div>
92
+ <div lang="en" style="display: none;">
93
+ <h1>This website is protected.</h1>
94
+ <p>
95
+ We have noticed an unusual activity from your IP <strong>%REQUEST_IP%</strong> and blocked access to this website.<br/>
96
+ <br/>
97
+ <strong>Please confirm that you are not a robot:</strong>
98
+ </p>
99
+ </div>
100
+ <div>
101
+ <p>
102
+ <img id="checkbox-unchecked" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM2NjY2NjYiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLXNxdWFyZSI+PHJlY3QgeD0iMyIgeT0iMyIgd2lkdGg9IjE4IiBoZWlnaHQ9IjE4IiByeD0iMiIgcnk9IjIiPjwvcmVjdD48L3N2Zz4=">
103
+ <img id="checkbox-checked" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM2NjY2NjYiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWNoZWNrLXNxdWFyZSI+PHBvbHlsaW5lIHBvaW50cz0iOSAxMSAxMiAxNCAyMiA0Ij48L3BvbHlsaW5lPjxwYXRoIGQ9Ik0yMSAxMnY3YTIgMiAwIDAgMS0yIDJINWEyIDIgMCAwIDEtMi0yVjVhMiAyIDAgMCAxIDItMmgxMSI+PC9wYXRoPjwvc3ZnPg==">
104
+ &nbsp;<span class="norobot" lang="de">Ich bin kein Roboter.</span><span class="norobot" lang="en" style="display: none;">I'm not a robot.</span>
105
+ <input type="hidden" name="foobar">
106
+ </p>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ <script>
111
+ var token = '%TOKEN%';
112
+
113
+ var hide = function(elt) {
114
+ elt.style.display = 'none';
115
+ };
116
+
117
+ var unhide = function(elt) {
118
+ switch (elt.tagName.toLowerCase()) {
119
+ case 'span':
120
+ case 'img':
121
+ elt.style.display = 'inline';
122
+ break;
123
+ default:
124
+ elt.style.display = 'block';
125
+ }
126
+ };
127
+
128
+ var language = window.navigator.userLanguage || window.navigator.language;
129
+ if ( language && language.toLowerCase().startsWith('en') ) {
130
+ document.querySelectorAll('[lang=de]').forEach((elt) => { elt.style.display = 'none' });
131
+ document.querySelectorAll('[lang=en]').forEach(unhide);
132
+ }
133
+
134
+ var unchecked = document.getElementById('checkbox-unchecked');
135
+ var checked = document.getElementById('checkbox-checked');
136
+ unchecked.addEventListener('click', function() {
137
+ hide(unchecked);
138
+ unhide(checked);
139
+ });
140
+ checked.addEventListener('click', function() {
141
+ hide(checked);
142
+ unhide(unchecked);
143
+ });
144
+ </script>
145
+ </body>
146
+ </html>
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-shield
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack-attack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.6.0
27
+ description: Plugin for rack-attack to block and unblock evil requests
28
+ email:
29
+ - mtgrosser@gmx.net
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/rack-shield.rb
38
+ - lib/rack/shield.rb
39
+ - lib/rack/shield/request_ext.rb
40
+ - lib/rack/shield/responder.rb
41
+ - lib/rack/shield/version.rb
42
+ - templates/shield.html
43
+ homepage: https://github.com/mtgrosser/rack-shield
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.1.4
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Block and unblock evil requests
66
+ test_files: []