aiwaf 0.1.2__tar.gz → 0.1.5__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.5/LICENSE +21 -0
- aiwaf-0.1.5/PKG-INFO +195 -0
- aiwaf-0.1.5/README.md +179 -0
- aiwaf-0.1.5/aiwaf/middleware.py +184 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/models.py +8 -0
- aiwaf-0.1.5/aiwaf/trainer.py +151 -0
- aiwaf-0.1.5/aiwaf.egg-info/PKG-INFO +195 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf.egg-info/SOURCES.txt +1 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/pyproject.toml +1 -1
- aiwaf-0.1.5/setup.py +43 -0
- aiwaf-0.1.2/PKG-INFO +0 -187
- aiwaf-0.1.2/README.md +0 -176
- aiwaf-0.1.2/aiwaf/middleware.py +0 -115
- aiwaf-0.1.2/aiwaf/trainer.py +0 -123
- aiwaf-0.1.2/aiwaf.egg-info/PKG-INFO +0 -187
- aiwaf-0.1.2/setup.py +0 -31
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/__init__.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/apps.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/blacklist_manager.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/management/__init__.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/management/commands/__init__.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/management/commands/detect_and_train.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/resources/model.pkl +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/storage.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/template_tags/__init__.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/template_tags/aiwaf_tags.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf/utils.py +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf.egg-info/dependency_links.txt +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/aiwaf.egg-info/top_level.txt +0 -0
- {aiwaf-0.1.2 → aiwaf-0.1.5}/setup.cfg +0 -0
aiwaf-0.1.5/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aayush Gauba
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
aiwaf-0.1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiwaf
|
|
3
|
+
Version: 0.1.5
|
|
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
|
+
|
|
18
|
+
# AI‑WAF
|
|
19
|
+
|
|
20
|
+
> A self‑learning, Django‑friendly Web Application Firewall
|
|
21
|
+
> with rate‑limiting, anomaly detection, honeypots, UUID‑tamper protection, dynamic keyword extraction, file‑extension probing detection, and daily retraining.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Package Structure
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
aiwaf/
|
|
29
|
+
├── __init__.py
|
|
30
|
+
├── blacklist_manager.py
|
|
31
|
+
├── middleware.py
|
|
32
|
+
├── trainer.py # exposes train()
|
|
33
|
+
├── utils.py
|
|
34
|
+
├── template_tags/
|
|
35
|
+
│ └── aiwaf_tags.py
|
|
36
|
+
├── resources/
|
|
37
|
+
│ ├── model.pkl # pre‑trained base model
|
|
38
|
+
│ └── dynamic_keywords.json # evolves daily
|
|
39
|
+
├── management/
|
|
40
|
+
│ └── commands/
|
|
41
|
+
│ └── detect_and_train.py # `python manage.py detect_and_train`
|
|
42
|
+
└── LICENSE
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- **IP Blocklist**
|
|
50
|
+
Instantly blocks suspicious IPs (supports CSV fallback or Django model).
|
|
51
|
+
|
|
52
|
+
- **Rate Limiting**
|
|
53
|
+
Sliding‑window blocks flooders (> `AIWAF_RATE_MAX` per `AIWAF_RATE_WINDOW`), then blacklists them.
|
|
54
|
+
|
|
55
|
+
- **AI Anomaly Detection**
|
|
56
|
+
IsolationForest on features:
|
|
57
|
+
- Path length
|
|
58
|
+
- Keyword hits (static + dynamic)
|
|
59
|
+
- Response time
|
|
60
|
+
- Status‑code index
|
|
61
|
+
- Burst count
|
|
62
|
+
- Total 404s
|
|
63
|
+
|
|
64
|
+
- **Dynamic Keyword Extraction**
|
|
65
|
+
Every retrain: top 10 most frequent “words” from 4xx/5xx paths are appended to your malicious keyword set.
|
|
66
|
+
|
|
67
|
+
- **File‑Extension Probing Detection**
|
|
68
|
+
Tracks repeated 404s on common web‑extensions (e.g. `.php`, `.asp`) and auto‑blocks after a burst.
|
|
69
|
+
|
|
70
|
+
- **Honeypot Field**
|
|
71
|
+
Hidden form field (via template tag) that bots fill → instant block.
|
|
72
|
+
|
|
73
|
+
- **UUID Tampering Protection**
|
|
74
|
+
Any `<uuid:…>` URL that doesn’t map to **any** model in its Django app gets blocked.
|
|
75
|
+
|
|
76
|
+
- **Daily Retraining**
|
|
77
|
+
Reads rotated/gzipped logs, auto‑blocks 404 floods (≥6), retrains the model, updates `model.pkl` + `dynamic_keywords.json`.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# From PyPI
|
|
85
|
+
pip install aiwaf
|
|
86
|
+
|
|
87
|
+
# Or for local development
|
|
88
|
+
git clone https://github.com/aayushgauba/aiwaf.git
|
|
89
|
+
cd aiwaf
|
|
90
|
+
pip install -e .
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## ⚙️ Configuration (`settings.py`)
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
INSTALLED_APPS += ["aiwaf"]
|
|
99
|
+
|
|
100
|
+
### Database Setup
|
|
101
|
+
|
|
102
|
+
After adding `aiwaf` to your `INSTALLED_APPS`, create the necessary tables for the IP‐blacklist and dynamic‐keyword models:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
python manage.py makemigrations aiwaf
|
|
106
|
+
python manage.py migrate
|
|
107
|
+
|
|
108
|
+
# Required
|
|
109
|
+
AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
|
|
110
|
+
|
|
111
|
+
# Optional (defaults shown)
|
|
112
|
+
AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
|
|
113
|
+
AIWAF_HONEYPOT_FIELD = "hp_field"
|
|
114
|
+
AIWAF_RATE_WINDOW = 10 # seconds
|
|
115
|
+
AIWAF_RATE_MAX = 20 # max reqs/window
|
|
116
|
+
AIWAF_RATE_FLOOD = 10 # flood threshold
|
|
117
|
+
AIWAF_WINDOW_SECONDS = 60 # anomaly window
|
|
118
|
+
AIWAF_FILE_EXTENSIONS = [".php", ".asp", ".jsp"] # 404‑burst tracked extensions
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> **Note:** You no longer need to define `AIWAF_MALICIOUS_KEYWORDS` or `AIWAF_STATUS_CODES` in your settings — they’re built in and evolve dynamically.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Middleware Setup
|
|
126
|
+
|
|
127
|
+
Add in **this** order to your `MIDDLEWARE` list:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
MIDDLEWARE = [
|
|
131
|
+
"aiwaf.middleware.IPBlockMiddleware",
|
|
132
|
+
"aiwaf.middleware.RateLimitMiddleware",
|
|
133
|
+
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
134
|
+
"aiwaf.middleware.HoneypotMiddleware",
|
|
135
|
+
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
136
|
+
# ... other middleware ...
|
|
137
|
+
]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Honeypot Field (in your template)
|
|
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
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
python manage.py detect_and_train
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**What happens:**
|
|
166
|
+
1. Read access logs
|
|
167
|
+
2. Auto‑block IPs with ≥ 6 total 404s
|
|
168
|
+
3. Extract features & train IsolationForest
|
|
169
|
+
4. Save `model.pkl`
|
|
170
|
+
5. Extract top 10 dynamic keywords from 4xx/5xx
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## How It Works
|
|
175
|
+
|
|
176
|
+
| Middleware | Purpose |
|
|
177
|
+
|------------------------------------|-----------------------------------------------------------------|
|
|
178
|
+
| IPAndKeywordBlockMiddleware | Blocks requests from known blacklisted IPs and Keywords |
|
|
179
|
+
| RateLimitMiddleware | Enforces burst & flood thresholds |
|
|
180
|
+
| AIAnomalyMiddleware | ML‑driven behavior analysis + block on anomaly |
|
|
181
|
+
| HoneypotMiddleware | Detects bots filling hidden inputs in forms |
|
|
182
|
+
| UUIDTamperMiddleware | Blocks guessed/nonexistent UUIDs across all models in an app |
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Credits
|
|
193
|
+
|
|
194
|
+
**AI‑WAF** by [Aayush Gauba](https://github.com/aayushgauba)
|
|
195
|
+
> “Let your firewall learn and evolve — keep your site a fortress.”
|
aiwaf-0.1.5/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
|
|
2
|
+
# AI‑WAF
|
|
3
|
+
|
|
4
|
+
> A self‑learning, Django‑friendly Web Application Firewall
|
|
5
|
+
> with rate‑limiting, anomaly detection, honeypots, UUID‑tamper protection, dynamic keyword extraction, file‑extension probing detection, and daily retraining.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Package Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
aiwaf/
|
|
13
|
+
├── __init__.py
|
|
14
|
+
├── blacklist_manager.py
|
|
15
|
+
├── middleware.py
|
|
16
|
+
├── trainer.py # exposes train()
|
|
17
|
+
├── utils.py
|
|
18
|
+
├── template_tags/
|
|
19
|
+
│ └── aiwaf_tags.py
|
|
20
|
+
├── resources/
|
|
21
|
+
│ ├── model.pkl # pre‑trained base model
|
|
22
|
+
│ └── dynamic_keywords.json # evolves daily
|
|
23
|
+
├── management/
|
|
24
|
+
│ └── commands/
|
|
25
|
+
│ └── detect_and_train.py # `python manage.py detect_and_train`
|
|
26
|
+
└── LICENSE
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **IP Blocklist**
|
|
34
|
+
Instantly blocks suspicious IPs (supports CSV fallback or Django model).
|
|
35
|
+
|
|
36
|
+
- **Rate Limiting**
|
|
37
|
+
Sliding‑window blocks flooders (> `AIWAF_RATE_MAX` per `AIWAF_RATE_WINDOW`), then blacklists them.
|
|
38
|
+
|
|
39
|
+
- **AI Anomaly Detection**
|
|
40
|
+
IsolationForest on features:
|
|
41
|
+
- Path length
|
|
42
|
+
- Keyword hits (static + dynamic)
|
|
43
|
+
- Response time
|
|
44
|
+
- Status‑code index
|
|
45
|
+
- Burst count
|
|
46
|
+
- Total 404s
|
|
47
|
+
|
|
48
|
+
- **Dynamic Keyword Extraction**
|
|
49
|
+
Every retrain: top 10 most frequent “words” from 4xx/5xx paths are appended to your malicious keyword set.
|
|
50
|
+
|
|
51
|
+
- **File‑Extension Probing Detection**
|
|
52
|
+
Tracks repeated 404s on common web‑extensions (e.g. `.php`, `.asp`) and auto‑blocks after a burst.
|
|
53
|
+
|
|
54
|
+
- **Honeypot Field**
|
|
55
|
+
Hidden form field (via template tag) that bots fill → instant block.
|
|
56
|
+
|
|
57
|
+
- **UUID Tampering Protection**
|
|
58
|
+
Any `<uuid:…>` URL that doesn’t map to **any** model in its Django app gets blocked.
|
|
59
|
+
|
|
60
|
+
- **Daily Retraining**
|
|
61
|
+
Reads rotated/gzipped logs, auto‑blocks 404 floods (≥6), retrains the model, updates `model.pkl` + `dynamic_keywords.json`.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# From PyPI
|
|
69
|
+
pip install aiwaf
|
|
70
|
+
|
|
71
|
+
# Or for local development
|
|
72
|
+
git clone https://github.com/aayushgauba/aiwaf.git
|
|
73
|
+
cd aiwaf
|
|
74
|
+
pip install -e .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ⚙️ Configuration (`settings.py`)
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
INSTALLED_APPS += ["aiwaf"]
|
|
83
|
+
|
|
84
|
+
### Database Setup
|
|
85
|
+
|
|
86
|
+
After adding `aiwaf` to your `INSTALLED_APPS`, create the necessary tables for the IP‐blacklist and dynamic‐keyword models:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python manage.py makemigrations aiwaf
|
|
90
|
+
python manage.py migrate
|
|
91
|
+
|
|
92
|
+
# Required
|
|
93
|
+
AIWAF_ACCESS_LOG = "/var/log/nginx/access.log"
|
|
94
|
+
|
|
95
|
+
# Optional (defaults shown)
|
|
96
|
+
AIWAF_MODEL_PATH = BASE_DIR / "aiwaf" / "resources" / "model.pkl"
|
|
97
|
+
AIWAF_HONEYPOT_FIELD = "hp_field"
|
|
98
|
+
AIWAF_RATE_WINDOW = 10 # seconds
|
|
99
|
+
AIWAF_RATE_MAX = 20 # max reqs/window
|
|
100
|
+
AIWAF_RATE_FLOOD = 10 # flood threshold
|
|
101
|
+
AIWAF_WINDOW_SECONDS = 60 # anomaly window
|
|
102
|
+
AIWAF_FILE_EXTENSIONS = [".php", ".asp", ".jsp"] # 404‑burst tracked extensions
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> **Note:** You no longer need to define `AIWAF_MALICIOUS_KEYWORDS` or `AIWAF_STATUS_CODES` in your settings — they’re built in and evolve dynamically.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Middleware Setup
|
|
110
|
+
|
|
111
|
+
Add in **this** order to your `MIDDLEWARE` list:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
MIDDLEWARE = [
|
|
115
|
+
"aiwaf.middleware.IPBlockMiddleware",
|
|
116
|
+
"aiwaf.middleware.RateLimitMiddleware",
|
|
117
|
+
"aiwaf.middleware.AIAnomalyMiddleware",
|
|
118
|
+
"aiwaf.middleware.HoneypotMiddleware",
|
|
119
|
+
"aiwaf.middleware.UUIDTamperMiddleware",
|
|
120
|
+
# ... other middleware ...
|
|
121
|
+
]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Honeypot Field (in your template)
|
|
127
|
+
|
|
128
|
+
```django
|
|
129
|
+
{% load aiwaf_tags %}
|
|
130
|
+
|
|
131
|
+
<form method="post">
|
|
132
|
+
{% csrf_token %}
|
|
133
|
+
{% honeypot_field %}
|
|
134
|
+
<!-- your real fields -->
|
|
135
|
+
</form>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> Renders a hidden `<input name="hp_field" style="display:none">`.
|
|
139
|
+
> Any non‑empty submission → IP blacklisted.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Running Detection & Training
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python manage.py detect_and_train
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**What happens:**
|
|
150
|
+
1. Read access logs
|
|
151
|
+
2. Auto‑block IPs with ≥ 6 total 404s
|
|
152
|
+
3. Extract features & train IsolationForest
|
|
153
|
+
4. Save `model.pkl`
|
|
154
|
+
5. Extract top 10 dynamic keywords from 4xx/5xx
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## How It Works
|
|
159
|
+
|
|
160
|
+
| Middleware | Purpose |
|
|
161
|
+
|------------------------------------|-----------------------------------------------------------------|
|
|
162
|
+
| IPAndKeywordBlockMiddleware | Blocks requests from known blacklisted IPs and Keywords |
|
|
163
|
+
| RateLimitMiddleware | Enforces burst & flood thresholds |
|
|
164
|
+
| AIAnomalyMiddleware | ML‑driven behavior analysis + block on anomaly |
|
|
165
|
+
| HoneypotMiddleware | Detects bots filling hidden inputs in forms |
|
|
166
|
+
| UUIDTamperMiddleware | Blocks guessed/nonexistent UUIDs across all models in an app |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Credits
|
|
177
|
+
|
|
178
|
+
**AI‑WAF** by [Aayush Gauba](https://github.com/aayushgauba)
|
|
179
|
+
> “Let your firewall learn and evolve — keep your site a fortress.”
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# aiwaf/middleware.py
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import re
|
|
5
|
+
import os
|
|
6
|
+
import numpy as np
|
|
7
|
+
import joblib
|
|
8
|
+
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
11
|
+
from django.http import JsonResponse
|
|
12
|
+
from django.conf import settings
|
|
13
|
+
from django.core.cache import cache
|
|
14
|
+
from django.db.models import F
|
|
15
|
+
from django.apps import apps
|
|
16
|
+
from django.urls import get_resolver
|
|
17
|
+
|
|
18
|
+
from .blacklist_manager import BlacklistManager
|
|
19
|
+
from .models import DynamicKeyword
|
|
20
|
+
|
|
21
|
+
MODEL_PATH = getattr(
|
|
22
|
+
settings,
|
|
23
|
+
"AIWAF_MODEL_PATH",
|
|
24
|
+
os.path.join(os.path.dirname(__file__), "resources", "model.pkl")
|
|
25
|
+
)
|
|
26
|
+
MODEL = joblib.load(MODEL_PATH)
|
|
27
|
+
|
|
28
|
+
STATIC_KW = getattr(
|
|
29
|
+
settings,
|
|
30
|
+
"AIWAF_MALICIOUS_KEYWORDS",
|
|
31
|
+
[
|
|
32
|
+
".php", "xmlrpc", "wp-", ".env", ".git", ".bak",
|
|
33
|
+
"conflg", "shell", "filemanager"
|
|
34
|
+
]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def get_ip(request):
|
|
38
|
+
xff = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
39
|
+
if xff:
|
|
40
|
+
return xff.split(",")[0].strip()
|
|
41
|
+
return request.META.get("REMOTE_ADDR", "")
|
|
42
|
+
|
|
43
|
+
class IPAndKeywordBlockMiddleware:
|
|
44
|
+
def __init__(self, get_response):
|
|
45
|
+
self.get_response = get_response
|
|
46
|
+
self.url_patterns = self._collect_view_paths()
|
|
47
|
+
|
|
48
|
+
def _collect_view_paths(self):
|
|
49
|
+
resolver = get_resolver()
|
|
50
|
+
patterns = set()
|
|
51
|
+
|
|
52
|
+
def extract(patterns_list, prefix=""):
|
|
53
|
+
for p in patterns_list:
|
|
54
|
+
if hasattr(p, "url_patterns"):
|
|
55
|
+
extract(p.url_patterns, prefix + str(p.pattern))
|
|
56
|
+
else:
|
|
57
|
+
pat = (prefix + str(p.pattern)).strip("^$")
|
|
58
|
+
patterns.add(pat)
|
|
59
|
+
extract(resolver.url_patterns)
|
|
60
|
+
return patterns
|
|
61
|
+
|
|
62
|
+
def __call__(self, request):
|
|
63
|
+
ip = get_ip(request)
|
|
64
|
+
path = request.path.lower()
|
|
65
|
+
if BlacklistManager.is_blocked(ip):
|
|
66
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
67
|
+
segments = [seg for seg in re.split(r"\W+", path) if len(seg) > 3]
|
|
68
|
+
for seg in segments:
|
|
69
|
+
obj, _ = DynamicKeyword.objects.get_or_create(keyword=seg)
|
|
70
|
+
DynamicKeyword.objects.filter(pk=obj.pk).update(count=F("count") + 1)
|
|
71
|
+
dynamic_top = list(
|
|
72
|
+
DynamicKeyword.objects
|
|
73
|
+
.order_by("-count")
|
|
74
|
+
.values_list("keyword", flat=True)[: getattr(settings, "AIWAF_DYNAMIC_TOP_N", 10)]
|
|
75
|
+
)
|
|
76
|
+
all_kw = set(STATIC_KW) | set(dynamic_top)
|
|
77
|
+
safe_kw = {kw for kw in all_kw if any(kw in pat for pat in self.url_patterns)}
|
|
78
|
+
suspicious_kw = all_kw - safe_kw
|
|
79
|
+
for seg in segments:
|
|
80
|
+
if seg in suspicious_kw:
|
|
81
|
+
BlacklistManager.block(ip, f"Keyword block: {seg}")
|
|
82
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
83
|
+
return self.get_response(request)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class RateLimitMiddleware:
|
|
87
|
+
WINDOW = 10
|
|
88
|
+
MAX = 20
|
|
89
|
+
FLOOD = 10
|
|
90
|
+
|
|
91
|
+
def __init__(self, get_response):
|
|
92
|
+
self.get_response = get_response
|
|
93
|
+
self.logs = defaultdict(list)
|
|
94
|
+
|
|
95
|
+
def __call__(self, request):
|
|
96
|
+
ip = get_ip(request)
|
|
97
|
+
now = time.time()
|
|
98
|
+
recs = [t for t in self.logs[ip] if now - t < self.WINDOW]
|
|
99
|
+
recs.append(now)
|
|
100
|
+
self.logs[ip] = recs
|
|
101
|
+
|
|
102
|
+
if len(recs) > self.MAX:
|
|
103
|
+
return JsonResponse({"error": "too_many_requests"}, status=429)
|
|
104
|
+
if len(recs) > self.FLOOD:
|
|
105
|
+
BlacklistManager.block(ip, "Flood pattern")
|
|
106
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
107
|
+
|
|
108
|
+
return self.get_response(request)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class AIAnomalyMiddleware(MiddlewareMixin):
|
|
112
|
+
WINDOW = getattr(settings, "AIWAF_WINDOW_SECONDS", 60)
|
|
113
|
+
TOP_N = getattr(settings, "AIWAF_DYNAMIC_TOP_N", 10)
|
|
114
|
+
|
|
115
|
+
def process_request(self, request):
|
|
116
|
+
ip = get_ip(request)
|
|
117
|
+
if BlacklistManager.is_blocked(ip):
|
|
118
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
119
|
+
|
|
120
|
+
now = time.time()
|
|
121
|
+
key = f"aiwaf:{ip}"
|
|
122
|
+
data = cache.get(key, [])
|
|
123
|
+
# TODO: you may want to capture real status & response_time in process_response
|
|
124
|
+
data.append((now, request.path, 0, 0.0))
|
|
125
|
+
data = [d for d in data if now - d[0] < self.WINDOW]
|
|
126
|
+
cache.set(key, data, timeout=self.WINDOW)
|
|
127
|
+
|
|
128
|
+
# update dynamic‐keyword counts
|
|
129
|
+
for seg in re.split(r"\W+", request.path.lower()):
|
|
130
|
+
if len(seg) > 3:
|
|
131
|
+
obj, _ = DynamicKeyword.objects.get_or_create(keyword=seg)
|
|
132
|
+
DynamicKeyword.objects.filter(pk=obj.pk).update(count=F("count") + 1)
|
|
133
|
+
|
|
134
|
+
if len(data) < 5:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
# pull top‐N dynamic tokens
|
|
138
|
+
top_dynamic = list(
|
|
139
|
+
DynamicKeyword.objects
|
|
140
|
+
.order_by("-count")
|
|
141
|
+
.values_list("keyword", flat=True)[: self.TOP_N]
|
|
142
|
+
)
|
|
143
|
+
ALL_KW = set(STATIC_KW) | set(top_dynamic)
|
|
144
|
+
|
|
145
|
+
total = len(data)
|
|
146
|
+
ratio404 = sum(1 for (_, _, st, _) in data if st == 404) / total
|
|
147
|
+
hits = sum(any(kw in path.lower() for kw in ALL_KW) for (_, path, _, _) in data)
|
|
148
|
+
avg_rt = np.mean([rt for (_, _, _, rt) in data]) if data else 0.0
|
|
149
|
+
ivs = [data[i][0] - data[i - 1][0] for i in range(1, total)]
|
|
150
|
+
avg_iv = np.mean(ivs) if ivs else 0.0
|
|
151
|
+
|
|
152
|
+
X = np.array([[total, ratio404, hits, avg_rt, avg_iv]], dtype=float)
|
|
153
|
+
if MODEL.predict(X)[0] == -1:
|
|
154
|
+
BlacklistManager.block(ip, "AI anomaly")
|
|
155
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
156
|
+
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class HoneypotMiddleware(MiddlewareMixin):
|
|
161
|
+
def process_view(self, request, view_func, view_args, view_kwargs):
|
|
162
|
+
trap = request.POST.get(getattr(settings, "AIWAF_HONEYPOT_FIELD", "hp_field"), "")
|
|
163
|
+
if trap:
|
|
164
|
+
ip = get_ip(request)
|
|
165
|
+
BlacklistManager.block(ip, "HONEYPOT triggered")
|
|
166
|
+
return JsonResponse({"error": "bot_detected"}, status=403)
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class UUIDTamperMiddleware(MiddlewareMixin):
|
|
171
|
+
def process_view(self, request, view_func, view_args, view_kwargs):
|
|
172
|
+
uid = view_kwargs.get("uuid")
|
|
173
|
+
if not uid:
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
ip = get_ip(request)
|
|
177
|
+
app_label = view_func.__module__.split(".")[0]
|
|
178
|
+
app_cfg = apps.get_app_config(app_label)
|
|
179
|
+
for Model in app_cfg.get_models():
|
|
180
|
+
if Model.objects.filter(pk=uid).exists():
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
BlacklistManager.block(ip, "UUID tampering")
|
|
184
|
+
return JsonResponse({"error": "blocked"}, status=403)
|
|
@@ -26,3 +26,11 @@ class BlacklistEntry(models.Model):
|
|
|
26
26
|
|
|
27
27
|
def __str__(self):
|
|
28
28
|
return f"{self.ip_address} ({self.reason})"
|
|
29
|
+
|
|
30
|
+
class DynamicKeyword(models.Model):
|
|
31
|
+
keyword = models.CharField(max_length=100, unique=True)
|
|
32
|
+
count = models.PositiveIntegerField(default=0)
|
|
33
|
+
last_updated = models.DateTimeField(auto_now=True)
|
|
34
|
+
|
|
35
|
+
class Meta:
|
|
36
|
+
ordering = ['-count']
|