aiwaf 0.1.7.7__tar.gz → 0.1.8.1__tar.gz
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.
Potentially problematic release.
This version of aiwaf might be problematic. Click here for more details.
- {aiwaf-0.1.7.7/aiwaf.egg-info → aiwaf-0.1.8.1}/PKG-INFO +54 -27
- aiwaf-0.1.7.7/PKG-INFO → aiwaf-0.1.8.1/README.md +48 -42
- aiwaf-0.1.8.1/aiwaf/management/commands/add_ipexemption.py +22 -0
- aiwaf-0.1.8.1/aiwaf/management/commands/aiwaf_reset.py +84 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/middleware.py +48 -16
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/models.py +11 -1
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/trainer.py +6 -0
- aiwaf-0.1.7.7/README.md → aiwaf-0.1.8.1/aiwaf.egg-info/PKG-INFO +69 -26
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf.egg-info/SOURCES.txt +3 -0
- aiwaf-0.1.8.1/aiwaf.egg-info/requires.txt +5 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/pyproject.toml +8 -2
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/setup.py +1 -1
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/LICENSE +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/__init__.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/apps.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/blacklist_manager.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/management/__init__.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/management/commands/__init__.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/management/commands/detect_and_train.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/resources/model.pkl +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/storage.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/templatetags/__init__.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/templatetags/aiwaf_tags.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf/utils.py +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf.egg-info/dependency_links.txt +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/aiwaf.egg-info/top_level.txt +0 -0
- {aiwaf-0.1.7.7 → aiwaf-0.1.8.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiwaf
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8.1
|
|
4
4
|
Summary: AI-powered Web Application Firewall
|
|
5
5
|
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
6
|
Author: Aayush Gauba
|
|
@@ -9,6 +9,11 @@ License: MIT
|
|
|
9
9
|
Requires-Python: >=3.8
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
|
+
Requires-Dist: Django>=3.2
|
|
13
|
+
Requires-Dist: numpy>=1.21
|
|
14
|
+
Requires-Dist: pandas>=1.3
|
|
15
|
+
Requires-Dist: scikit-learn>=1.0
|
|
16
|
+
Requires-Dist: joblib>=1.1
|
|
12
17
|
Dynamic: author
|
|
13
18
|
Dynamic: home-page
|
|
14
19
|
Dynamic: license-file
|
|
@@ -72,19 +77,58 @@ aiwaf/
|
|
|
72
77
|
- **File‑Extension Probing Detection**
|
|
73
78
|
Tracks repeated 404s on common extensions (e.g. `.php`, `.asp`) and blocks IPs.
|
|
74
79
|
|
|
75
|
-
- **Honeypot
|
|
76
|
-
|
|
80
|
+
- **Timing-Based Honeypot**
|
|
81
|
+
Tracks GET→POST timing patterns. Blocks IPs that:
|
|
82
|
+
- POST directly without a preceding GET request
|
|
83
|
+
- Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
|
|
77
84
|
|
|
78
85
|
- **UUID Tampering Protection**
|
|
79
86
|
Blocks guessed or invalid UUIDs that don’t resolve to real models.
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
|
|
89
|
+
**Exempt Path & IP Awareness**
|
|
90
|
+
|
|
91
|
+
**Exempt Paths:**
|
|
92
|
+
Set `AIWAF_EXEMPT_PATHS` in your Django `settings.py` (not in your code). Fully respects this setting across all modules — exempt paths are:
|
|
83
93
|
- Skipped from keyword learning
|
|
84
94
|
- Immune to AI blocking
|
|
85
95
|
- Ignored in log training
|
|
86
96
|
- Cleaned from `DynamicKeyword` model automatically
|
|
87
97
|
|
|
98
|
+
**Exempt IPs:**
|
|
99
|
+
You can exempt specific IP addresses from all blocking and blacklisting logic. Exempted IPs will:
|
|
100
|
+
- Never be added to the blacklist (even if they trigger rules)
|
|
101
|
+
- Be automatically removed from the blacklist during retraining
|
|
102
|
+
- Bypass all block/deny logic in middleware
|
|
103
|
+
|
|
104
|
+
### Managing Exempt IPs
|
|
105
|
+
|
|
106
|
+
Add an IP to the exemption list using the management command:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python manage.py add_ipexemption <ip-address> --reason "optional reason"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Resetting AI-WAF
|
|
113
|
+
|
|
114
|
+
Clear all blacklist and exemption entries:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Clear everything (with confirmation prompt)
|
|
118
|
+
python manage.py aiwaf_reset
|
|
119
|
+
|
|
120
|
+
# Clear everything without confirmation
|
|
121
|
+
python manage.py aiwaf_reset --confirm
|
|
122
|
+
|
|
123
|
+
# Clear only blacklist entries
|
|
124
|
+
python manage.py aiwaf_reset --blacklist-only
|
|
125
|
+
|
|
126
|
+
# Clear only exemption entries
|
|
127
|
+
python manage.py aiwaf_reset --exemptions-only
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
|
|
131
|
+
|
|
88
132
|
- **Daily Retraining**
|
|
89
133
|
Reads rotated logs, auto‑blocks 404 floods, retrains the IsolationForest, updates `model.pkl`, and evolves the keyword DB.
|
|
90
134
|
|
|
@@ -119,13 +163,13 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
|
|
|
119
163
|
|
|
120
164
|
```python
|
|
121
165
|
AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
|
|
122
|
-
|
|
166
|
+
AIWAF_MIN_FORM_TIME = 1.0 # minimum seconds between GET and POST
|
|
123
167
|
AIWAF_RATE_WINDOW = 10 # seconds
|
|
124
168
|
AIWAF_RATE_MAX = 20 # max requests per window
|
|
125
169
|
AIWAF_RATE_FLOOD = 10 # flood threshold
|
|
126
170
|
AIWAF_WINDOW_SECONDS = 60 # anomaly detection window
|
|
127
171
|
AIWAF_FILE_EXTENSIONS = [".php", ".asp", ".jsp"]
|
|
128
|
-
AIWAF_EXEMPT_PATHS
|
|
172
|
+
AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
|
|
129
173
|
"/favicon.ico",
|
|
130
174
|
"/robots.txt",
|
|
131
175
|
"/static/",
|
|
@@ -147,7 +191,7 @@ MIDDLEWARE = [
|
|
|
147
191
|
"aiwaf.middleware.IPAndKeywordBlockMiddleware",
|
|
148
192
|
"aiwaf.middleware.RateLimitMiddleware",
|
|
149
193
|
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
150
|
-
"aiwaf.middleware.
|
|
194
|
+
"aiwaf.middleware.HoneypotTimingMiddleware",
|
|
151
195
|
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
152
196
|
# ... other middleware ...
|
|
153
197
|
]
|
|
@@ -155,24 +199,7 @@ MIDDLEWARE = [
|
|
|
155
199
|
|
|
156
200
|
---
|
|
157
201
|
|
|
158
|
-
##
|
|
159
|
-
|
|
160
|
-
```django
|
|
161
|
-
{% load aiwaf_tags %}
|
|
162
|
-
|
|
163
|
-
<form method="post">
|
|
164
|
-
{% csrf_token %}
|
|
165
|
-
{% honeypot_field %}
|
|
166
|
-
<!-- your real fields -->
|
|
167
|
-
</form>
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
> Renders a hidden `<input name="hp_field" style="display:none">`.
|
|
171
|
-
> Any non‑empty submission → IP blacklisted.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## 🔁 Running Detection & Training
|
|
202
|
+
## Running Detection & Training
|
|
176
203
|
|
|
177
204
|
```bash
|
|
178
205
|
python manage.py detect_and_train
|
|
@@ -195,7 +222,7 @@ python manage.py detect_and_train
|
|
|
195
222
|
| IPAndKeywordBlockMiddleware | Blocks requests from known blacklisted IPs and Keywords |
|
|
196
223
|
| RateLimitMiddleware | Enforces burst & flood thresholds |
|
|
197
224
|
| AIAnomalyMiddleware | ML‑driven behavior analysis + block on anomaly |
|
|
198
|
-
|
|
|
225
|
+
| HoneypotTimingMiddleware | Detects bots via GET→POST timing analysis |
|
|
199
226
|
| UUIDTamperMiddleware | Blocks guessed/nonexistent UUIDs across all models in an app |
|
|
200
227
|
|
|
201
228
|
---
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: aiwaf
|
|
3
|
-
Version: 0.1.7.7
|
|
4
|
-
Summary: AI-powered Web Application Firewall
|
|
5
|
-
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
|
-
Author: Aayush Gauba
|
|
7
|
-
Author-email: Aayush Gauba <gauba.aayush@gmail.com>
|
|
8
|
-
License: MIT
|
|
9
|
-
Requires-Python: >=3.8
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Dynamic: author
|
|
13
|
-
Dynamic: home-page
|
|
14
|
-
Dynamic: license-file
|
|
15
|
-
Dynamic: requires-python
|
|
16
|
-
|
|
17
1
|
|
|
18
2
|
# AI‑WAF
|
|
19
3
|
|
|
@@ -72,19 +56,58 @@ aiwaf/
|
|
|
72
56
|
- **File‑Extension Probing Detection**
|
|
73
57
|
Tracks repeated 404s on common extensions (e.g. `.php`, `.asp`) and blocks IPs.
|
|
74
58
|
|
|
75
|
-
- **Honeypot
|
|
76
|
-
|
|
59
|
+
- **Timing-Based Honeypot**
|
|
60
|
+
Tracks GET→POST timing patterns. Blocks IPs that:
|
|
61
|
+
- POST directly without a preceding GET request
|
|
62
|
+
- Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
|
|
77
63
|
|
|
78
64
|
- **UUID Tampering Protection**
|
|
79
65
|
Blocks guessed or invalid UUIDs that don’t resolve to real models.
|
|
80
66
|
|
|
81
|
-
|
|
82
|
-
|
|
67
|
+
|
|
68
|
+
**Exempt Path & IP Awareness**
|
|
69
|
+
|
|
70
|
+
**Exempt Paths:**
|
|
71
|
+
Set `AIWAF_EXEMPT_PATHS` in your Django `settings.py` (not in your code). Fully respects this setting across all modules — exempt paths are:
|
|
83
72
|
- Skipped from keyword learning
|
|
84
73
|
- Immune to AI blocking
|
|
85
74
|
- Ignored in log training
|
|
86
75
|
- Cleaned from `DynamicKeyword` model automatically
|
|
87
76
|
|
|
77
|
+
**Exempt IPs:**
|
|
78
|
+
You can exempt specific IP addresses from all blocking and blacklisting logic. Exempted IPs will:
|
|
79
|
+
- Never be added to the blacklist (even if they trigger rules)
|
|
80
|
+
- Be automatically removed from the blacklist during retraining
|
|
81
|
+
- Bypass all block/deny logic in middleware
|
|
82
|
+
|
|
83
|
+
### Managing Exempt IPs
|
|
84
|
+
|
|
85
|
+
Add an IP to the exemption list using the management command:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python manage.py add_ipexemption <ip-address> --reason "optional reason"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Resetting AI-WAF
|
|
92
|
+
|
|
93
|
+
Clear all blacklist and exemption entries:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Clear everything (with confirmation prompt)
|
|
97
|
+
python manage.py aiwaf_reset
|
|
98
|
+
|
|
99
|
+
# Clear everything without confirmation
|
|
100
|
+
python manage.py aiwaf_reset --confirm
|
|
101
|
+
|
|
102
|
+
# Clear only blacklist entries
|
|
103
|
+
python manage.py aiwaf_reset --blacklist-only
|
|
104
|
+
|
|
105
|
+
# Clear only exemption entries
|
|
106
|
+
python manage.py aiwaf_reset --exemptions-only
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
|
|
110
|
+
|
|
88
111
|
- **Daily Retraining**
|
|
89
112
|
Reads rotated logs, auto‑blocks 404 floods, retrains the IsolationForest, updates `model.pkl`, and evolves the keyword DB.
|
|
90
113
|
|
|
@@ -119,13 +142,13 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
|
|
|
119
142
|
|
|
120
143
|
```python
|
|
121
144
|
AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
|
|
122
|
-
|
|
145
|
+
AIWAF_MIN_FORM_TIME = 1.0 # minimum seconds between GET and POST
|
|
123
146
|
AIWAF_RATE_WINDOW = 10 # seconds
|
|
124
147
|
AIWAF_RATE_MAX = 20 # max requests per window
|
|
125
148
|
AIWAF_RATE_FLOOD = 10 # flood threshold
|
|
126
149
|
AIWAF_WINDOW_SECONDS = 60 # anomaly detection window
|
|
127
150
|
AIWAF_FILE_EXTENSIONS = [".php", ".asp", ".jsp"]
|
|
128
|
-
AIWAF_EXEMPT_PATHS
|
|
151
|
+
AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
|
|
129
152
|
"/favicon.ico",
|
|
130
153
|
"/robots.txt",
|
|
131
154
|
"/static/",
|
|
@@ -147,7 +170,7 @@ MIDDLEWARE = [
|
|
|
147
170
|
"aiwaf.middleware.IPAndKeywordBlockMiddleware",
|
|
148
171
|
"aiwaf.middleware.RateLimitMiddleware",
|
|
149
172
|
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
150
|
-
"aiwaf.middleware.
|
|
173
|
+
"aiwaf.middleware.HoneypotTimingMiddleware",
|
|
151
174
|
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
152
175
|
# ... other middleware ...
|
|
153
176
|
]
|
|
@@ -155,24 +178,7 @@ MIDDLEWARE = [
|
|
|
155
178
|
|
|
156
179
|
---
|
|
157
180
|
|
|
158
|
-
##
|
|
159
|
-
|
|
160
|
-
```django
|
|
161
|
-
{% load aiwaf_tags %}
|
|
162
|
-
|
|
163
|
-
<form method="post">
|
|
164
|
-
{% csrf_token %}
|
|
165
|
-
{% honeypot_field %}
|
|
166
|
-
<!-- your real fields -->
|
|
167
|
-
</form>
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
> Renders a hidden `<input name="hp_field" style="display:none">`.
|
|
171
|
-
> Any non‑empty submission → IP blacklisted.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## 🔁 Running Detection & Training
|
|
181
|
+
## Running Detection & Training
|
|
176
182
|
|
|
177
183
|
```bash
|
|
178
184
|
python manage.py detect_and_train
|
|
@@ -195,7 +201,7 @@ python manage.py detect_and_train
|
|
|
195
201
|
| IPAndKeywordBlockMiddleware | Blocks requests from known blacklisted IPs and Keywords |
|
|
196
202
|
| RateLimitMiddleware | Enforces burst & flood thresholds |
|
|
197
203
|
| AIAnomalyMiddleware | ML‑driven behavior analysis + block on anomaly |
|
|
198
|
-
|
|
|
204
|
+
| HoneypotTimingMiddleware | Detects bots via GET→POST timing analysis |
|
|
199
205
|
| UUIDTamperMiddleware | Blocks guessed/nonexistent UUIDs across all models in an app |
|
|
200
206
|
|
|
201
207
|
---
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
2
|
+
from aiwaf.models import IPExemption
|
|
3
|
+
|
|
4
|
+
class Command(BaseCommand):
|
|
5
|
+
help = 'Add an IP address to the IPExemption list (prevents blacklisting)'
|
|
6
|
+
|
|
7
|
+
def add_arguments(self, parser):
|
|
8
|
+
parser.add_argument('ip', type=str, help='IP address to exempt')
|
|
9
|
+
parser.add_argument('--reason', type=str, default='', help='Reason for exemption (optional)')
|
|
10
|
+
|
|
11
|
+
def handle(self, *args, **options):
|
|
12
|
+
ip = options['ip']
|
|
13
|
+
reason = options['reason']
|
|
14
|
+
obj, created = IPExemption.objects.get_or_create(ip_address=ip, defaults={'reason': reason})
|
|
15
|
+
if not created:
|
|
16
|
+
self.stdout.write(self.style.WARNING(f'IP {ip} is already exempted.'))
|
|
17
|
+
else:
|
|
18
|
+
self.stdout.write(self.style.SUCCESS(f'IP {ip} added to exemption list.'))
|
|
19
|
+
if reason:
|
|
20
|
+
obj.reason = reason
|
|
21
|
+
obj.save()
|
|
22
|
+
self.stdout.write(self.style.SUCCESS(f'Reason set to: {reason}'))
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
from aiwaf.models import BlacklistEntry, IPExemption
|
|
3
|
+
|
|
4
|
+
class Command(BaseCommand):
|
|
5
|
+
help = 'Reset AI-WAF by clearing all blacklist and exemption (whitelist) entries'
|
|
6
|
+
|
|
7
|
+
def add_arguments(self, parser):
|
|
8
|
+
parser.add_argument(
|
|
9
|
+
'--blacklist-only',
|
|
10
|
+
action='store_true',
|
|
11
|
+
help='Clear only blacklist entries, keep exemptions'
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
'--exemptions-only',
|
|
15
|
+
action='store_true',
|
|
16
|
+
help='Clear only exemption entries, keep blacklist'
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
'--confirm',
|
|
20
|
+
action='store_true',
|
|
21
|
+
help='Skip confirmation prompt'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def handle(self, *args, **options):
|
|
25
|
+
blacklist_only = options['blacklist_only']
|
|
26
|
+
exemptions_only = options['exemptions_only']
|
|
27
|
+
confirm = options['confirm']
|
|
28
|
+
|
|
29
|
+
# Count current entries
|
|
30
|
+
blacklist_count = BlacklistEntry.objects.count()
|
|
31
|
+
exemption_count = IPExemption.objects.count()
|
|
32
|
+
|
|
33
|
+
if blacklist_only and exemptions_only:
|
|
34
|
+
self.stdout.write(self.style.ERROR('Cannot use both --blacklist-only and --exemptions-only flags'))
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
# Determine what to clear
|
|
38
|
+
if blacklist_only:
|
|
39
|
+
action = f"Clear {blacklist_count} blacklist entries"
|
|
40
|
+
clear_blacklist = True
|
|
41
|
+
clear_exemptions = False
|
|
42
|
+
elif exemptions_only:
|
|
43
|
+
action = f"Clear {exemption_count} exemption entries"
|
|
44
|
+
clear_blacklist = False
|
|
45
|
+
clear_exemptions = True
|
|
46
|
+
else:
|
|
47
|
+
action = f"Clear {blacklist_count} blacklist entries and {exemption_count} exemption entries"
|
|
48
|
+
clear_blacklist = True
|
|
49
|
+
clear_exemptions = True
|
|
50
|
+
|
|
51
|
+
# Show what will be cleared
|
|
52
|
+
self.stdout.write(f"AI-WAF Reset: {action}")
|
|
53
|
+
|
|
54
|
+
if not confirm:
|
|
55
|
+
response = input("Are you sure you want to proceed? [y/N]: ")
|
|
56
|
+
if response.lower() not in ['y', 'yes']:
|
|
57
|
+
self.stdout.write(self.style.WARNING('Operation cancelled'))
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# Perform the reset
|
|
61
|
+
deleted_counts = {'blacklist': 0, 'exemptions': 0}
|
|
62
|
+
|
|
63
|
+
if clear_blacklist:
|
|
64
|
+
deleted_counts['blacklist'], _ = BlacklistEntry.objects.all().delete()
|
|
65
|
+
|
|
66
|
+
if clear_exemptions:
|
|
67
|
+
deleted_counts['exemptions'], _ = IPExemption.objects.all().delete()
|
|
68
|
+
|
|
69
|
+
# Report results
|
|
70
|
+
if clear_blacklist and clear_exemptions:
|
|
71
|
+
self.stdout.write(
|
|
72
|
+
self.style.SUCCESS(
|
|
73
|
+
f"✅ Reset complete: Deleted {deleted_counts['blacklist']} blacklist entries "
|
|
74
|
+
f"and {deleted_counts['exemptions']} exemption entries"
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
elif clear_blacklist:
|
|
78
|
+
self.stdout.write(
|
|
79
|
+
self.style.SUCCESS(f"✅ Blacklist cleared: Deleted {deleted_counts['blacklist']} entries")
|
|
80
|
+
)
|
|
81
|
+
elif clear_exemptions:
|
|
82
|
+
self.stdout.write(
|
|
83
|
+
self.style.SUCCESS(f"✅ Exemptions cleared: Deleted {deleted_counts['exemptions']} entries")
|
|
84
|
+
)
|
|
@@ -16,7 +16,9 @@ from django.apps import apps
|
|
|
16
16
|
from django.urls import get_resolver
|
|
17
17
|
from .trainer import STATIC_KW, STATUS_IDX, is_exempt_path, path_exists_in_django
|
|
18
18
|
from .blacklist_manager import BlacklistManager
|
|
19
|
-
from .models import DynamicKeyword
|
|
19
|
+
from .models import DynamicKeyword, IPExemption
|
|
20
|
+
def is_ip_exempted(ip):
|
|
21
|
+
return IPExemption.objects.filter(ip_address=ip).exists()
|
|
20
22
|
|
|
21
23
|
def is_exempt_path(path):
|
|
22
24
|
path = path.lower()
|
|
@@ -77,6 +79,8 @@ class IPAndKeywordBlockMiddleware:
|
|
|
77
79
|
return self.get_response(request)
|
|
78
80
|
ip = get_ip(request)
|
|
79
81
|
path = raw_path.lstrip("/")
|
|
82
|
+
if is_ip_exempted(ip):
|
|
83
|
+
return self.get_response(request)
|
|
80
84
|
if BlacklistManager.is_blocked(ip):
|
|
81
85
|
return JsonResponse({"error": "blocked"}, status=403)
|
|
82
86
|
segments = [seg for seg in re.split(r"\W+", path) if len(seg) > 3]
|
|
@@ -95,8 +99,9 @@ class IPAndKeywordBlockMiddleware:
|
|
|
95
99
|
}
|
|
96
100
|
for seg in segments:
|
|
97
101
|
if seg in suspicious_kw:
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
if not is_ip_exempted(ip):
|
|
103
|
+
BlacklistManager.block(ip, f"Keyword block: {seg}")
|
|
104
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
100
105
|
return self.get_response(request)
|
|
101
106
|
|
|
102
107
|
|
|
@@ -120,8 +125,9 @@ class RateLimitMiddleware:
|
|
|
120
125
|
timestamps.append(now)
|
|
121
126
|
cache.set(key, timestamps, timeout=self.WINDOW)
|
|
122
127
|
if len(timestamps) > self.FLOOD:
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
if not is_ip_exempted(ip):
|
|
129
|
+
BlacklistManager.block(ip, "Flood pattern")
|
|
130
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
125
131
|
if len(timestamps) > self.MAX:
|
|
126
132
|
return JsonResponse({"error": "too_many_requests"}, status=429)
|
|
127
133
|
return self.get_response(request)
|
|
@@ -141,6 +147,8 @@ class AIAnomalyMiddleware(MiddlewareMixin):
|
|
|
141
147
|
return None
|
|
142
148
|
request._start_time = time.time()
|
|
143
149
|
ip = get_ip(request)
|
|
150
|
+
if is_ip_exempted(ip):
|
|
151
|
+
return None
|
|
144
152
|
if BlacklistManager.is_blocked(ip):
|
|
145
153
|
return JsonResponse({"error": "blocked"}, status=403)
|
|
146
154
|
return None
|
|
@@ -166,8 +174,9 @@ class AIAnomalyMiddleware(MiddlewareMixin):
|
|
|
166
174
|
feats = [path_len, kw_hits, resp_time, status_idx, burst_count, total_404]
|
|
167
175
|
X = np.array(feats, dtype=float).reshape(1, -1)
|
|
168
176
|
if self.model.predict(X)[0] == -1:
|
|
169
|
-
|
|
170
|
-
|
|
177
|
+
if not is_ip_exempted(ip):
|
|
178
|
+
BlacklistManager.block(ip, "AI anomaly")
|
|
179
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
171
180
|
|
|
172
181
|
data.append((now, request.path, response.status_code, resp_time))
|
|
173
182
|
data = [d for d in data if now - d[0] < self.WINDOW]
|
|
@@ -180,15 +189,37 @@ class AIAnomalyMiddleware(MiddlewareMixin):
|
|
|
180
189
|
return response
|
|
181
190
|
|
|
182
191
|
|
|
183
|
-
class
|
|
184
|
-
|
|
192
|
+
class HoneypotTimingMiddleware(MiddlewareMixin):
|
|
193
|
+
MIN_FORM_TIME = getattr(settings, "AIWAF_MIN_FORM_TIME", 1.0) # seconds
|
|
194
|
+
|
|
195
|
+
def process_request(self, request):
|
|
185
196
|
if is_exempt_path(request.path):
|
|
186
197
|
return None
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
|
|
199
|
+
ip = get_ip(request)
|
|
200
|
+
if is_ip_exempted(ip):
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
if request.method == "GET":
|
|
204
|
+
# Store timestamp for this IP's GET request
|
|
205
|
+
cache.set(f"honeypot_get:{ip}", time.time(), timeout=300) # 5 min timeout
|
|
206
|
+
|
|
207
|
+
elif request.method == "POST":
|
|
208
|
+
# Check if there was a preceding GET request
|
|
209
|
+
get_time = cache.get(f"honeypot_get:{ip}")
|
|
210
|
+
|
|
211
|
+
if get_time is None:
|
|
212
|
+
# No GET request - likely bot posting directly
|
|
213
|
+
BlacklistManager.block(ip, "Direct POST without GET")
|
|
214
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
215
|
+
|
|
216
|
+
# Check timing
|
|
217
|
+
time_diff = time.time() - get_time
|
|
218
|
+
if time_diff < self.MIN_FORM_TIME:
|
|
219
|
+
# Posted too quickly - likely bot
|
|
220
|
+
BlacklistManager.block(ip, f"Form submitted too quickly ({time_diff:.2f}s)")
|
|
221
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
222
|
+
|
|
192
223
|
return None
|
|
193
224
|
|
|
194
225
|
|
|
@@ -211,5 +242,6 @@ class UUIDTamperMiddleware(MiddlewareMixin):
|
|
|
211
242
|
except (ValueError, TypeError):
|
|
212
243
|
continue
|
|
213
244
|
|
|
214
|
-
|
|
215
|
-
|
|
245
|
+
if not is_ip_exempted(ip):
|
|
246
|
+
BlacklistManager.block(ip, "UUID tampering")
|
|
247
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
@@ -33,4 +33,14 @@ class DynamicKeyword(models.Model):
|
|
|
33
33
|
last_updated = models.DateTimeField(auto_now=True)
|
|
34
34
|
|
|
35
35
|
class Meta:
|
|
36
|
-
ordering = ['-count']
|
|
36
|
+
ordering = ['-count']
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Model to store IP addresses that are exempt from blacklisting
|
|
40
|
+
class IPExemption(models.Model):
|
|
41
|
+
ip_address = models.GenericIPAddressField(unique=True, db_index=True)
|
|
42
|
+
reason = models.CharField(max_length=100, blank=True, default="")
|
|
43
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
44
|
+
|
|
45
|
+
def __str__(self):
|
|
46
|
+
return f"{self.ip_address} (Exempted: {self.reason})"
|
|
@@ -26,8 +26,10 @@ _LOG_RX = re.compile(
|
|
|
26
26
|
r'(\d{3}).*?"(.*?)" "(.*?)".*?response-time=(\d+\.\d+)'
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
+
|
|
29
30
|
BlacklistEntry = apps.get_model("aiwaf", "BlacklistEntry")
|
|
30
31
|
DynamicKeyword = apps.get_model("aiwaf", "DynamicKeyword")
|
|
32
|
+
IPExemption = apps.get_model("aiwaf", "IPExemption")
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def is_exempt_path(path: str) -> bool:
|
|
@@ -103,6 +105,10 @@ def _parse(line: str) -> dict | None:
|
|
|
103
105
|
|
|
104
106
|
def train() -> None:
|
|
105
107
|
remove_exempt_keywords()
|
|
108
|
+
# Remove any IPs in IPExemption from the blacklist
|
|
109
|
+
exempt_ips = set(IPExemption.objects.values_list("ip_address", flat=True))
|
|
110
|
+
if exempt_ips:
|
|
111
|
+
BlacklistEntry.objects.filter(ip_address__in=exempt_ips).delete()
|
|
106
112
|
raw_lines = _read_all_logs()
|
|
107
113
|
if not raw_lines:
|
|
108
114
|
print("No log lines found – check AIWAF_ACCESS_LOG setting.")
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiwaf
|
|
3
|
+
Version: 0.1.8.1
|
|
4
|
+
Summary: AI-powered Web Application Firewall
|
|
5
|
+
Home-page: https://github.com/aayushgauba/aiwaf
|
|
6
|
+
Author: Aayush Gauba
|
|
7
|
+
Author-email: Aayush Gauba <gauba.aayush@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: Django>=3.2
|
|
13
|
+
Requires-Dist: numpy>=1.21
|
|
14
|
+
Requires-Dist: pandas>=1.3
|
|
15
|
+
Requires-Dist: scikit-learn>=1.0
|
|
16
|
+
Requires-Dist: joblib>=1.1
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
Dynamic: requires-python
|
|
21
|
+
|
|
1
22
|
|
|
2
23
|
# AI‑WAF
|
|
3
24
|
|
|
@@ -56,19 +77,58 @@ aiwaf/
|
|
|
56
77
|
- **File‑Extension Probing Detection**
|
|
57
78
|
Tracks repeated 404s on common extensions (e.g. `.php`, `.asp`) and blocks IPs.
|
|
58
79
|
|
|
59
|
-
- **Honeypot
|
|
60
|
-
|
|
80
|
+
- **Timing-Based Honeypot**
|
|
81
|
+
Tracks GET→POST timing patterns. Blocks IPs that:
|
|
82
|
+
- POST directly without a preceding GET request
|
|
83
|
+
- Submit forms faster than `AIWAF_MIN_FORM_TIME` seconds (default: 1 second)
|
|
61
84
|
|
|
62
85
|
- **UUID Tampering Protection**
|
|
63
86
|
Blocks guessed or invalid UUIDs that don’t resolve to real models.
|
|
64
87
|
|
|
65
|
-
|
|
66
|
-
|
|
88
|
+
|
|
89
|
+
**Exempt Path & IP Awareness**
|
|
90
|
+
|
|
91
|
+
**Exempt Paths:**
|
|
92
|
+
Set `AIWAF_EXEMPT_PATHS` in your Django `settings.py` (not in your code). Fully respects this setting across all modules — exempt paths are:
|
|
67
93
|
- Skipped from keyword learning
|
|
68
94
|
- Immune to AI blocking
|
|
69
95
|
- Ignored in log training
|
|
70
96
|
- Cleaned from `DynamicKeyword` model automatically
|
|
71
97
|
|
|
98
|
+
**Exempt IPs:**
|
|
99
|
+
You can exempt specific IP addresses from all blocking and blacklisting logic. Exempted IPs will:
|
|
100
|
+
- Never be added to the blacklist (even if they trigger rules)
|
|
101
|
+
- Be automatically removed from the blacklist during retraining
|
|
102
|
+
- Bypass all block/deny logic in middleware
|
|
103
|
+
|
|
104
|
+
### Managing Exempt IPs
|
|
105
|
+
|
|
106
|
+
Add an IP to the exemption list using the management command:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python manage.py add_ipexemption <ip-address> --reason "optional reason"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Resetting AI-WAF
|
|
113
|
+
|
|
114
|
+
Clear all blacklist and exemption entries:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Clear everything (with confirmation prompt)
|
|
118
|
+
python manage.py aiwaf_reset
|
|
119
|
+
|
|
120
|
+
# Clear everything without confirmation
|
|
121
|
+
python manage.py aiwaf_reset --confirm
|
|
122
|
+
|
|
123
|
+
# Clear only blacklist entries
|
|
124
|
+
python manage.py aiwaf_reset --blacklist-only
|
|
125
|
+
|
|
126
|
+
# Clear only exemption entries
|
|
127
|
+
python manage.py aiwaf_reset --exemptions-only
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This will ensure the IP is never blocked by AI‑WAF. You can also manage exemptions via the Django admin interface.
|
|
131
|
+
|
|
72
132
|
- **Daily Retraining**
|
|
73
133
|
Reads rotated logs, auto‑blocks 404 floods, retrains the IsolationForest, updates `model.pkl`, and evolves the keyword DB.
|
|
74
134
|
|
|
@@ -103,13 +163,13 @@ AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
|
|
|
103
163
|
|
|
104
164
|
```python
|
|
105
165
|
AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
|
|
106
|
-
|
|
166
|
+
AIWAF_MIN_FORM_TIME = 1.0 # minimum seconds between GET and POST
|
|
107
167
|
AIWAF_RATE_WINDOW = 10 # seconds
|
|
108
168
|
AIWAF_RATE_MAX = 20 # max requests per window
|
|
109
169
|
AIWAF_RATE_FLOOD = 10 # flood threshold
|
|
110
170
|
AIWAF_WINDOW_SECONDS = 60 # anomaly detection window
|
|
111
171
|
AIWAF_FILE_EXTENSIONS = [".php", ".asp", ".jsp"]
|
|
112
|
-
AIWAF_EXEMPT_PATHS
|
|
172
|
+
AIWAF_EXEMPT_PATHS = [ # optional but highly recommended
|
|
113
173
|
"/favicon.ico",
|
|
114
174
|
"/robots.txt",
|
|
115
175
|
"/static/",
|
|
@@ -131,7 +191,7 @@ MIDDLEWARE = [
|
|
|
131
191
|
"aiwaf.middleware.IPAndKeywordBlockMiddleware",
|
|
132
192
|
"aiwaf.middleware.RateLimitMiddleware",
|
|
133
193
|
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
134
|
-
"aiwaf.middleware.
|
|
194
|
+
"aiwaf.middleware.HoneypotTimingMiddleware",
|
|
135
195
|
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
136
196
|
# ... other middleware ...
|
|
137
197
|
]
|
|
@@ -139,24 +199,7 @@ MIDDLEWARE = [
|
|
|
139
199
|
|
|
140
200
|
---
|
|
141
201
|
|
|
142
|
-
##
|
|
143
|
-
|
|
144
|
-
```django
|
|
145
|
-
{% load aiwaf_tags %}
|
|
146
|
-
|
|
147
|
-
<form method="post">
|
|
148
|
-
{% csrf_token %}
|
|
149
|
-
{% honeypot_field %}
|
|
150
|
-
<!-- your real fields -->
|
|
151
|
-
</form>
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
> Renders a hidden `<input name="hp_field" style="display:none">`.
|
|
155
|
-
> Any non‑empty submission → IP blacklisted.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## 🔁 Running Detection & Training
|
|
202
|
+
## Running Detection & Training
|
|
160
203
|
|
|
161
204
|
```bash
|
|
162
205
|
python manage.py detect_and_train
|
|
@@ -179,7 +222,7 @@ python manage.py detect_and_train
|
|
|
179
222
|
| IPAndKeywordBlockMiddleware | Blocks requests from known blacklisted IPs and Keywords |
|
|
180
223
|
| RateLimitMiddleware | Enforces burst & flood thresholds |
|
|
181
224
|
| AIAnomalyMiddleware | ML‑driven behavior analysis + block on anomaly |
|
|
182
|
-
|
|
|
225
|
+
| HoneypotTimingMiddleware | Detects bots via GET→POST timing analysis |
|
|
183
226
|
| UUIDTamperMiddleware | Blocks guessed/nonexistent UUIDs across all models in an app |
|
|
184
227
|
|
|
185
228
|
---
|
|
@@ -13,9 +13,12 @@ aiwaf/utils.py
|
|
|
13
13
|
aiwaf.egg-info/PKG-INFO
|
|
14
14
|
aiwaf.egg-info/SOURCES.txt
|
|
15
15
|
aiwaf.egg-info/dependency_links.txt
|
|
16
|
+
aiwaf.egg-info/requires.txt
|
|
16
17
|
aiwaf.egg-info/top_level.txt
|
|
17
18
|
aiwaf/management/__init__.py
|
|
18
19
|
aiwaf/management/commands/__init__.py
|
|
20
|
+
aiwaf/management/commands/add_ipexemption.py
|
|
21
|
+
aiwaf/management/commands/aiwaf_reset.py
|
|
19
22
|
aiwaf/management/commands/detect_and_train.py
|
|
20
23
|
aiwaf/resources/model.pkl
|
|
21
24
|
aiwaf/templatetags/__init__.py
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aiwaf"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.8.1"
|
|
4
4
|
description = "AI-powered Web Application Firewall"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.8"
|
|
7
7
|
license = {text = "MIT"}
|
|
8
8
|
authors = [{ name = "Aayush Gauba", email = "gauba.aayush@gmail.com" }]
|
|
9
|
-
dependencies = [
|
|
9
|
+
dependencies = [
|
|
10
|
+
"Django>=3.2",
|
|
11
|
+
"numpy>=1.21",
|
|
12
|
+
"pandas>=1.3",
|
|
13
|
+
"scikit-learn>=1.0",
|
|
14
|
+
"joblib>=1.1"
|
|
15
|
+
]
|
|
@@ -9,7 +9,7 @@ long_description = (HERE / "README.md").read_text(encoding="utf-8")
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="aiwaf",
|
|
12
|
-
version="0.1.
|
|
12
|
+
version="0.1.8.1",
|
|
13
13
|
description="AI‑driven, self‑learning Web Application Firewall for Django",
|
|
14
14
|
long_description=long_description,
|
|
15
15
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|