aikido-zen 1.0.2.beta.10-x86_64-darwin → 1.0.2-x86_64-darwin

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/docs/config.md +1 -1
  4. data/docs/troubleshooting.md +62 -0
  5. data/lib/aikido/zen/agent.rb +2 -2
  6. data/lib/aikido/zen/attack.rb +8 -6
  7. data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
  8. data/lib/aikido/zen/attack_wave.rb +88 -0
  9. data/lib/aikido/zen/cache.rb +91 -0
  10. data/lib/aikido/zen/capped_collections.rb +22 -4
  11. data/lib/aikido/zen/collector/event.rb +29 -0
  12. data/lib/aikido/zen/collector/hosts.rb +16 -1
  13. data/lib/aikido/zen/collector/stats.rb +17 -3
  14. data/lib/aikido/zen/collector/users.rb +2 -2
  15. data/lib/aikido/zen/collector.rb +14 -0
  16. data/lib/aikido/zen/config.rb +29 -6
  17. data/lib/aikido/zen/context/rack_request.rb +3 -0
  18. data/lib/aikido/zen/context/rails_request.rb +3 -0
  19. data/lib/aikido/zen/context.rb +2 -2
  20. data/lib/aikido/zen/event.rb +47 -2
  21. data/lib/aikido/zen/helpers.rb +24 -0
  22. data/lib/aikido/zen/middleware/{check_allowed_addresses.rb → allowed_address_checker.rb} +1 -1
  23. data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
  24. data/lib/aikido/zen/middleware/{set_context.rb → context_setter.rb} +1 -1
  25. data/lib/aikido/zen/middleware/rack_throttler.rb +3 -1
  26. data/lib/aikido/zen/middleware/request_tracker.rb +8 -3
  27. data/lib/aikido/zen/outbound_connection.rb +11 -1
  28. data/lib/aikido/zen/rails_engine.rb +3 -2
  29. data/lib/aikido/zen/request/rails_router.rb +17 -2
  30. data/lib/aikido/zen/request.rb +2 -36
  31. data/lib/aikido/zen/route.rb +50 -0
  32. data/lib/aikido/zen/runtime_settings/endpoints.rb +37 -8
  33. data/lib/aikido/zen/runtime_settings.rb +5 -4
  34. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +3 -2
  35. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +3 -2
  36. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +3 -2
  37. data/lib/aikido/zen/scanners/ssrf_scanner.rb +2 -1
  38. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +5 -1
  39. data/lib/aikido/zen/sinks/action_controller.rb +3 -1
  40. data/lib/aikido/zen/sinks/socket.rb +7 -0
  41. data/lib/aikido/zen/system_info.rb +1 -5
  42. data/lib/aikido/zen/version.rb +1 -1
  43. data/lib/aikido/zen.rb +55 -6
  44. data/tasklib/bench.rake +1 -1
  45. metadata +10 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06c1f2c47b0f89b6ac01829f58d780ba2a8afe79cb3ed7cb84ae3174a6b7b99b
4
- data.tar.gz: a82b7e203f4e07ea1a27e2e95c1e16060fec493c66f96c64c704ad209b6e1367
3
+ metadata.gz: 56688012bf2ffa1f5e46297d3c33c922aaa296c36fed10f1241d59af612f6035
4
+ data.tar.gz: e5ccc6144cd3df2d7f2bace371a5c6db80e4c67549b944f33382169255ebe03a
5
5
  SHA512:
