active_hashcash 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a52910cc5af45ca4b31ce6af112ce45a4ec401e584d80076aa50247f9dfc154
4
- data.tar.gz: 03ca632ff343a29d9841cb950684c693b4d7684b1bd42251731fea8f8f1c36c8
3
+ metadata.gz: 29b1886eff044771c56c4eeff7dfcc4a305262fd39b9633a43522e1a8a465f7a
4
+ data.tar.gz: 0ad53ba4ebdcec34064f33cc1ea2f75dc95ce6ffb57a733604174e0059c37521
5
5
  SHA512:
6
- metadata.gz: b2758deaf12537653c08a27a988aa18e13449975caaf73e854a39ad5011eca63430ac1eb019d9dc5bb66d415a0d8975a0908c833056d0422fdf8a1b192d3316e
7
- data.tar.gz: 579afe8ab7e448ffd550882fad8b4c8e724be3ec5ab043aac57c49463097a738cf66fd9700193d4f418a01f6ffb03d29510d5593b4800c659548abce968f81fc
6
+ metadata.gz: 794687bc50403b03a63e4855451bec53751b6da4a20ef1674bdbf0d66dfdfacfaa68f3d1416a14a0a1bdf823b4641b8a16e6b70aba288278883170c7a73b6202
7
+ data.tar.gz: 7b0ca6f84cb75b2e49b5747034393c778023a2efb6974b97ba9fab084074da57cb78f5014de5d26e20af378bb8a86788f833f02e036e5feb56e8aee52c3b87d9
data/CHANGELOG.md CHANGED
@@ -1,4 +1,15 @@
1
- ## [Unreleased]
1
+ # Changelog of ActiveHashcash
2
+
3
+ ## 0.3.0 - 2024-03-14
4
+
5
+ - Increase complexity automatically to slowdown brute force attacks
6
+ - Add mountable dashboard to list latest stamps and most frequent IP addresses
7
+ - Store stamps into the database instead of Redis
8
+ - Fix ActiveHashcash::Store#add? by converting stamp to a string
9
+
10
+ ## 0.2.0 - 2022-08-02
11
+
12
+ - Add ActiveHashcash::Store#clean to removed expired stamps
2
13
 
3
14
  ## [0.1.1] - 2022-07-08
4
15
 
data/README.md CHANGED
@@ -2,29 +2,34 @@
2
2
 
3
3
  <img align="right" width="200px" src="logo.png" alt="Active Hashcash logo"/>
4
4
 
5
- ActiveHashcash protects your Rails application against brute force attacks, DoS and bots.
5
+ ActiveHashcash protects Rails applications against bots and brute force attacks without annoying humans.
6
6
 
7
7
  Hashcash is proof-of-work algorithm, invented by Adam Back in 1997, to protect systems against denial of service attacks.
8
- ActiveHashcash is an easy way to protect any Rails application against brute force attacks and some bots.
8
+ ActiveHashcash is an easy way to protect any Rails application against brute force attacks and bots.
9
9
 
10
10
  The idea is to force clients to spend some time to solve a hard problem that is very easy to verify for the server.
