beskar 0.0.2 → 0.1.0
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 +4 -4
- data/CHANGELOG.md +143 -0
- data/README.md +298 -110
- data/app/controllers/beskar/application_controller.rb +170 -0
- data/app/controllers/beskar/banned_ips_controller.rb +280 -0
- data/app/controllers/beskar/dashboard_controller.rb +70 -0
- data/app/controllers/beskar/security_events_controller.rb +182 -0
- data/app/controllers/concerns/beskar/controllers/security_tracking.rb +6 -6
- data/app/models/beskar/banned_ip.rb +68 -27
- data/app/models/beskar/security_event.rb +14 -0
- data/app/services/beskar/banned_ip_manager.rb +78 -0
- data/app/views/beskar/banned_ips/edit.html.erb +259 -0
- data/app/views/beskar/banned_ips/index.html.erb +361 -0
- data/app/views/beskar/banned_ips/new.html.erb +310 -0
- data/app/views/beskar/banned_ips/show.html.erb +310 -0
- data/app/views/beskar/dashboard/index.html.erb +280 -0
- data/app/views/beskar/security_events/index.html.erb +309 -0
- data/app/views/beskar/security_events/show.html.erb +307 -0
- data/app/views/layouts/beskar/application.html.erb +647 -5
- data/config/routes.rb +41 -0
- data/lib/beskar/configuration.rb +24 -10
- data/lib/beskar/engine.rb +4 -4
- data/lib/beskar/logger.rb +293 -0
- data/lib/beskar/middleware/request_analyzer.rb +128 -53
- data/lib/beskar/models/security_trackable_authenticable.rb +11 -11
- data/lib/beskar/models/security_trackable_devise.rb +5 -5
- data/lib/beskar/models/security_trackable_generic.rb +12 -12
- data/lib/beskar/services/account_locker.rb +12 -12
- data/lib/beskar/services/geolocation_service.rb +8 -8
- data/lib/beskar/services/ip_whitelist.rb +2 -2
- data/lib/beskar/services/waf.rb +307 -78
- data/lib/beskar/version.rb +1 -1
- data/lib/beskar.rb +1 -0
- data/lib/generators/beskar/install/install_generator.rb +158 -0
- data/lib/generators/beskar/install/templates/initializer.rb.tt +177 -0
- data/lib/tasks/beskar_tasks.rake +11 -2
- metadata +35 -6
- data/lib/beskar/templates/beskar_initializer.rb +0 -107
data/README.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
**Beskar** is a comprehensive, Rails-native security engine designed to provide multi-layered, proactive protection for modern web applications. It defends against common threats, bot activity, and account takeovers without requiring external dependencies, integrating seamlessly into your application as a natural extension of the framework.
|
|
4
4
|
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#features)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [Dashboard Authentication (REQUIRED)](#dashboard-authentication-required)
|
|
11
|
+
- [Add to Your User Model](#add-to-your-user-model)
|
|
12
|
+
- [Configuration](#configuration)
|
|
13
|
+
- [Usage](#usage)
|
|
14
|
+
- [Risk-Based Account Locking](#risk-based-account-locking-with-devise-lockable)
|
|
15
|
+
- [Rate Limiting](#rate-limiting)
|
|
16
|
+
- [IP Whitelisting](#ip-whitelisting)
|
|
17
|
+
- [Web Application Firewall (WAF)](#web-application-firewall-waf)
|
|
18
|
+
- [IP Blocking and Banning](#ip-blocking-and-banning)
|
|
19
|
+
- [Security Events](#security-events)
|
|
20
|
+
- [Middleware Integration](#middleware-integration)
|
|
21
|
+
- [WAF Pattern Reference](#waf-pattern-reference)
|
|
22
|
+
- [Security Best Practices](#security-best-practices)
|
|
23
|
+
- [Troubleshooting](#troubleshooting)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Contributing](#contributing)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
5
28
|
## Features
|
|
6
29
|
|
|
7
30
|
- **Devise Integration:** Seamless integration with Devise authentication for automatic login tracking and security analysis.
|
|
@@ -10,14 +33,14 @@
|
|
|
10
33
|
- **Brute Force Detection:** Advanced pattern recognition to detect single account attacks vs credential stuffing attempts, with automatic IP banning.
|
|
11
34
|
- **IP Whitelisting:** Allow trusted IPs (office networks, partners, security scanners) to bypass blocking while maintaining full audit logs. Supports individual IPs and CIDR notation.
|
|
12
35
|
- **Persistent IP Blocking:** Hybrid cache + database blocking system that survives application restarts. Auto-bans IPs after authentication abuse or excessive rate limiting violations.
|
|
13
|
-
- **Web Application Firewall (WAF):** Real-time detection and blocking of vulnerability scanning attempts across
|
|
36
|
+
- **Web Application Firewall (WAF):** Real-time detection and blocking of vulnerability scanning attempts across 12 attack categories including Rails exception analysis (WordPress scans, WordPress static files, PHP admin panels, config files, path traversal, framework debug, CMS detection, common exploits, UnknownFormat, IP spoofing, InvalidType, RecordNotFound enumeration). Includes escalating ban durations, monitor-only mode, and configurable exclusion patterns.
|
|
14
37
|
- **Security Event Tracking:** Comprehensive logging of authentication events with risk scoring and metadata extraction.
|
|
15
38
|
- **IP Geolocation:** MaxMind GeoLite2-City database integration for country/city location, coordinates, timezone, and enhanced risk scoring (configurable, database not included due to licensing).
|
|
16
39
|
- **Geographic Anomaly Detection:** Haversine-based impossible travel detection and location-based risk assessment.
|
|
17
40
|
- **Advanced Bot Detection:** Multi-layered defense using JavaScript challenges and invisible honeypots to filter out malicious bots while allowing legitimate ones.
|
|
18
41
|
- **Modular Architecture:** Devise-specific code is isolated in separate services for maintainability and extensibility.
|
|
19
42
|
- **Rails-Native Architecture:** Built as a mountable `Rails::Engine`, it leverages `ActiveJob` and `Rails.cache` for high performance and low overhead.
|
|
20
|
-
- **
|
|
43
|
+
- **Security Dashboard:** A mountable web interface for monitoring security events, managing IP bans, and viewing statistics. Features configurable authentication, real-time filtering, and export capabilities. See [Dashboard Authentication](#dashboard-authentication) section below.
|
|
21
44
|
|
|
22
45
|
## Installation
|
|
23
46
|
|
|
@@ -52,22 +75,98 @@ bin/rails db:migrate
|
|
|
52
75
|
|
|
53
76
|
### Quick Start
|
|
54
77
|
|
|
78
|
+
**1. Configure Dashboard Authentication (Required)**
|
|
79
|
+
|
|
80
|
+
Before using Beskar, you must configure authentication for the dashboard. See the [Dashboard Authentication](#dashboard-authentication) section below for details and examples.
|
|
81
|
+
|
|
82
|
+
**2. Enable WAF Monitoring**
|
|
83
|
+
|
|
55
84
|
By default, Beskar enables the **Web Application Firewall (WAF) in monitor-only mode**. This means:
|
|
56
85
|
- ✅ Vulnerability scans are detected and logged
|
|
57
|
-
- ✅ Security events are created for analysis
|
|
86
|
+
- ✅ Security events are created for analysis
|
|
58
87
|
- ⚠️ No requests are blocked yet (safe to enable in production)
|
|
59
88
|
|
|
60
89
|
After monitoring for 24-48 hours, review the logs and disable monitor-only mode to enable active blocking:
|
|
61
90
|
|
|
62
91
|
```ruby
|
|
63
92
|
# config/initializers/beskar.rb
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
Beskar.configure do |config|
|
|
94
|
+
config.monitor_only = true # Change this to false to enable blocking
|
|
95
|
+
config.waf[:enabled] = true
|
|
67
96
|
# ... rest of configuration
|
|
68
|
-
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Dashboard Authentication (REQUIRED)
|
|
101
|
+
|
|
102
|
+
**⚠️ IMPORTANT: Dashboard authentication must be configured for all environments.**
|
|
103
|
+
|
|
104
|
+
The Beskar dashboard requires authentication to prevent unauthorized access. You must configure how users authenticate to access the dashboard by setting up the `authenticate_admin` callback:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
# config/initializers/beskar.rb
|
|
108
|
+
Beskar.configure do |config|
|
|
109
|
+
# REQUIRED: Configure dashboard authentication
|
|
110
|
+
# The block is executed in the controller context and receives the request object.
|
|
111
|
+
# You have access to all controller methods (cookies, session, etc.) and helpers.
|
|
112
|
+
config.authenticate_admin = ->(request) do
|
|
113
|
+
# Return truthy to allow access, falsey to deny
|
|
114
|
+
|
|
115
|
+
# Example 1: Devise with admin role (recommended for production)
|
|
116
|
+
user = request.env['warden']&.authenticate(scope: :user)
|
|
117
|
+
user&.admin?
|
|
118
|
+
end
|
|
119
|
+
end
|
|
69
120
|
```
|
|
70
121
|
|
|
122
|
+
**Why this is required:** Previous versions allowed unauthenticated access in development/test environments, which could lead to production security issues. Now, authentication must be explicitly configured for all environments to prevent accidental exposure.
|
|
123
|
+
|
|
124
|
+
**Other Authentication Strategies:**
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Token-based authentication
|
|
128
|
+
config.authenticate_admin = ->(request) do
|
|
129
|
+
request.headers['Authorization'] == "Bearer #{ENV['BESKAR_ADMIN_TOKEN']}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# HTTP Basic Auth (uses controller method)
|
|
133
|
+
config.authenticate_admin = ->(request) do
|
|
134
|
+
authenticate_or_request_with_http_basic do |username, password|
|
|
135
|
+
username == ENV['BESKAR_USERNAME'] && password == ENV['BESKAR_PASSWORD']
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Cookie-based authentication (uses controller cookies)
|
|
140
|
+
config.authenticate_admin = ->(request) do
|
|
141
|
+
cookies.signed[:admin_token] == ENV['BESKAR_ADMIN_TOKEN']
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Development/Testing bypass (use with caution!)
|
|
145
|
+
config.authenticate_admin = ->(request) do
|
|
146
|
+
Rails.env.development? || Rails.env.test?
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Accessing the Dashboard:**
|
|
151
|
+
|
|
152
|
+
After configuring authentication, mount the engine in your routes:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
# config/routes.rb
|
|
156
|
+
Rails.application.routes.draw do
|
|
157
|
+
mount Beskar::Engine => "/beskar"
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Then visit `http://localhost:3000/beskar` to access the dashboard.
|
|
162
|
+
|
|
163
|
+
**Dashboard Features:**
|
|
164
|
+
- 📊 Security event monitoring with filtering and search
|
|
165
|
+
- 🚫 IP ban management (view, extend, unban)
|
|
166
|
+
- 📈 Statistics and risk distribution analysis
|
|
167
|
+
- 📥 Export capabilities (CSV/JSON)
|
|
168
|
+
- 🔒 CSRF protection and secure by default
|
|
169
|
+
|
|
71
170
|
### Add to Your User Model
|
|
72
171
|
|
|
73
172
|
Include the `SecurityTrackable` concern in your Devise user model:
|
|
@@ -75,8 +174,8 @@ Include the `SecurityTrackable` concern in your Devise user model:
|
|
|
75
174
|
```ruby
|
|
76
175
|
# app/models/user.rb
|
|
77
176
|
class User < ApplicationRecord
|
|
78
|
-
include Beskar::SecurityTrackable
|
|
79
|
-
|
|
177
|
+
include Beskar::Models::SecurityTrackable
|
|
178
|
+
|
|
80
179
|
devise :database_authenticatable, :registerable,
|
|
81
180
|
:recoverable, :rememberable, :validatable
|
|
82
181
|
# ... other Devise modules
|
|
@@ -85,11 +184,20 @@ end
|
|
|
85
184
|
|
|
86
185
|
## Configuration
|
|
87
186
|
|
|
88
|
-
You can configure Beskar in the initializer file created by the installer
|
|
187
|
+
You can configure Beskar in the initializer file created by the installer.
|
|
188
|
+
|
|
189
|
+
> **Note:** Dashboard authentication setup is covered in the [Dashboard Authentication](#dashboard-authentication-required) section above.
|
|
89
190
|
|
|
90
191
|
```ruby
|
|
91
192
|
# config/initializers/beskar.rb
|
|
92
193
|
Beskar.configure do |config|
|
|
194
|
+
# === Dashboard Authentication (REQUIRED) ===
|
|
195
|
+
# See "Dashboard Authentication" section above for examples and details
|
|
196
|
+
config.authenticate_admin = ->(request) do
|
|
197
|
+
user = request.env['warden']&.authenticate(scope: :user)
|
|
198
|
+
user&.admin?
|
|
199
|
+
end
|
|
200
|
+
|
|
93
201
|
# === Security Tracking ===
|
|
94
202
|
# Controls what security events are tracked and analyzed
|
|
95
203
|
config.security_tracking = {
|
|
@@ -119,27 +227,28 @@ Beskar.configure do |config|
|
|
|
119
227
|
}
|
|
120
228
|
|
|
121
229
|
# === IP Whitelisting ===
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
config.ip_whitelist = [
|
|
125
|
-
"192.168.1.100", # Single IP address
|
|
126
|
-
"10.0.0.0/24", # CIDR notation - entire subnet
|
|
127
|
-
"172.16.0.0/16", # Larger CIDR range
|
|
128
|
-
"2001:db8::/32" # IPv6 support
|
|
129
|
-
]
|
|
230
|
+
# See "IP Whitelisting" section below for detailed examples
|
|
231
|
+
config.ip_whitelist = [] # Add trusted IPs here (supports CIDR notation)
|
|
130
232
|
|
|
131
233
|
# === Web Application Firewall (WAF) ===
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
234
|
+
# See "Web Application Firewall" section below for production examples
|
|
235
|
+
# Defaults shown here - use [:key] syntax to preserve other defaults
|
|
236
|
+
config.waf[:enabled] = true # Master switch for WAF
|
|
237
|
+
# config.waf[:auto_block] = true # Default: true
|
|
238
|
+
# config.waf[:score_threshold] = 150 # Default: 150 (cumulative risk score before blocking)
|
|
239
|
+
# config.waf[:violation_window] = 6.hours # Default: 6 hours (max time to track violations)
|
|
240
|
+
# config.waf[:block_durations] = [1.hour, 6.hours, 24.hours, 7.days] # Escalating bans
|
|
241
|
+
# config.waf[:permanent_block_after] = 500 # Permanent after cumulative score reaches 500
|
|
242
|
+
# config.waf[:create_security_events] = true # Log to SecurityEvent table
|
|
243
|
+
# config.waf[:record_not_found_exclusions] = [] # Regex patterns for false positives
|
|
244
|
+
# config.waf[:decay_enabled] = true # Enable exponential decay of violation scores
|
|
245
|
+
# config.waf[:decay_rates] = { # Decay rates by severity (half-life in minutes)
|
|
246
|
+
# critical: 360, # 6 hour half-life
|
|
247
|
+
# high: 120, # 2 hour half-life
|
|
248
|
+
# medium: 45, # 45 minute half-life
|
|
249
|
+
# low: 15 # 15 minute half-life
|
|
250
|
+
# }
|
|
251
|
+
# config.waf[:max_violations_tracked] = 50 # Maximum violations to track per IP
|
|
143
252
|
|
|
144
253
|
# === Risk-Based Account Locking ===
|
|
145
254
|
# Automatically lock accounts when authentication risk score exceeds threshold
|
|
@@ -163,32 +272,11 @@ Beskar.configure do |config|
|
|
|
163
272
|
}
|
|
164
273
|
end
|
|
165
274
|
|
|
166
|
-
# Security Tracking Configuration Details
|
|
167
|
-
The security tracking system respects all configuration settings:
|
|
168
|
-
|
|
169
|
-
- **`enabled: false`** - Completely disables all security event tracking
|
|
170
|
-
- **`track_successful_logins: false`** - Stops tracking successful login events
|
|
171
|
-
- **`track_failed_logins: false`** - Stops tracking failed login attempts
|
|
172
|
-
- **`auto_analyze_patterns: false`** - Disables automatic threat pattern analysis
|
|
173
|
-
|
|
174
|
-
When tracking is disabled via configuration, no `SecurityEvent` records are created and no background analysis jobs are queued.
|
|
175
275
|
```
|
|
176
276
|
|
|
177
277
|
## Usage
|
|
178
278
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
Once installed and configured, Beskar works automatically with Devise. Add the `SecurityTrackable` module to your User model:
|
|
182
|
-
|
|
183
|
-
```ruby
|
|
184
|
-
class User < ApplicationRecord
|
|
185
|
-
devise :database_authenticatable, :registerable,
|
|
186
|
-
:recoverable, :rememberable, :validatable
|
|
187
|
-
|
|
188
|
-
# Add Beskar security tracking
|
|
189
|
-
include Beskar::Models::SecurityTrackable
|
|
190
|
-
end
|
|
191
|
-
```
|
|
279
|
+
> **Note:** If you haven't already, see the [Add to Your User Model](#add-to-your-user-model) section in Quick Start for setting up `SecurityTrackable`.
|
|
192
280
|
|
|
193
281
|
### Risk-Based Account Locking (with Devise Lockable)
|
|
194
282
|
|
|
@@ -203,7 +291,7 @@ class User < ApplicationRecord
|
|
|
203
291
|
devise :database_authenticatable, :registerable,
|
|
204
292
|
:recoverable, :rememberable, :validatable,
|
|
205
293
|
:lockable # Add this for risk-based locking
|
|
206
|
-
|
|
294
|
+
|
|
207
295
|
include Beskar::Models::SecurityTrackable
|
|
208
296
|
end
|
|
209
297
|
```
|
|
@@ -351,7 +439,7 @@ attack_type = rate_limiter.attack_pattern_type
|
|
|
351
439
|
case attack_type
|
|
352
440
|
when :brute_force_single_account
|
|
353
441
|
# Single IP attacking one account
|
|
354
|
-
when :distributed_single_account
|
|
442
|
+
when :distributed_single_account
|
|
355
443
|
# Multiple IPs attacking one account
|
|
356
444
|
when :single_ip_multiple_accounts
|
|
357
445
|
# One IP attacking multiple accounts (credential stuffing)
|
|
@@ -394,56 +482,156 @@ Beskar::Services::IpWhitelist.clear_cache!
|
|
|
394
482
|
|
|
395
483
|
### Web Application Firewall (WAF)
|
|
396
484
|
|
|
397
|
-
Beskar's WAF
|
|
485
|
+
Beskar's WAF uses a **score-based blocking system with exponential decay** to intelligently detect and block vulnerability scanning attempts across 12 attack categories:
|
|
398
486
|
|
|
399
487
|
**Attack Categories Detected:**
|
|
400
|
-
1. **WordPress Scans** (High
|
|
401
|
-
2. **
|
|
402
|
-
3. **
|
|
403
|
-
4. **
|
|
404
|
-
5. **
|
|
405
|
-
6. **
|
|
406
|
-
7. **
|
|
488
|
+
1. **WordPress Scans** (High: 80 points) - `/wp-admin`, `/wp-login.php`, `/wp-content/*.php`, `/xmlrpc.php`
|
|
489
|
+
2. **WordPress Static Files** (Low: 30 points) - `/wp-content/*.css`, `/wp-content/*.js`, `/wp-content/*.jpg` (broken links, not attacks)
|
|
490
|
+
3. **PHP Admin Panels** (High: 80 points) - `/phpmyadmin`, `/admin.php`, `/phpinfo.php`
|
|
491
|
+
4. **Config Files** (Critical: 95 points) - `/.env`, `/.git`, `/database.yml`
|
|
492
|
+
5. **Path Traversal** (Critical: 95 points) - `/../../../etc/passwd`, URL encoded variants
|
|
493
|
+
6. **Framework Debug** (Medium: 60 points) - `/rails/info/routes`, `/__debug__`, `/telescope`
|
|
494
|
+
7. **CMS Detection** (Medium: 60 points) - `/joomla`, `/drupal`, `/magento`
|
|
495
|
+
8. **Common Exploits** (Critical: 95 points) - `/shell.php`, `/c99.php`, `/webshell`
|
|
496
|
+
9. **ActionController::UnknownFormat** (Medium: 60 points) - Detects requests for unusual formats like `/users/1.exe`, `/api/data.bat` that trigger Rails format exceptions, indicating potential scanning
|
|
497
|
+
10. **ActionDispatch::RemoteIp::IpSpoofAttackError** (Critical: 95 points) - Detects IP spoofing attempts when conflicting IP headers are present
|
|
498
|
+
11. **ActionDispatch::Http::MimeNegotiation::InvalidType** (Medium: 60 points) - Detects invalid MIME type requests like `GET "../../../../../../../../etc/passwd{{"` that indicate scanner activity
|
|
499
|
+
12. **ActiveRecord::RecordNotFound** (Low: 30 points) - Detects potential record enumeration scans like `/admin/users/999999`, with configurable exclusions to prevent false positives
|
|
500
|
+
|
|
501
|
+
**How Score-Based Blocking Works:**
|
|
502
|
+
|
|
503
|
+
Instead of counting violations (1, 2, 3...), Beskar tracks a **cumulative risk score** that decays over time:
|
|
504
|
+
|
|
505
|
+
- Each violation adds points based on severity (Critical=95, High=80, Medium=60, Low=30)
|
|
506
|
+
- Violations **decay exponentially** based on severity (critical threats persist longer)
|
|
507
|
+
- IP is blocked when cumulative score reaches threshold (default: 150 points)
|
|
508
|
+
- Lower-severity violations decay faster, reducing false positives from legitimate 404s
|
|
509
|
+
|
|
510
|
+
**Example Scenarios:**
|
|
511
|
+
```ruby
|
|
512
|
+
# Scenario 1: Legitimate user hitting 404s
|
|
513
|
+
10 × RecordNotFound (30 points each) = 300 cumulative
|
|
514
|
+
BUT: Low severity decays with 15-minute half-life
|
|
515
|
+
→ Score drops quickly, no ban triggered
|
|
516
|
+
|
|
517
|
+
# Scenario 2: Attacker scanning config files
|
|
518
|
+
2 × /.env access (95 points each) = 190 points
|
|
519
|
+
→ Exceeds threshold (150) → Immediate ban
|
|
520
|
+
→ Critical severity persists for 6 hours
|
|
521
|
+
|
|
522
|
+
# Scenario 3: Mixed attack pattern
|
|
523
|
+
1 × WordPress scan (80) + 1 × Config access (95) = 175
|
|
524
|
+
→ Exceeds threshold → Ban triggered
|
|
525
|
+
→ Different decay rates for each violation type
|
|
526
|
+
```
|
|
407
527
|
|
|
408
|
-
**Configuration
|
|
528
|
+
**Configuration Profiles:**
|
|
409
529
|
|
|
410
530
|
```ruby
|
|
411
|
-
#
|
|
531
|
+
# 🔥 STRICT - High-security environment (financial, healthcare)
|
|
412
532
|
Beskar.configure do |config|
|
|
413
|
-
config.waf =
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
533
|
+
config.waf[:enabled] = true
|
|
534
|
+
config.waf[:auto_block] = true
|
|
535
|
+
config.waf[:score_threshold] = 100 # Lower threshold = faster blocking
|
|
536
|
+
config.waf[:violation_window] = 12.hours # Longer memory
|
|
537
|
+
config.waf[:permanent_block_after] = 300 # Permanent ban at 300 cumulative score
|
|
538
|
+
config.waf[:block_durations] = [6.hours, 24.hours, 7.days, 30.days]
|
|
539
|
+
|
|
540
|
+
# Slower decay = violations persist longer
|
|
541
|
+
config.waf[:decay_rates] = {
|
|
542
|
+
critical: 720, # 12 hour half-life (very persistent)
|
|
543
|
+
high: 360, # 6 hour half-life
|
|
544
|
+
medium: 120, # 2 hour half-life
|
|
545
|
+
low: 30 # 30 minute half-life
|
|
421
546
|
}
|
|
547
|
+
|
|
548
|
+
# Exclude legitimate 404-prone paths
|
|
549
|
+
config.waf[:record_not_found_exclusions] = [
|
|
550
|
+
%r{/posts/.*}, %r{/articles/\d+}, %r{/public/.*}
|
|
551
|
+
]
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# ⚖️ BALANCED - Default production (recommended for most apps)
|
|
555
|
+
Beskar.configure do |config|
|
|
556
|
+
config.waf[:enabled] = true
|
|
557
|
+
config.waf[:auto_block] = true
|
|
558
|
+
config.waf[:score_threshold] = 150 # Default threshold
|
|
559
|
+
config.waf[:violation_window] = 6.hours # Standard window
|
|
560
|
+
config.waf[:permanent_block_after] = 500 # Permanent at 500 cumulative
|
|
561
|
+
config.waf[:decay_enabled] = true
|
|
562
|
+
# Uses default decay rates (critical: 360, high: 120, medium: 45, low: 15)
|
|
563
|
+
|
|
564
|
+
config.waf[:record_not_found_exclusions] = [
|
|
565
|
+
%r{/posts/.*}, %r{/products/[\\w-]+}
|
|
566
|
+
]
|
|
422
567
|
end
|
|
423
568
|
|
|
424
|
-
#
|
|
569
|
+
# 🧪 PERMISSIVE - High-traffic public site with many 404s
|
|
425
570
|
Beskar.configure do |config|
|
|
426
|
-
config.waf =
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
571
|
+
config.waf[:enabled] = true
|
|
572
|
+
config.waf[:auto_block] = true
|
|
573
|
+
config.waf[:score_threshold] = 200 # Higher tolerance
|
|
574
|
+
config.waf[:violation_window] = 3.hours # Shorter memory
|
|
575
|
+
config.waf[:permanent_block_after] = 800 # Rare permanent bans
|
|
576
|
+
|
|
577
|
+
# Faster decay = violations forgotten quickly
|
|
578
|
+
config.waf[:decay_rates] = {
|
|
579
|
+
critical: 180, # 3 hour half-life
|
|
580
|
+
high: 60, # 1 hour half-life
|
|
581
|
+
medium: 20, # 20 minute half-life
|
|
582
|
+
low: 5 # 5 minute half-life (very forgiving)
|
|
430
583
|
}
|
|
431
|
-
|
|
432
|
-
|
|
584
|
+
|
|
585
|
+
# Extensive exclusions for public content
|
|
586
|
+
config.waf[:record_not_found_exclusions] = [
|
|
587
|
+
%r{/posts/.*}, %r{/articles/.*}, %r{/tags/.*},
|
|
588
|
+
%r{/search/.*}, %r{/public/.*}, %r{/assets/.*}
|
|
589
|
+
]
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# 🔍 MONITOR ONLY - Testing/staging (recommended before going live)
|
|
593
|
+
Beskar.configure do |config|
|
|
594
|
+
config.monitor_only = true # Log violations but NEVER block
|
|
595
|
+
config.waf[:enabled] = true
|
|
596
|
+
config.waf[:create_security_events] = true
|
|
597
|
+
config.ip_whitelist = ["127.0.0.1", "::1"] # Whitelist localhost
|
|
433
598
|
end
|
|
434
599
|
```
|
|
435
600
|
|
|
436
601
|
**Blocking Behavior:**
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
- **
|
|
440
|
-
- **
|
|
441
|
-
- **
|
|
602
|
+
|
|
603
|
+
With **default settings** (score_threshold: 150):
|
|
604
|
+
- **Violations accumulate**: Each violation adds points based on severity
|
|
605
|
+
- **Score threshold reached**: IP automatically banned when cumulative score ≥ 150
|
|
606
|
+
- **Exponential decay**: Violations lose impact over time based on severity
|
|
607
|
+
- **Ban duration escalates**: Based on total score accumulated:
|
|
608
|
+
- 150-300 points → 1 hour ban
|
|
609
|
+
- 300-450 points → 6 hour ban
|
|
610
|
+
- 450-600 points → 24 hour ban
|
|
611
|
+
- 600+ points → 7 day ban
|
|
612
|
+
- 500+ cumulative score → **permanent ban**
|
|
613
|
+
|
|
614
|
+
**Key Advantages:**
|
|
615
|
+
- **Fewer false positives**: Low-severity violations (404s) decay quickly
|
|
616
|
+
- **Faster response to serious threats**: Critical violations persist longer
|
|
617
|
+
- **Adaptive blocking**: Mixed attack patterns properly weighted
|
|
618
|
+
- **Monitor mode compatible**: Set `config.monitor_only = true` to log without blocking
|
|
619
|
+
|
|
620
|
+
> **Production Tip:** Start with monitor mode for 24-48 hours to observe your traffic patterns, then adjust thresholds and exclusions before enabling blocking.
|
|
442
621
|
|
|
443
622
|
**Check WAF status:**
|
|
444
623
|
```ruby
|
|
445
|
-
#
|
|
624
|
+
# Get current risk score (with decay applied)
|
|
625
|
+
current_score = Beskar::Services::Waf.get_current_score(ip_address)
|
|
626
|
+
# => 145.3 (below threshold, not blocked)
|
|
627
|
+
|
|
628
|
+
# Get number of violations tracked
|
|
446
629
|
violation_count = Beskar::Services::Waf.get_violation_count(ip_address)
|
|
630
|
+
# => 3 (number of violations being tracked)
|
|
631
|
+
|
|
632
|
+
# Get detailed violation records
|
|
633
|
+
violations = Beskar::Services::Waf.get_violations(ip_address)
|
|
634
|
+
# => [{timestamp: ..., score: 95, severity: :critical, category: :config_files}, ...]
|
|
447
635
|
|
|
448
636
|
# Reset violations (admin action)
|
|
449
637
|
Beskar::Services::Waf.reset_violations(ip_address)
|
|
@@ -461,12 +649,15 @@ end
|
|
|
461
649
|
|
|
462
650
|
Beskar uses a hybrid cache + database blocking system that persists across application restarts.
|
|
463
651
|
|
|
464
|
-
**Automatic IP Banning:**
|
|
652
|
+
**Automatic IP Banning Thresholds:**
|
|
653
|
+
|
|
654
|
+
| Trigger | Threshold | Time Window | Ban Duration | Configurable |
|
|
655
|
+
|---------|-----------|-------------|--------------|--------------|
|
|
656
|
+
| **Failed Authentication** | 10 attempts | 1 hour | 1 hour (escalating) | Via rate_limiting config |
|
|
657
|
+
| **Rate Limit Violations** | 5 violations | 1 hour | 1 hour (escalating) | Via rate_limiting config |
|
|
658
|
+
| **WAF Violations** | 3 violations | 1 hour | 1 hour (escalating) | Via waf[:block_threshold] |
|
|
465
659
|
|
|
466
|
-
|
|
467
|
-
1. **Authentication abuse** - 10+ failed login attempts in 1 hour
|
|
468
|
-
2. **Rate limit violations** - 5+ rate limit violations in 1 hour
|
|
469
|
-
3. **WAF violations** - 3+ vulnerability scan attempts (configurable)
|
|
660
|
+
> **Note:** All ban durations escalate on repeat offenses: 1h → 6h → 24h → 7d → permanent
|
|
470
661
|
|
|
471
662
|
**Manual IP Management:**
|
|
472
663
|
|
|
@@ -574,10 +765,10 @@ class SecurityCleanupJob < ApplicationJob
|
|
|
574
765
|
# Remove expired bans from database
|
|
575
766
|
removed = Beskar::BannedIp.cleanup_expired!
|
|
576
767
|
Rails.logger.info "Cleaned up #{removed} expired IP bans"
|
|
577
|
-
|
|
768
|
+
|
|
578
769
|
# Archive old security events (optional)
|
|
579
770
|
Beskar::SecurityEvent.where('created_at < ?', 90.days.ago).delete_all
|
|
580
|
-
|
|
771
|
+
|
|
581
772
|
# Generate security report (example)
|
|
582
773
|
report = {
|
|
583
774
|
active_bans: Beskar::BannedIp.active.count,
|
|
@@ -587,7 +778,7 @@ class SecurityCleanupJob < ApplicationJob
|
|
|
587
778
|
created_at: 24.hours.ago..Time.current
|
|
588
779
|
).count
|
|
589
780
|
}
|
|
590
|
-
|
|
781
|
+
|
|
591
782
|
# Send to monitoring service
|
|
592
783
|
Rails.logger.info "Security Report: #{report}"
|
|
593
784
|
end
|
|
@@ -611,10 +802,7 @@ Every request passes through these security checks in order:
|
|
|
611
802
|
**Features:**
|
|
612
803
|
- **Early exit** - Banned IPs are blocked immediately for performance
|
|
613
804
|
- **Whitelist bypass** - Trusted IPs bypass all blocking but activity is logged
|
|
614
|
-
- **Auto-blocking** - Automatic IP banning
|
|
615
|
-
- 10+ failed authentication attempts (authentication abuse)
|
|
616
|
-
- 5+ rate limit violations in 1 hour (rate limit abuse)
|
|
617
|
-
- 3+ WAF violations (configurable, vulnerability scanning)
|
|
805
|
+
- **Auto-blocking** - See [Automatic IP Banning Thresholds](#ip-blocking-and-banning) section for details
|
|
618
806
|
- **Custom error pages** - Returns helpful 403/429 error responses
|
|
619
807
|
- **Response headers** - Adds `X-Beskar-Blocked` and `X-Beskar-Rate-Limited` headers
|
|
620
808
|
- **Graceful degradation** - Continues working if cache is unavailable
|
|
@@ -637,13 +825,18 @@ Security events are logged to the `beskar_security_events` table for analysis an
|
|
|
637
825
|
|
|
638
826
|
| Category | Severity | Example Patterns | Risk Score |
|
|
639
827
|
|----------|----------|------------------|------------|
|
|
640
|
-
| WordPress Scans | High | `/wp-admin`, `/wp-login.php`, `/
|
|
828
|
+
| WordPress Scans | High | `/wp-admin`, `/wp-login.php`, `/wp-content/*.php` | 80 |
|
|
829
|
+
| WordPress Static Files | Low | `/wp-content/*.css`, `/wp-content/*.jpg` | 30 |
|
|
641
830
|
| PHP Admin Panels | High | `/phpmyadmin`, `/admin.php`, `/phpinfo.php` | 80 |
|
|
642
831
|
| Config Files | **Critical** | `/.env`, `/.git`, `/database.yml`, `/config.php` | **95** |
|
|
643
832
|
| Path Traversal | **Critical** | `/../../../etc/passwd`, `%2e%2e/` | **95** |
|
|
644
833
|
| Framework Debug | Medium | `/rails/info/routes`, `/__debug__`, `/telescope` | 60 |
|
|
645
834
|
| CMS Detection | Medium | `/joomla`, `/drupal`, `/magento` | 60 |
|
|
646
835
|
| Common Exploits | **Critical** | `/shell.php`, `/c99.php`, `/webshell` | **95** |
|
|
836
|
+
| UnknownFormat Exception | Medium | `/users/1.exe`, `/api/data.bat` | 60 |
|
|
837
|
+
| IP Spoofing Exception | **Critical** | Conflicting IP headers | **95** |
|
|
838
|
+
| Invalid MIME Type Exception | Medium | `GET "../../../../etc/passwd{{"` | 60 |
|
|
839
|
+
| RecordNotFound Exception | Low | `/admin/users/999999` | 30 |
|
|
647
840
|
|
|
648
841
|
**Pattern matching is:**
|
|
649
842
|
- Case-insensitive
|
|
@@ -658,21 +851,17 @@ Security events are logged to the `beskar_security_events` table for analysis an
|
|
|
658
851
|
When first enabling WAF, use monitor-only mode to tune thresholds:
|
|
659
852
|
|
|
660
853
|
```ruby
|
|
661
|
-
config.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
create_security_events: true
|
|
665
|
-
}
|
|
854
|
+
config.monitor_only = true # Log but don't block
|
|
855
|
+
config.waf[:enabled] = true
|
|
856
|
+
config.waf[:create_security_events] = true
|
|
666
857
|
```
|
|
667
858
|
|
|
668
859
|
After reviewing logs for false positives, enable blocking:
|
|
669
860
|
|
|
670
861
|
```ruby
|
|
671
|
-
config.
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
auto_block: true
|
|
675
|
-
}
|
|
862
|
+
config.monitor_only = false
|
|
863
|
+
config.waf[:enabled] = true
|
|
864
|
+
config.waf[:auto_block] = true
|
|
676
865
|
```
|
|
677
866
|
|
|
678
867
|
### 2. Whitelist Carefully
|
|
@@ -773,7 +962,7 @@ config.ip_whitelist = ["user.ip.address.here"]
|
|
|
773
962
|
**Solution:** Enable monitor-only mode and review patterns:
|
|
774
963
|
|
|
775
964
|
```ruby
|
|
776
|
-
config.
|
|
965
|
+
config.monitor_only = true # This is a global setting, not WAF-specific
|
|
777
966
|
|
|
778
967
|
# Review what's being flagged
|
|
779
968
|
Beskar::SecurityEvent.where(event_type: 'waf_violation').last(20).each do |event|
|
|
@@ -853,7 +1042,7 @@ $ bin/rails test
|
|
|
853
1042
|
|
|
854
1043
|
## Contributing
|
|
855
1044
|
|
|
856
|
-
Bug reports and pull requests are welcome on GitHub at [https://github.com/prograis/beskar](https://github.com/prograils/beskar).
|
|
1045
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/prograis/beskar](https://github.com/prograils/beskar).
|
|
857
1046
|
|
|
858
1047
|
## License
|
|
859
1048
|
|
|
@@ -862,4 +1051,3 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
862
1051
|
## Code of Conduct
|
|
863
1052
|
|
|
864
1053
|
Just be nice to each other.
|
|
865
|
-
|