6
- metadata.gz: 26f7e3ca103c02f3b8ad488dea789db23f80e13c619c98e2ec7fb84171b40f4e61322c22ebc40c69c2334fd654b9f094e7e2920012e24f5ef15d09c93fb9d952
7
- data.tar.gz: 0d7f0dbc1498a1a12772db2b2ee6d00fa064a12e97905efa221ac4b810737a7b3cd0de33756c36608d2cfa27a195afa4ac91278d2bb4c972c3c860b84f516743
6
+ metadata.gz: c1403be3b7e971c1cf0cc209338cf31a1c819c92b7cd2355f29036528472a33f072fd5f92329a5801e17d77917cb1af5e17d4ece1f4262e80d6fc5003e3907b4
7
+ data.tar.gz: c1e83d45fbcc247daeee059f153719e3108a4b1509daf81cb26b5470659c5ac4df7e0b485d39380a6ea37722d0fc04fe3e8149ac87e155fb01156bd8f6626559
data/README.md CHANGED
@@ -23,6 +23,7 @@ Zen will autonomously protect your Ruby applications against:
23
23
  * 🛡️ [Command injection attacks](https://www.aikido.dev/blog/command-injection-in-2024-unpacked)
24
24
  * 🛡️ [Path traversal attacks](https://www.aikido.dev/blog/path-traversal-in-2024-the-year-unpacked)
25
25
  * 🛡️ [NoSQL injection attacks](https://www.aikido.dev/blog/web-application-security-vulnerabilities) (coming soon)
26
+ * 🛡️ [Attack waves](https://help.aikido.dev/zen-firewall/zen-features/attack-wave-protection)
26
27
 
27
28
  Zen operates autonomously on the same server as your Ruby app to:
28
29
 
data/docs/config.md CHANGED
@@ -8,7 +8,7 @@ other Rack-based apps).
8
8
  ## Disable Zen
9
9
 
10
10
  In order to fully turn off Zen and prevent it from intercepting any requests or
11
- reporting back to the Aikido servers, set `AIKIDO_DISABLED=true` in your
11
+ reporting back to the Aikido servers, set `AIKIDO_DISABLE=true` in your
12
12
  environment, or set `Aikido::Zen.config.disabled = true`.
13
13
 
14
14
  (We recommend the ENV variable as you can normally change this easily without
@@ -0,0 +1,62 @@
1
+ # Troubleshooting
2
+
3
+ If the Zen Firewall isn't working as expected, follow these steps in order to diagnose common issues.
4
+
5
+ ## Review installation steps
6
+
7
+ Double-check your setup against the [installation guide](../README.md#installation).
8
+
9
+ Make sure:
10
+ - Your runtime and framework are supported (see [Supported libraries and frameworks](../README.md#supported-libraries-and-frameworks)).
11
+ - The package installed successfully.
12
+ - Your framework-specific integration matches the example in the docs (for Rails, see the example in [docs/rails.md](../docs/rails.md)).
13
+
14
+ ## Check connection to Aikido
15
+
16
+ The firewall must be able to reach Aikido's API endpoints.
17
+
18
+ Test connectivity from the same environment where your app runs, following the instructions on this page: https://help.aikido.dev/zen-firewall/miscellaneous/outbound-network-connections-for-zen
19
+
20
+ ## Check logs for errors
21
+
22
+ Common places:
23
+ - Local dev: `cat log/development.log` or `tail -f log/development.log`
24
+ - Docker: `docker logs <your-app-container>`
25
+ - systemd: `journalctl -u <your-app-service> --since "1 hour ago"`
26
+
27
+ Tip: search logs for lines containing `Aikido` or `Zen`.
28
+
29
+ For example:
30
+
31
+ ```sh
32
+ grep -Ei 'aikido|zen' log/development.log
33
+ ```
34
+
35
+ ## Enable debug output temporarily
36
+
37
+ If you use Rails, set the log level to `info` or `debug` while you investigate.
38
+
39
+ ```ruby
40
+ # config/environments/development.rb or production.rb
41
+ config.log_level = :info # use :debug if needed
42
+ ```
43
+
44
+ If you have your own logger, make sure Zen logs go to stdout.
45
+
46
+ You can enable Zen debugging mode as follows.
47
+
48
+ ```ruby
49
+ # config/initializers/zen.rb
50
+ Rails.application.config.zen.debugging = true
51
+ ```
52
+
53
+ Or set `AIKIDO_DEBUG=true` in your environment.
54
+
55
+ ## Contact support
56
+
57
+ If you still can't resolve the problem:
58
+
59
+ - Use the in-app chat to reach our support team directly.
60
+ - Or create an issue on [GitHub](https://github.com/AikidoSec/firewall-ruby/issues) with details about your setup, framework, and logs.
61
+
62
+ Include as much context as possible; this helps us respond faster.
@@ -43,7 +43,7 @@ module Aikido::Zen
43
43
  @started_at = Time.now.utc
44
44
  @collector.start(at: @started_at)
45
45
 
46
- if @config.blocking_mode?
46
+ if Aikido::Zen.blocking_mode?
47
47
  @config.logger.info("Requests identified as attacks will be blocked")
48
48
  else
49
49
  @config.logger.warn("Non-blocking mode enabled! No requests will be blocked.")
@@ -106,7 +106,7 @@ module Aikido::Zen
106
106
  # @raise [Aikido::Zen::UnderAttackError] if the firewall is configured
107
107
  # to block requests.
108
108
  def handle_attack(attack)
109
- attack.will_be_blocked! if @config.blocking_mode?
109
+ attack.will_be_blocked! if Aikido::Zen.blocking_mode?
110
110
 
111
111
  @config.logger.error(
112
112
  format("Zen has %s a %s: %s", attack.blocked? ? "blocked" : "detected", attack.humanized_name, attack.as_json.to_json)
@@ -10,10 +10,11 @@ module Aikido::Zen
10
10
  attr_reader :operation
11
11
  attr_accessor :sink
12
12
 
13
- def initialize(context:, sink:, operation:)
13
+ def initialize(context:, sink:, operation:, stack: nil)
14
14
  @context = context
15
15
  @operation = operation
16
16
  @sink = sink
17
+ @stack = stack
17
18
  @blocked = false
18
19
  end
19
20
 
@@ -46,8 +47,9 @@ module Aikido::Zen
46
47
  kind: kind,
47
48
  blocked: blocked?,
48
49
  metadata: metadata,
49
- operation: @operation
50
- }.merge(input.as_json)
50
+ operation: @operation,
51
+ stack: @stack
52
+ }.compact.merge(input.as_json)
51
53
  end
52
54
 
53
55
  def exception(*)
@@ -171,7 +173,7 @@ module Aikido::Zen
171
173
  def metadata
172
174
  {
173
175
  hostname: @request.uri.hostname,
174
- port: @request.uri.port
176
+ port: @request.uri.port.to_s
175
177
  }
176
178
  end
177
179
  end
@@ -197,7 +199,7 @@ module Aikido::Zen
197
199
  end
198
200
 
199
201
  def kind
200
- "ssrf"
202
+ "stored_ssrf"
201
203
  end
202
204
 
203
205
  def input
@@ -207,7 +209,7 @@ module Aikido::Zen
207
209
  def metadata
208
210
  {
209
211
  hostname: @hostname,
210
- resolvedIP: @address
212
+ privateIP: @address
211
213
  }
212
214
  end
213
215
  end
@@ -0,0 +1,457 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module AttackWave
5
+ module Helpers
6
+ def self.web_scanner?(context)
7
+ return true if suspicious_request?(context)
8
+
9
+ return true if include_suspicious_payload?(context)
10
+
11
+ false
12
+ end
13
+
14
+ def self.suspicious_request?(context)
15
+ request = context.request
16
+
17
+ suspicious_method?(request.request_method) || suspicious_path?(request.path_info)
18
+ end
19
+
20
+ def self.suspicious_method?(method)
21
+ SUSPICIOUS_METHODS.include?(method.downcase)
22
+ end
23
+
24
+ def self.suspicious_path?(path)
25
+ path_parts = path.downcase.split("/")
26
+
27
+ file_name = path_parts.pop if path_parts.length > 0
28
+
29
+ if file_name
30
+ return true if SUSPICIOUS_FILE_NAMES.include?(file_name)
31
+
32
+ file_name_parts = file_name.split(".")
33
+
34
+ file_extension = file_name_parts.pop if file_name_parts.length > 1
35
+
36
+ return true if SUSPICIOUS_FILE_EXTENSIONS.include?(file_extension)
37
+ end
38
+
39
+ path_parts.any? do |directory_name|
40
+ SUSPICIOUS_DIRECTORY_NAMES.include?(directory_name)
41
+ end
42
+ end
43
+
44
+ def self.include_suspicious_payload?(context)
45
+ context.payloads.each do |payload|
46
+ next unless payload.source == :query
47
+
48
+ value = payload.value.downcase
49
+
50
+ length = value.length
51
+
52
+ next if length < 5 || length > 1_000
53
+
54
+ return true if SUSPICIOUS_SQL_KEYWORDS.any? do |keyword|
55
+ value.include?(keyword)
56
+ end
57
+ end
58
+
59
+ false
60
+ end
61
+
62
+ SUSPICIOUS_METHODS = [
63
+ "BADMETHOD",
64
+ "BADHTTPMETHOD",
65
+ "BADDATA",
66
+ "BADMTHD",
67
+ "BDMTHD"
68
+ ].map(&:downcase).freeze
69
+
70
+ SUSPICIOUS_DIRECTORY_NAMES = [
71
+ ".",
72
+ "..",
73
+ ".anydesk",
74
+ ".aptitude",
75
+ ".aws",
76
+ ".azure",
77
+ ".cache",
78
+ ".circleci",
79
+ ".config",
80
+ ".dbus",
81
+ ".docker",
82
+ ".drush",
83
+ ".TODO: gem",
84
+ ".git",
85
+ ".github",
86
+ ".gnupg",
87
+ ".gsutil",
88
+ ".hg",
89
+ ".idea",
90
+ ".java",
91
+ ".kube",
92
+ ".lftp",
93
+ ".minikube",
94
+ ".npm",
95
+ ".nvm",
96
+ ".pki",
97
+ ".snap",
98
+ ".ssh",
99
+ ".subversion",
100
+ ".svn",
101
+ ".tconn",
102
+ ".thunderbird",
103
+ ".tor",
104
+ ".vagrant.d",
105
+ ".vidalia",
106
+ ".vim",
107
+ ".vmware",
108
+ ".vscode",
109
+ "apache",
110
+ "apache2",
111
+ "grub",
112
+ "System32",
113
+ "tmp",
114
+ "xampp",
115
+ "cgi-bin",
116
+ "%systemroot%"
117
+ ].map(&:downcase).freeze
118
+
119
+ SUSPICIOUS_FILE_NAMES = [
120
+ ".addressbook",
121
+ ".atom",
122
+ ".bashrc",
123
+ ".boto",
124
+ ".config",
125
+ ".config.json",
126
+ ".config.xml",
127
+ ".config.yaml",
128
+ ".config.yml",
129
+ ".envrc",
130
+ ".eslintignore",
131
+ ".fbcindex",
132
+ ".forward",
133
+ ".gitattributes",
134
+ ".gitconfig",
135
+ ".gitignore",
136
+ ".gitkeep",
137
+ ".gitlab-ci.yaml",
138
+ ".gitlab-ci.yml",
139
+ ".gitmodules",
140
+ ".google_authenticator",
141
+ ".hgignore",
142
+ ".htaccess",
143
+ ".htpasswd",
144
+ ".htdigest",
145
+ ".ksh_history",
146
+ ".lesshst",
147
+ ".lhistory",
148
+ ".lighttpdpassword",
149
+ ".lldb-history",
150
+ ".lynx_cookies",
151
+ ".my.cnf",
152
+ ".mysql_history",
153
+ ".nano_history",
154
+ ".netrc",
155
+ ".node_repl_history",
156
+ ".npmrc",
157
+ ".nsconfig",
158
+ ".nsr",
159
+ ".password-store",
160
+ ".pearrc",
161
+ ".pgpass",
162
+ ".php_history",
163
+ ".pinerc",
164
+ ".proclog",
165
+ ".procmailrc",
166
+ ".profile",
167
+ ".psql_history",
168
+ ".python_history",
169
+ ".rediscli_history",
170
+ ".rhosts",
171
+ ".selected_editor",
172
+ ".sh_history",
173
+ ".sqlite_history",
174
+ ".svnignore",
175
+ ".tcshrc",
176
+ ".tmux.conf",
177
+ ".travis.yaml",
178
+ ".travis.yml",
179
+ ".viminfo",
180
+ ".vimrc",
181
+ ".www_acl",
182
+ ".wwwacl",
183
+ ".xauthority",
184
+ ".yarnrc",
185
+ ".zhistory",
186
+ ".zsh_history",
187
+ ".zshenv",
188
+ ".zshrc",
189
+ "Dockerfile",
190
+ "aws-key.yaml",
191
+ "aws-key.yml",
192
+ "aws.yaml",
193
+ "aws.yml",
194
+ "docker-compose.yaml",
195
+ "docker-compose.yml",
196
+ "npm-shrinkwrap.json",
197
+ "package-lock.json",
198
+ "package.json",
199
+ "phpinfo.php",
200
+ "wp-config.php",
201
+ "wp-config.php3",
202
+ "wp-config.php4",
203
+ "wp-config.php5",
204
+ "wp-config.phtml",
205
+ "composer.json",
206
+ "composer.lock",
207
+ "composer.phar",
208
+ "yarn.lock",
209
+ ".env.local",
210
+ ".env.development",
211
+ ".env.test",
212
+ ".env.production",
213
+ ".env.prod",
214
+ ".env.dev",
215
+ ".env.example",
216
+ "php.ini",
217
+ "wp-settings.php",
218
+ "config.asp",
219
+ "config_dev.asp",
220
+ "config-dev.asp",
221
+ "config.dev.asp",
222
+ "config_prod.asp",
223
+ "config-prod.asp",
224
+ "config.prod.asp",
225
+ "config.sample.asp",
226
+ "config-sample.asp",
227
+ "config_sample.asp",
228
+ "config_test.asp",
229
+ "config-test.asp",
230
+ "config.test.asp",
231
+ "config.ini",
232
+ "config_dev.ini",
233
+ "config-dev.ini",
234
+ "config.dev.ini",
235
+ "config_prod.ini",
236
+ "config-prod.ini",
237
+ "config.prod.ini",
238
+ "config.sample.ini",
239
+ "config-sample.ini",
240
+ "config_sample.ini",
241
+ "config_test.ini",
242
+ "config-test.ini",
243
+ "config.test.ini",
244
+ "config.json",
245
+ "config_dev.json",
246
+ "config-dev.json",
247
+ "config.dev.json",
248
+ "config_prod.json",
249
+ "config-prod.json",
250
+ "config.prod.json",
251
+ "config.sample.json",
252
+ "config-sample.json",
253
+ "config_sample.json",
254
+ "config_test.json",
255
+ "config-test.json",
256
+ "config.test.json",
257
+ "config.php",
258
+ "config_dev.php",
259
+ "config-dev.php",
260
+ "config.dev.php",
261
+ "config_prod.php",
262
+ "config-prod.php",
263
+ "config.prod.php",
264
+ "config.sample.php",
265
+ "config-sample.php",
266
+ "config_sample.php",
267
+ "config_test.php",
268
+ "config-test.php",
269
+ "config.test.php",
270
+ "config.pl",
271
+ "config_dev.pl",
272
+ "config-dev.pl",
273
+ "config.dev.pl",
274
+ "config_prod.pl",
275
+ "config-prod.pl",
276
+ "config.prod.pl",
277
+ "config.sample.pl",
278
+ "config-sample.pl",
279
+ "config_sample.pl",
280
+ "config_test.pl",
281
+ "config-test.pl",
282
+ "config.test.pl",
283
+ "config.py",
284
+ "config_dev.py",
285
+ "config-dev.py",
286
+ "config.dev.py",
287
+ "config_prod.py",
288
+ "config-prod.py",
289
+ "config.prod.py",
290
+ "config.sample.py",
291
+ "config-sample.py",
292
+ "config_sample.py",
293
+ "config_test.py",
294
+ "config-test.py",
295
+ "config.test.py",
296
+ "config.rb",
297
+ "config_dev.rb",
298
+ "config-dev.rb",
299
+ "config.dev.rb",
300
+ "config_prod.rb",
301
+ "config-prod.rb",
302
+ "config.prod.rb",
303
+ "config.sample.rb",
304
+ "config-sample.rb",
305
+ "config_sample.rb",
306
+ "config_test.rb",
307
+ "config-test.rb",
308
+ "config.test.rb",
309
+ "config.toml",
310
+ "config_dev.toml",
311
+ "config-dev.toml",
312
+ "config.dev.toml",
313
+ "config_prod.toml",
314
+ "config-prod.toml",
315
+ "config.prod.toml",
316
+ "config.sample.toml",
317
+ "config-sample.toml",
318
+ "config_sample.toml",
319
+ "config_test.toml",
320
+ "config-test.toml",
321
+ "config.test.toml",
322
+ "config.txt",
323
+ "config_dev.txt",
324
+ "config-dev.txt",
325
+ "config.dev.txt",
326
+ "config_prod.txt",
327
+ "config-prod.txt",
328
+ "config.prod.txt",
329
+ "config.sample.txt",
330
+ "config-sample.txt",
331
+ "config_sample.txt",
332
+ "config_test.txt",
333
+ "config-test.txt",
334
+ "config.test.txt",
335
+ "config.xml",
336
+ "config_dev.xml",
337
+ "config-dev.xml",
338
+ "config.dev.xml",
339
+ "config_prod.xml",
340
+ "config-prod.xml",
341
+ "config.prod.xml",
342
+ "config.sample.xml",
343
+ "config-sample.xml",
344
+ "config_sample.xml",
345
+ "config_test.xml",
346
+ "config-test.xml",
347
+ "config.test.xml",
348
+ "config.yaml",
349
+ "config_dev.yaml",
350
+ "config-dev.yaml",
351
+ "config.dev.yaml",
352
+ "config_prod.yaml",
353
+ "config-prod.yaml",
354
+ "config.prod.yaml",
355
+ "config.sample.yaml",
356
+ "config-sample.yaml",
357
+ "config_sample.yaml",
358
+ "config_test.yaml",
359
+ "config-test.yaml",
360
+ "config.test.yaml",
361
+ "config.yml",
362
+ "config_dev.yml",
363
+ "config-dev.yml",
364
+ "config.dev.yml",
365
+ "config_prod.yml",
366
+ "config-prod.yml",
367
+ "config.prod.yml",
368
+ "config.sample.yml",
369
+ "config-sample.yml",
370
+ "config_sample.yml",
371
+ "config_test.yml",
372
+ "config-test.yml",
373
+ "config.test.yml",
374
+ "boot.ini",
375
+ "gruntfile.js",
376
+ "localsettings.php",
377
+ "my.ini",
378
+ "npm-debug.log",
379
+ "parameters.yml",
380
+ "parameters.yaml",
381
+ "services.yml",
382
+ "services.yaml",
383
+ "web.config",
384
+ "webpack.config.js",
385
+ "config.old",
386
+ "config.inc.php",
387
+ "error.log",
388
+ "access.log",
389
+ ".DS_Store",
390
+ "passwd",
391
+ "win.ini",
392
+ "cmd.exe",
393
+ "my.cnf",
394
+ ".bash_history",
395
+ "docker-compose-dev.yml",
396
+ "docker-compose.override.yml",
397
+ "docker-compose.dev.yml",
398
+ "Cargo.lock",
399
+ "secrets.yml",
400
+ "secrets.yaml",
401
+ "docker-compose.staging.yml",
402
+ "docker-compose.production.yml",
403
+ "yaws-key.pem",
404
+ "mysql_config.ini",
405
+ "firewall.log",
406
+ "log4j.properties",
407
+ "serviceAccountCredentials.json",
408
+ "haproxy.cfg",
409
+ "service-account-credentials.json",
410
+ "vpn.log",
411
+ "system.log",
412
+ "webuser-auth.xml",
413
+ "fastcgi.conf",
414
+ "smb.conf",
415
+ "iis.log",
416
+ "pom.xml",
417
+ "openapi.json",
418
+ "vim_settings.xml",
419
+ "winscp.ini",
420
+ "ws_ftp.ini"
421
+ ].map(&:downcase).freeze
422
+
423
+ SUSPICIOUS_FILE_EXTENSIONS = [
424
+ "env",
425
+ "bak",
426
+ "sql",
427
+ "sqlite",
428
+ "sqlite3",
429
+ "db",
430
+ "old",
431
+ "save",
432
+ "orig",
433
+ "sqlitedb",
434
+ "sqlite3db"
435
+ ].map(&:downcase).freeze
436
+
437
+ SUSPICIOUS_SQL_KEYWORDS = [
438
+ "SELECT (CASE WHEN",
439
+ "SELECT COUNT(",
440
+ "SLEEP(",
441
+ "WAITFOR DELAY",
442
+ "SELECT LIKE(CHAR(",
443
+ "INFORMATION_SCHEMA.COLUMNS",
444
+ "INFORMATION_SCHEMA.TABLES",
445
+ "MD5(",
446
+ "DBMS_PIPE.RECEIVE_MESSAGE",
447
+ "SYSIBM.SYSTABLES",
448
+ "RANDOMBLOB(",
449
+ "SELECT * FROM",
450
+ "1'='1",
451
+ "PG_SLEEP(",
452
+ "UNION ALL SELECT",
453
+ "../"
454
+ ].map(&:downcase).freeze
455
+ end
456
+ end
457
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cache"
4
+ require_relative "attack_wave/helpers"
5
+
6
+ module Aikido::Zen
7
+ module AttackWave
8
+ class Detector
9
+ def initialize(config: Aikido::Zen.config, clock: nil)
10
+ @config = config
11
+
12
+ @event_times = Cache.new(@config.attack_wave_max_cache_entries, ttl: @config.attack_wave_min_time_between_events, clock: clock)
13
+
14
+ @request_counts = Cache.new(@config.attack_wave_max_cache_entries, 0, ttl: @config.attack_wave_min_time_between_requests, clock: clock)
15
+ end
16
+
17
+ def attack_wave?(context)
18
+ client_ip = context.request.client_ip
19
+
20
+ return false unless client_ip
21
+
22
+ return false if @event_times[client_ip]
23
+
24
+ return false unless AttackWave::Helpers.web_scanner?(context)
25
+
26
+ request_count = @request_counts[client_ip] += 1
27
+
28
+ return false if request_count < @config.attack_wave_threshold
29
+
30
+ @event_times[client_ip] = Time.now.utc
31
+
32
+ true
33
+ end
34
+ end
35
+
36
+ class Request
37
+ # @return [String]
38
+ attr_reader :ip_address
39
+
40
+ # @return [String]
41
+ attr_reader :user_agent
42
+
43
+ # @return [String]
44
+ attr_reader :source
45
+
46
+ # @param ip_address [String]
47
+ # @param user_agent [String]
48
+ # @param source [String]
49
+ # @return [Aikido::Zen::AttackWave::Request]
50
+ def initialize(ip_address:, user_agent:, source:)
51
+ @ip_address = ip_address
52
+ @user_agent = user_agent
53
+ @source = source
54
+ end
55
+
56
+ def as_json
57
+ {
58
+ ipAddress: @ip_address,
59
+ userAgent: @user_agent,
60
+ source: @source
61
+ }.compact
62
+ end
63
+ end
64
+
65
+ class Attack
66
+ # @return [Hash<String, String>]
67
+ attr_reader :metadata
68
+
69
+ # @return [Aikido::Zen::Actor]
70
+ attr_reader :user
71
+
72
+ # @param metadata [Hash<String, String>]
73
+ # @param metadata [Aikido::Zen::Actor]
74
+ # @return [Aikido::Zen::AttackWave::Attack]
75
+ def initialize(metadata:, user:)
76
+ @metadata = metadata
77
+ @user = user
78
+ end
79
+
80
+ def as_json
81
+ {
82
+ metadata: @metadata.as_json,
83
+ user: @user.as_json
84
+ }.compact
85
+ end
86
+ end
87
+ end
88
+ end