11
11
  We have developped ActiveHashcash after seeing brute force attacks against our Rails application monitoring service [RorVsWild](https://rorvswild.com).
12
12
 
13
- The idea is to enable ActiveHashcash on sensitive forms such as login and registration. While the user is filling the form,
14
- ActiveHashcash performs the work in JavaScript and set the result into a hidden input text. The form cannot be submitted while the proof of work has not been found.
15
- The user submits the form, and the stamp is verified by the controller in a before action.
13
+ ActiveHashcash is ideal to set up on sensitive forms such as login and registration.
14
+ While the user is filling the form, the problem is solved in JavaScript and set the result into a hidden input text.
15
+ The form cannot be submitted while the proof of work has not been found.
16
+ Then the user submits the form, and the stamp is verified by the controller in a before action.
16
17
 
17
- It blocks bots that do not interpret JavaScript since the proof of work is not computed. For the more sophisticated bots, we are happy to slow them down.
18
+ It blocks bots that do not interpret JavaScript since the proof of work is not computed.
19
+ More sophisticated bots and brute force attacks are slow down.
20
+ Moreover the complexity increases automatically for IP addresses sending many requests.
21
+ Thus it becomes very CPU costly for attackers.
18
22
 
19
- Here is a [demo on a registration form](https://www.rorvswild.com/account/new) :
23
+ Finally legitimate users are not annoyed by asking to solve a puzzle or clicking on the all images containing a bus.
24
+ Here is a [demo on a registration form](https://www.rorvswild.com/session) :
20
25
 
21
26
  ![Active Hashcash GIF preview](demo.gif)
22
27
 
23
- ## Limitations
28
+ ---
24
29
 
25
- The JavaScript implementation is 10 to 20 times slower than the official C version.
26
- It needs some work and knowledges to be optimised. Unfortunately, I'm not a JavaScript expert.
27
- Maybe you have good JS skills to optimize it ?
30
+ <img align="left" height="24px" src="rorvswild_logo.jpg" alt="RorVsWild logo"/>Made by <a href="https://www.rorvswild.com">RorVsWild</a>, performances & exceptions monitoring for Ruby on Rails applications.
31
+
32
+ ---
28
33
 
29
34
  ## Installation
30
35
 
@@ -62,24 +67,108 @@ end
62
67
  To customize some behaviour, you can override most of the methods which begins with `hashcash_`.
63
68
  Simply have a look to `active_hashcash.rb`.
64
69
 
70
+ Stamps are stored into into the database to prevents from spending them more than once.
71
+ You must run a migration:
72
+
73
+ ```
74
+ rails active_hashcash:install:migrations
75
+ rails db:migrate
76
+ ```
77
+
78
+ ### Dashboard
79
+
80
+ There is a mountable dahsboard which allows to see all spent stamps.
81
+ It's not mandatory, but useful for monitoring purpose.
82
+
83
+ ![ActiveHashcash dashboard](active_hashcash_dashboard.png "ActiveHashcash dashboard")
84
+
85
+ ```ruby
86
+ # config/routes.rb
87
+ mount ActiveHashcash::Engine, at: "hashcash"
88
+ ```
89
+
90
+ ActiveHashcash cannot guess how you handle user authentication, because it is different for all Rails applications.
91
+ So you have to monkey patch `ActiveHashcash::ApplicationController` in order to inject your own mechanism.
92
+ The patch can be saved wherever you want.
93
+ For example, I like to have all the patches in one place, so I put them in `lib/patches`.
94
+
95
+ ```ruby
96
+ # lib/patches/active_hashcash.rb
97
+
98
+ ActiveHashcash::ApplicationController.class_eval do
99
+ before_action :require_admin
100
+
101
+ def require_admin
102
+ # This example supposes there are current_user and User#admin? methods
103
+ raise ActionController::RoutingError.new("Not found") unless current_user.try(:admin?)
104
+ end
105
+ end
106
+ end
107
+ ```
108
+
109
+ Then you have to require the monkey patch.
110
+ Because it's loaded via require, it won't be reloaded in development.
111
+ Since you are not supposed to change this file often, it should not be an issue.
112
+
113
+ ```ruby
114
+ # config/application.rb
115
+ config.after_initialize do
116
+ require "patches/active_hashcash"
117
+ end
118
+ ```
119
+
120
+ If you use Devise, you can check the permission directly from routes.rb:
121
+
122
+ ```ruby
123
+ # config/routes.rb
124
+ authenticate :user, -> (u) { u.admin? } do # Supposing there is a User#admin? method
125
+ mount ActiveHashcash::Engine, at: "hashcash" # http://localhost:3000/hashcash
126
+ end
127
+ ```
128
+
129
+ ### Before version 0.3.0
130
+
65
131
  You must have Redis in order to prevent double spent stamps. Otherwise it will be useless.
66
132
  It automatically tries to connect with the environement variables `ACTIVE_HASHCASH_REDIS_URL` or `REDIS_URL`.
67
133
  You can also manually set the URL with `ActiveHashcash.redis_url = redis://user:password@localhost:6379`.
68
134
 
135
+ You should call `ActiveHashcash::Store#clean` once a day, to remove expired stamps.
136
+
137
+ To upgrade from 0.2.0 you must run the migration :
138
+
139
+ ```
140
+ rails active_hashcash:install:migrations
141
+ rails db:migrate
142
+ ```
143
+
69
144
  ## Complexity
70
145
 
71
146
  Complexity is the most important parameter. By default its value is 20 and requires most of the time 5 to 20 seconds to be solved on a decent laptop.
72
147
  The user won't wait that long, since he needs to fill the form while the problem is solving.
73
148
  Howevever, if your application includes people with slow and old devices, then consider lowering this value, to 16 or 18.
74
149
 
75
- You can change the complexity, either with `ActiveHashcash.bits = 20` or by overriding the method `hashcash_bits` in you controller.
150
+ You can change the minimum complexity with `ActiveHashcash.bits = 20`.
151
+
152
+ Since version 0.3.0, the complexity increases with the number of stamps spent during le last 24H from the same IP address.
153
+ Thus it becomes very efficient to slow down brute force attacks.
154
+
155
+ ## Limitations
156
+
157
+ The JavaScript implementation is 10 to 20 times slower than the official C version.
158
+ I first used the SubtleCrypto API but it is surprisingly slower than a custom SHA1 implementation.
159
+ Maybe I did in an unefficient way 2df3ba5?
160
+ Another idea would be to compile the work algorithm in wasm.
161
+
162
+ Unfortunately, I'm not a JavaScript expert.
163
+ Maybe you have good JS skills to optimize it?
164
+ Any help would be appreciate to better fights bots and brute for attacks!
76
165
 
77
166
  ## Contributing
78
167
 
79
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_hashcash.
168
+ Bug reports and pull requests are welcome on GitHub at https://github.com/BaseSecrete/active_hashcash.
80
169
 
81
170
  ## License
82
171
 
83
172
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
84
173
 
85
- Made by Alexis Bernard at [Base Secrète](https://basesecrete.com).
174
+ Made by Alexis Bernard at [RorVsWild](https://www.rorvswild.com).
@@ -0,0 +1,7 @@
1
+ module ActiveHashcash
2
+ class Engine < ::Rails::Engine
3
+ config.assets.paths << File.expand_path("../..", __FILE__) if config.respond_to?(:assets)
4
+
5
+ isolate_namespace ActiveHashcash
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveHashcash
2
+ VERSION = "0.3.0"
3
+ end
@@ -1,3 +1,6 @@
1
+ require "active_hashcash/version"
2
+ require "active_hashcash/engine"
3
+
1
4
  module ActiveHashcash
2
5
  extend ActiveSupport::Concern
3
6
 
@@ -7,23 +10,41 @@ module ActiveHashcash
7
10
  helper_method :hashcash_hidden_field_tag
8
11
  end
9
12
 
10
- mattr_accessor :bits, instance_accessor: false, default: 20
11
13
  mattr_accessor :resource, instance_accessor: false
12
- mattr_accessor :redis_url, instance_accessor: false
13
14
 
14
- # TODO: protect_from_brute_force bits: 20, exception: ActionController::InvalidAuthenticityToken, with: :handle_failed_hashcash
15
+ # This is base complexity.
16
+ # Consider lowering it to not exclude people with old and slow devices.
17
+ mattr_accessor :bits, instance_accessor: false, default: 16
18
+
19
+ mattr_accessor :date_format, instance_accessor: false, default: "%y%m%d"
15
20
 
16
- # Call me via a before_action when the form is submitted : `before_action :chech_hashcash, only: :create`
21
+ # Call me via a before_action when the form is submitted : `before_action :check_hashcash, only: :create`
17
22
  def check_hashcash
18
- if hashcash_stamp_is_valid? && !hashcash_stamp_spent?
19
- hashcash_redis.sadd("active_hashcash_stamps".freeze, hashcash_param)
23
+ attrs = {
24
+ ip_address: hashcash_ip_address,
25
+ request_path: hashcash_request_path,
26
+ context: hashcash_stamp_context
27
+ }
28
+ if hashcash_param && Stamp.spend(hashcash_param, hashcash_resource, hashcash_bits, Date.yesterday, attrs)
20
29
  hashcash_after_success
21
30
  else
22
31
  hashcash_after_failure
23
32
  end
24
33
  end
25
34
 
26
- # Override the methods below in your controller, to change any parameter of behaviour.
35
+ # Override the methods below in your controller, to change any parameter or behaviour.
36
+
37
+ def hashcash_ip_address
38
+ request.remote_ip
39
+ end
40
+
41
+ def hashcash_request_path
42
+ request.path
43
+ end
44
+
45
+ def hashcash_stamp_context
46
+ # Override this method to store custom data for each stamp
47
+ end
27
48
 
28
49
  # By default the host name is used as the resource.
29
50
  # It' should be good for most cases and prevent from reusing the same stamp between sites.
@@ -31,10 +52,15 @@ module ActiveHashcash
31
52
  ActiveHashcash.resource || request.host
32
53
  end
33
54
 
34
- # Define the complexity, the higher the slower it is. Consider lowering this value to not exclude people with old and slow devices.
35
- # On a decent laptop, it takes around 30 seconds for the JavaScript implementation to solve a 20 bits complexity and few seconds when it's 16.
55
+ # Returns the complexity, the higher the slower it is.
56
+ # Complexity is increased logarithmicly for each IP during the last 24H to slowdown brute force attacks.
57
+ # The minimun value returned is `ActiveHashcash.bits`.
36
58
  def hashcash_bits
37
- ActiveHashcash.bits
59
+ if (previous_stamp_count = ActiveHashcash::Stamp.where(ip_address: hashcash_ip_address).where(created_at: 1.day.ago..).count) > 0
60
+ (ActiveHashcash.bits + Math.log2(previous_stamp_count)).floor
61
+ else
62
+ ActiveHashcash.bits
63
+ end
38
64
  end
39
65
 
40
66
  # Override if you want to rename the hashcash param.
@@ -63,88 +89,4 @@ module ActiveHashcash
63
89
  options = {resource: hashcash_resource, bits: hashcash_bits, waiting_message: hashcash_waiting_message}
64
90
  hidden_field_tag(name, "", "data-hashcash" => options.to_json)
65
91
  end
66
-
67
- def hashcash_redis
68
- @hashcash_redis = Redis.new(url: hashcash_redis_url)
69
- end
70
-
71
- def hashcash_redis_url
72
- ActiveHashcash.redis_url || ENV["ACTIVE_HASHCASH_REDIS_URL"] || ENV["REDIS_URL"]
73
- end
74
-
75
- def hashcash_stamp_is_valid?
76
- stamp = hashcash_param && Stamp.parse(hashcash_param)
77
- stamp && stamp.valid? && stamp.bits >= hashcash_bits && stamp.parse_date >= Date.yesterday
78
- end
79
-
80
- def hashcash_stamp_spent?
81
- hashcash_redis.sismember("active_hashcash_stamps".freeze, hashcash_param)
82
- end
83
-
84
-
85
-
86
- class Engine < ::Rails::Engine
87
- config.assets.paths << File.expand_path("..", __FILE__)
88
-
89
- config.after_initialize { load_translations }
90
-
91
- def load_translations
92
- if !I18n.backend.exists?(I18n.locale, "active_hashcash")
93
- I18n.backend.store_translations(:de, {active_hashcash: {waiting_label: "Warten auf die Überprüfung ..."}})
94
- I18n.backend.store_translations(:en, {active_hashcash: {waiting_label: "Waiting for verification ..."}})
95
- I18n.backend.store_translations(:es, {active_hashcash: {waiting_label: "A la espera de la verificación ..."}})
96
- I18n.backend.store_translations(:fr, {active_hashcash: {waiting_label: "En attente de vérification ..."}})
97
- I18n.backend.store_translations(:it, {active_hashcash: {waiting_label: "In attesa di verifica ..."}})
98
- I18n.backend.store_translations(:jp, {active_hashcash: {waiting_label: "検証待ち ..."}})
99
- I18n.backend.store_translations(:pt, {active_hashcash: {waiting_label: "À espera de verificação ..."}})
100
- end
101
- end
102
- end
103
-
104
- class Stamp
105
- attr_reader :version, :bits, :date, :resource, :extension, :rand, :counter
106
-
107
- def self.parse(string)
108
- args = string.split(":")
109
- new(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
110
- end
111
-
112
- def self.mint(resource, options = {})
113
- new(
114
- options[:version] || 1,
115
- options[:bits] || ActiveHashcash.bits,
116
- options[:date] || Date.today.strftime("%y%m%d"),
117
- resource,
118
- options[:ext],
119
- options[:rand] || SecureRandom.alphanumeric(16),
120
- options[:counter] || 0).work
121
- end
122
-
123
- def initialize(version, bits, date, resource, extension, rand, counter)
124
- @version = version
125
- @bits = bits.to_i
126
- @date = date.respond_to?(:strftime) ? date.strftime("%y%m%d") : date
127
- @resource = resource
128
- @extension = extension
129
- @rand = rand
130
- @counter = counter
131
- end
132
-
133
- def valid?
134
- Digest::SHA1.hexdigest(to_s).hex >> (160-bits) == 0
135
- end
136
-
137
- def to_s
138
- [version, bits, date, resource, extension, rand, counter].join(":")
139
- end
140
-
141
- def parse_date
142
- Date.strptime(date, "%y%m%d")
143
- end
144
-
145
- def work
146
- @counter += 1 until valid?
147
- self
148
- end
149
- end
150
92
  end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :active_hashcash do
3
+ # # Task goes here
4
+ # end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_hashcash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Bernard
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-08 00:00:00.000000000 Z
11
+ date: 2024-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: redis
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 4.0.0
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: 4.0.0
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rails
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -38,8 +24,8 @@ dependencies:
38
24
  - - ">="
39
25
  - !ruby/object:Gem::Version
40
26
  version: 5.2.0
41
- description: ActiveHashcash protects your Rails application against brute force attacks,
42
- DoS and bots.
27
+ description: Protect Rails applications against bots and brute force attacks without
28
+ annoying humans.
43
29
  email:
44
30
  - alexis@basesecrete.com
45
31
  executables: []
@@ -50,7 +36,10 @@ files:
50
36
  - LICENSE.txt
51
37
  - README.md
52
38
  - lib/active_hashcash.rb
39
+ - lib/active_hashcash/engine.rb
40
+ - lib/active_hashcash/version.rb
53
41
  - lib/hashcash.js
42
+ - lib/tasks/active_hashcash_tasks.rake
54
43
  homepage: https://github.com/BaseSecrete/active_hashcash
55
44
  licenses:
56
45
  - MIT
@@ -58,7 +47,7 @@ metadata:
58
47
  homepage_uri: https://github.com/BaseSecrete/active_hashcash
59
48
  source_code_uri: https://github.com/BaseSecrete/active_hashcash
60
49
  changelog_uri: https://github.com/BaseSecrete/active_hashcash/CHANGELOG.md
61
- post_install_message:
50
+ post_install_message:
62
51
  rdoc_options: []
63
52
  require_paths:
64
53
  - lib
@@ -74,8 +63,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
63
  version: '0'
75
64
  requirements: []
76
65
  rubygems_version: 3.2.22
77
- signing_key:
66
+ signing_key:
78
67
  specification_version: 4
79
- summary: ActiveHashcash protects your Rails application against brute force attacks,
80
- DoS and bots.
68
+ summary: Protect Rails applications against bots and brute force attacks without annoying
69
+ humans.
81
70
  test_files: []