wafaHell 0.2.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- wafaHell/app.py +31 -0
- wafaHell/globals.py +5 -0
- wafaHell/logger.py +102 -14
- wafaHell/middleware.py +398 -81
- wafaHell/mock.py +53 -0
- wafaHell/model.py +98 -10
- wafaHell/panel.py +406 -0
- wafaHell/rateLimiter.py +0 -1
- wafaHell/utils.py +413 -7
- {wafahell-0.2.0.dist-info → wafahell-1.1.0.dist-info}/METADATA +5 -1
- wafahell-1.1.0.dist-info/RECORD +15 -0
- {wafahell-0.2.0.dist-info → wafahell-1.1.0.dist-info}/WHEEL +1 -1
- wafaHell/teste.py +0 -16
- wafahell-0.2.0.dist-info/RECORD +0 -12
- {wafahell-0.2.0.dist-info → wafahell-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {wafahell-0.2.0.dist-info → wafahell-1.1.0.dist-info}/top_level.txt +0 -0
wafaHell/model.py
CHANGED
|
@@ -1,23 +1,111 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from sqlalchemy import
|
|
2
|
+
from sqlalchemy import (
|
|
3
|
+
DateTime, Text, create_engine,
|
|
4
|
+
Column, Integer, String, UniqueConstraint
|
|
5
|
+
)
|
|
3
6
|
from sqlalchemy.orm import declarative_base, sessionmaker, scoped_session
|
|
4
7
|
|
|
8
|
+
# =========================
|
|
9
|
+
# CONFIGURAÇÃO GLOBAL
|
|
10
|
+
# =========================
|
|
11
|
+
|
|
12
|
+
DATABASE_URL = "sqlite:///wafaHell.db"
|
|
13
|
+
|
|
14
|
+
engine = create_engine(
|
|
15
|
+
DATABASE_URL,
|
|
16
|
+
echo=False,
|
|
17
|
+
future=True
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
SessionLocal = scoped_session(
|
|
21
|
+
sessionmaker(
|
|
22
|
+
bind=engine,
|
|
23
|
+
autocommit=False,
|
|
24
|
+
autoflush=False
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
|
|
5
28
|
Base = declarative_base()
|
|
6
29
|
|
|
30
|
+
# =========================
|
|
31
|
+
# MODELS
|
|
32
|
+
# =========================
|
|
33
|
+
|
|
7
34
|
class Blocked(Base):
|
|
8
35
|
__tablename__ = "blocks"
|
|
9
36
|
|
|
10
37
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
11
38
|
ip = Column(String, nullable=True)
|
|
12
39
|
user_agent = Column(String, nullable=True)
|
|
13
|
-
blocked_at = Column(
|
|
40
|
+
blocked_at = Column(
|
|
41
|
+
String,
|
|
42
|
+
nullable=False,
|
|
43
|
+
default=lambda: datetime.now().strftime("%H:%M:%S")
|
|
44
|
+
)
|
|
45
|
+
blocked_until = Column(DateTime, nullable=False)
|
|
14
46
|
|
|
15
47
|
def __repr__(self):
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
48
|
+
return (
|
|
49
|
+
f"<Blocked(ip='{self.ip}', "
|
|
50
|
+
f"user_agent='{self.user_agent}', "
|
|
51
|
+
f"blocked_at='{self.blocked_at}', "
|
|
52
|
+
f"blocked_until='{self.blocked_until}')>"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
class Whitelist(Base):
|
|
56
|
+
__tablename__ = "whitelist"
|
|
57
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
58
|
+
ip = Column(String, nullable=False, unique=True)
|
|
59
|
+
added_at = Column(
|
|
60
|
+
String,
|
|
61
|
+
nullable=False,
|
|
62
|
+
default=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
63
|
+
)
|
|
64
|
+
def __repr__(self):
|
|
65
|
+
return f"<Whitelist(ip='{self.ip}', added_at='{self.added_at}')>"
|
|
66
|
+
|
|
67
|
+
class WafLog(Base):
|
|
68
|
+
__tablename__ = "waf_logs"
|
|
69
|
+
|
|
70
|
+
id = Column(Integer, primary_key=True)
|
|
71
|
+
timestamp = Column(DateTime, default=datetime.utcnow)
|
|
72
|
+
|
|
73
|
+
time_bucket = Column(String(20), index=True)
|
|
74
|
+
|
|
75
|
+
attack_type = Column(String(50))
|
|
76
|
+
ip = Column(String(50))
|
|
77
|
+
path = Column(String(200))
|
|
78
|
+
method = Column(String(10))
|
|
79
|
+
payload = Column(Text)
|
|
80
|
+
attack_local = Column(String(50))
|
|
81
|
+
level = Column(String(20))
|
|
82
|
+
|
|
83
|
+
__table_args__ = (
|
|
84
|
+
UniqueConstraint('ip', 'attack_type', 'time_bucket', name='uix_ip_attack_time'),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class AdminUser(Base):
|
|
89
|
+
__tablename__ = "admin_user"
|
|
90
|
+
|
|
91
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
92
|
+
login = Column(String(50), nullable=False, unique=True)
|
|
93
|
+
password = Column(String(255), nullable=False)
|
|
94
|
+
|
|
95
|
+
def __repr__(self):
|
|
96
|
+
return f"<AdminUser(login='{self.login}')>"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# =========================
|
|
100
|
+
# DB INIT / SESSION
|
|
101
|
+
# =========================
|
|
102
|
+
|
|
103
|
+
def init_db():
|
|
104
|
+
"""Cria as tabelas uma única vez"""
|
|
105
|
+
Base.metadata.create_all(bind=engine)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_session():
|
|
109
|
+
"""Retorna uma sessão reutilizável"""
|
|
110
|
+
return SessionLocal()
|
|
111
|
+
|
wafaHell/panel.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
from .model import AdminUser, WafLog, Whitelist, get_session, Blocked
|
|
2
|
+
from .globals import waf_cache
|
|
3
|
+
from .utils import Dashboard, admin
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
import re
|
|
6
|
+
from flask import request, jsonify, make_response, flash, session, redirect, render_template
|
|
7
|
+
from sqlalchemy import func
|
|
8
|
+
import csv
|
|
9
|
+
import io
|
|
10
|
+
from werkzeug.security import check_password_hash
|
|
11
|
+
|
|
12
|
+
dashboard = Dashboard()
|
|
13
|
+
|
|
14
|
+
def get_logs_and_stats(ip_filter=None, type_filter=None, limit=100):
|
|
15
|
+
session = get_session()
|
|
16
|
+
try:
|
|
17
|
+
log_query = session.query(WafLog).order_by(WafLog.id.desc())
|
|
18
|
+
stat_query = session.query(func.count(WafLog.id))
|
|
19
|
+
|
|
20
|
+
if ip_filter:
|
|
21
|
+
log_query = log_query.filter(WafLog.ip == ip_filter)
|
|
22
|
+
stat_query = stat_query.filter(WafLog.ip == ip_filter)
|
|
23
|
+
if type_filter:
|
|
24
|
+
log_query = log_query.filter(WafLog.attack_type == type_filter)
|
|
25
|
+
stat_query = stat_query.filter(WafLog.attack_type == type_filter)
|
|
26
|
+
|
|
27
|
+
db_logs = log_query.limit(limit).all()
|
|
28
|
+
|
|
29
|
+
# Estatísticas Dinâmicas
|
|
30
|
+
stats = {
|
|
31
|
+
"total": stat_query.scalar() or 0,
|
|
32
|
+
# Agora attacks conta tudo que não é 'Info' ou 'System'
|
|
33
|
+
"attacks": stat_query.filter(WafLog.attack_type.in_(['SQLI', 'XSS', 'RATE LIMIT'])).scalar() or 0,
|
|
34
|
+
"sqli": stat_query.filter(WafLog.attack_type == 'SQLI').scalar() or 0,
|
|
35
|
+
"xss": stat_query.filter(WafLog.attack_type == 'XSS').scalar() or 0,
|
|
36
|
+
"rate_limit": stat_query.filter(WafLog.attack_type == 'RATE LIMIT').scalar() or 0,
|
|
37
|
+
"blocks": stat_query.filter(WafLog.attack_type == 'IP BLOCK').scalar() or 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
formatted_logs = []
|
|
41
|
+
for log in db_logs:
|
|
42
|
+
# Determinamos o "Tipo" visual para o HTML
|
|
43
|
+
display_type = "INFO"
|
|
44
|
+
if log.attack_type in ['SQLI', 'XSS', 'RATE LIMIT']:
|
|
45
|
+
display_type = "ATTACK"
|
|
46
|
+
elif log.attack_type == 'IP BLOCK':
|
|
47
|
+
display_type = "BLOCKED" # Mudança aqui para o dashboard
|
|
48
|
+
|
|
49
|
+
formatted_logs.append({
|
|
50
|
+
"timestamp": log.timestamp.strftime("%H:%M:%S - %d/%m/%Y"),
|
|
51
|
+
"type": display_type,
|
|
52
|
+
"details": {
|
|
53
|
+
"attack_type": log.attack_type,
|
|
54
|
+
"ip": log.ip or "---",
|
|
55
|
+
"path": log.path or "---",
|
|
56
|
+
"method": log.method or "---",
|
|
57
|
+
"payload": log.payload or "---",
|
|
58
|
+
"attack_local": log.attack_local or "---"
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
return formatted_logs, stats
|
|
62
|
+
finally:
|
|
63
|
+
session.close()
|
|
64
|
+
|
|
65
|
+
def setup_dashboard(app, custom_path=None):
|
|
66
|
+
target_path = custom_path or '/admin/dashboard'
|
|
67
|
+
|
|
68
|
+
@app.route(target_path + "/login", methods=["GET", "POST"])
|
|
69
|
+
def login():
|
|
70
|
+
if request.method == "POST":
|
|
71
|
+
username = request.form.get("user")
|
|
72
|
+
password = request.form.get("password")
|
|
73
|
+
db_session = get_session()
|
|
74
|
+
try:
|
|
75
|
+
admin = db_session.query(AdminUser).filter(AdminUser.login == username).first()
|
|
76
|
+
|
|
77
|
+
# 3. Valida (Aqui você deveria usar hash, mas vamos focar na lógica)
|
|
78
|
+
if admin and check_password_hash(admin.password, password):
|
|
79
|
+
session["logged_in"] = True
|
|
80
|
+
next_page = request.args.get("next", target_path)
|
|
81
|
+
return redirect(next_page)
|
|
82
|
+
else:
|
|
83
|
+
flash("Acesso Negado: Credenciais Inválidas", "error")
|
|
84
|
+
return redirect(request.url)
|
|
85
|
+
finally:
|
|
86
|
+
db_session.close()
|
|
87
|
+
|
|
88
|
+
return render_template("login.html")
|
|
89
|
+
|
|
90
|
+
@app.route(target_path + '/data')
|
|
91
|
+
@admin
|
|
92
|
+
def wafahell_data():
|
|
93
|
+
ip_f = request.args.get('ip')
|
|
94
|
+
type_f = request.args.get('type')
|
|
95
|
+
logs, stats = get_logs_and_stats(ip_filter=ip_f, type_filter=type_f)
|
|
96
|
+
return jsonify({"logs": logs, "stats": stats})
|
|
97
|
+
|
|
98
|
+
# Rota 2: Retorna o HTML inicial
|
|
99
|
+
@app.route(target_path)
|
|
100
|
+
@admin
|
|
101
|
+
def wafahell_dashboard():
|
|
102
|
+
ip_f = request.args.get('ip')
|
|
103
|
+
type_f = request.args.get('type')
|
|
104
|
+
logs, stats = get_logs_and_stats(ip_filter=ip_f, type_filter=type_f)
|
|
105
|
+
return render_template('dashboard.html', logs=logs, stats=stats, filters={'ip': ip_f, 'type': type_f})
|
|
106
|
+
|
|
107
|
+
@app.route(target_path + '/export/csv')
|
|
108
|
+
@admin
|
|
109
|
+
def export_csv():
|
|
110
|
+
try:
|
|
111
|
+
ip_filter = request.args.get('ip')
|
|
112
|
+
type_filter = request.args.get('type')
|
|
113
|
+
|
|
114
|
+
session = get_session()
|
|
115
|
+
query = session.query(WafLog)
|
|
116
|
+
|
|
117
|
+
if ip_filter:
|
|
118
|
+
query = query.filter(WafLog.ip == ip_filter)
|
|
119
|
+
if type_filter:
|
|
120
|
+
query = query.filter(WafLog.attack_type == type_filter)
|
|
121
|
+
|
|
122
|
+
logs = query.order_by(WafLog.timestamp.desc()).all()
|
|
123
|
+
session.close()
|
|
124
|
+
|
|
125
|
+
# Criamos o CSV em memória
|
|
126
|
+
output = io.StringIO()
|
|
127
|
+
writer = csv.writer(output)
|
|
128
|
+
|
|
129
|
+
# Cabeçalho
|
|
130
|
+
writer.writerow(['Data/Hora', 'Tipo', 'IP', 'Endpoint', 'Metodo', 'Payload'])
|
|
131
|
+
|
|
132
|
+
for log in logs:
|
|
133
|
+
writer.writerow([
|
|
134
|
+
log.timestamp,
|
|
135
|
+
log.attack_type,
|
|
136
|
+
log.ip,
|
|
137
|
+
log.path,
|
|
138
|
+
log.method,
|
|
139
|
+
log.payload
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
response = make_response(output.getvalue())
|
|
143
|
+
response.headers["Content-Disposition"] = f"attachment; filename=waf_logs_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
|
|
144
|
+
response.headers["Content-type"] = "text/csv"
|
|
145
|
+
return response
|
|
146
|
+
except Exception as e:
|
|
147
|
+
session.rollback()
|
|
148
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
|
149
|
+
finally:
|
|
150
|
+
session.close()
|
|
151
|
+
|
|
152
|
+
@app.route(target_path + '/block_ip', methods=['POST'])
|
|
153
|
+
@admin
|
|
154
|
+
def block_ip() -> bool:
|
|
155
|
+
data = request.get_json()
|
|
156
|
+
ip = data.get('ip')
|
|
157
|
+
block_time = data.get('block_time_minutes', 5) # Pega do JSON ou assume 5
|
|
158
|
+
|
|
159
|
+
if not ip:
|
|
160
|
+
return jsonify({"status": "error", "message": "IP ausente"}), 400
|
|
161
|
+
|
|
162
|
+
session = get_session()
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
exists = session.query(Blocked).filter_by(ip=ip, user_agent="MANUAL_BLOCK").first()
|
|
166
|
+
if exists:
|
|
167
|
+
exists.blocked_until = datetime.now(timezone.utc) + timedelta(minutes=int(block_time))
|
|
168
|
+
session.commit()
|
|
169
|
+
return jsonify({"status": "success", "message": "Tempo de bloqueio atualizado"})
|
|
170
|
+
|
|
171
|
+
now = datetime.now(timezone.utc)
|
|
172
|
+
until = now + timedelta(minutes=int(block_time))
|
|
173
|
+
|
|
174
|
+
new_block = Blocked(
|
|
175
|
+
ip=ip,
|
|
176
|
+
user_agent="MANUAL_BLOCK",
|
|
177
|
+
blocked_until=until
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
session.add(new_block)
|
|
181
|
+
session.commit()
|
|
182
|
+
return jsonify({"status": "success", "message": "IP bloqueado com sucesso"})
|
|
183
|
+
except Exception as e:
|
|
184
|
+
session.rollback()
|
|
185
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
|
186
|
+
finally:
|
|
187
|
+
session.close()
|
|
188
|
+
|
|
189
|
+
@app.route(target_path + '/blocked_list', methods=['GET'])
|
|
190
|
+
@admin
|
|
191
|
+
def get_blocked_list():
|
|
192
|
+
session = get_session()
|
|
193
|
+
try:
|
|
194
|
+
# Pega todos os IPs bloqueados que ainda não expiraram
|
|
195
|
+
now = datetime.now(timezone.utc)
|
|
196
|
+
blocks = session.query(Blocked).filter(Blocked.blocked_until > now).all()
|
|
197
|
+
|
|
198
|
+
data = []
|
|
199
|
+
for b in blocks:
|
|
200
|
+
# Calcula tempo restante
|
|
201
|
+
remaining = b.blocked_until.replace(tzinfo=timezone.utc) - now
|
|
202
|
+
mins, secs = divmod(remaining.total_seconds(), 60)
|
|
203
|
+
|
|
204
|
+
data.append({
|
|
205
|
+
"ip": b.ip,
|
|
206
|
+
"user_agent": b.user_agent[:30] + '...' if len(b.user_agent) > 30 else b.user_agent,
|
|
207
|
+
"expires_in": f"{int(mins)}m {int(secs)}s"
|
|
208
|
+
})
|
|
209
|
+
return jsonify(data)
|
|
210
|
+
finally:
|
|
211
|
+
session.close()
|
|
212
|
+
|
|
213
|
+
@app.route(target_path + '/unblock_ip', methods=['POST'])
|
|
214
|
+
@admin
|
|
215
|
+
def manual_unblock():
|
|
216
|
+
data = request.get_json()
|
|
217
|
+
ip = data.get('ip')
|
|
218
|
+
session = get_session()
|
|
219
|
+
try:
|
|
220
|
+
# Remove da tabela
|
|
221
|
+
session.query(Blocked).filter(Blocked.ip == ip).delete()
|
|
222
|
+
session.commit()
|
|
223
|
+
|
|
224
|
+
return jsonify({"status": "success"})
|
|
225
|
+
except Exception as e:
|
|
226
|
+
return jsonify({"error": str(e)}), 500
|
|
227
|
+
finally:
|
|
228
|
+
session.close()
|
|
229
|
+
|
|
230
|
+
@app.route(target_path + '/graphs', methods=['GET'])
|
|
231
|
+
@admin
|
|
232
|
+
def graphs():
|
|
233
|
+
return render_template('graphs.html')
|
|
234
|
+
|
|
235
|
+
@app.route(target_path + '/stats', methods=['GET'])
|
|
236
|
+
@admin
|
|
237
|
+
def api_stats():
|
|
238
|
+
return jsonify(dashboard.dashboard_setup())
|
|
239
|
+
|
|
240
|
+
@app.route(target_path + '/vars', methods=['GET'])
|
|
241
|
+
@admin
|
|
242
|
+
def get_vars():
|
|
243
|
+
from middleware import Wafahell
|
|
244
|
+
waf = Wafahell()
|
|
245
|
+
|
|
246
|
+
output = {}
|
|
247
|
+
allowed_types = (str, int, float, bool, list, dict)
|
|
248
|
+
blacklisted_keys = {'app', 'log', 'recent_blocks_cache', '_instance', 'initialized', 'dashboard_path', 'rules_sqli', 'rules_xss'}
|
|
249
|
+
|
|
250
|
+
for key, value in vars(waf).items():
|
|
251
|
+
if key not in blacklisted_keys and isinstance(value, allowed_types):
|
|
252
|
+
output[key] = value
|
|
253
|
+
|
|
254
|
+
return output
|
|
255
|
+
|
|
256
|
+
@app.route(target_path + '/vars/change', methods=['POST'])
|
|
257
|
+
@admin
|
|
258
|
+
def vars_change():
|
|
259
|
+
data = request.json
|
|
260
|
+
key = data.get('key')
|
|
261
|
+
value = data.get('value')
|
|
262
|
+
|
|
263
|
+
from middleware import Wafahell
|
|
264
|
+
waf = Wafahell()
|
|
265
|
+
|
|
266
|
+
if hasattr(waf, key):
|
|
267
|
+
|
|
268
|
+
setattr(waf, key, value)
|
|
269
|
+
|
|
270
|
+
print(f" * [WafaHell] Variable '{key}' changed to: {value}")
|
|
271
|
+
|
|
272
|
+
return {"status": "success", "message": f"{key} updated", "newValue": value}
|
|
273
|
+
|
|
274
|
+
return {"status": "error", "message": "Invalid variable"}, 400
|
|
275
|
+
|
|
276
|
+
@app.route(target_path + '/import_blacklist', methods=['POST'])
|
|
277
|
+
@admin
|
|
278
|
+
def import_blacklist():
|
|
279
|
+
data = request.get_json()
|
|
280
|
+
raw_ips = data.get('ips', [])
|
|
281
|
+
|
|
282
|
+
if not raw_ips:
|
|
283
|
+
return jsonify({"status": "error", "message": "Nenhum IP fornecido."}), 400
|
|
284
|
+
|
|
285
|
+
# 1. Validação e Limpeza (Regex IPv4)
|
|
286
|
+
# Usamos set() para remover duplicatas enviadas no mesmo arquivo
|
|
287
|
+
ip_regex = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
|
|
288
|
+
valid_ips = set(ip for ip in raw_ips if ip_regex.match(ip))
|
|
289
|
+
|
|
290
|
+
if not valid_ips:
|
|
291
|
+
return jsonify({"status": "error", "message": "Nenhum IP válido encontrado."}), 400
|
|
292
|
+
|
|
293
|
+
session = get_session()
|
|
294
|
+
try:
|
|
295
|
+
# 2. Filtragem de Duplicatas no Banco
|
|
296
|
+
# Descobre quais desses IPs já estão bloqueados para não dar erro de Unique Key
|
|
297
|
+
existing_query = session.query(Blocked.ip).filter(Blocked.ip.in_(valid_ips)).all()
|
|
298
|
+
existing_ips = {row.ip for row in existing_query}
|
|
299
|
+
|
|
300
|
+
# Apenas os novos IPs
|
|
301
|
+
ips_to_insert = valid_ips - existing_ips
|
|
302
|
+
|
|
303
|
+
if not ips_to_insert:
|
|
304
|
+
return jsonify({"status": "success", "message": "Todos os IPs já estavam na blacklist."})
|
|
305
|
+
|
|
306
|
+
# 3. Preparação dos Dados (Bulk)
|
|
307
|
+
# Definimos um tempo padrão de 30 dias para importações manuais de lista
|
|
308
|
+
now = datetime.now(timezone.utc)
|
|
309
|
+
until = now + timedelta(days=3600)
|
|
310
|
+
|
|
311
|
+
bulk_data = []
|
|
312
|
+
for ip in ips_to_insert:
|
|
313
|
+
bulk_data.append({
|
|
314
|
+
"ip": ip,
|
|
315
|
+
"user_agent": "MANUAL_IMPORT_LIST",
|
|
316
|
+
# Mantendo o formato string que você usa no Model
|
|
317
|
+
"blocked_at": now.strftime("%H:%M:%S"),
|
|
318
|
+
"blocked_until": until
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
# 4. Atualização IMEDIATA do Cache (Fast Path)
|
|
322
|
+
# Isso garante que o bloqueio funcione no milissegundo seguinte,
|
|
323
|
+
# sem precisar esperar a próxima query no banco.
|
|
324
|
+
waf_cache.set(f"blocked_{ip}", True, expire=60)
|
|
325
|
+
|
|
326
|
+
# 5. Inserção em Massa (Muito Rápido)
|
|
327
|
+
session.bulk_insert_mappings(Blocked, bulk_data)
|
|
328
|
+
session.commit()
|
|
329
|
+
|
|
330
|
+
count = len(ips_to_insert)
|
|
331
|
+
print(f" * [WafaHell] Blacklist importada: {count} novos IPs.")
|
|
332
|
+
|
|
333
|
+
return jsonify({
|
|
334
|
+
"status": "success",
|
|
335
|
+
"message": f"Sucesso! {count} novos IPs adicionados à Blacklist."
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
except Exception as e:
|
|
339
|
+
session.rollback()
|
|
340
|
+
print(f"Erro na importação: {e}")
|
|
341
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
|
342
|
+
finally:
|
|
343
|
+
session.close()
|
|
344
|
+
|
|
345
|
+
@app.route(target_path + '/import_whitelist', methods=['POST'])
|
|
346
|
+
@admin
|
|
347
|
+
def import_whitelist():
|
|
348
|
+
data = request.get_json()
|
|
349
|
+
raw_ips = data.get('ips', [])
|
|
350
|
+
|
|
351
|
+
if not raw_ips:
|
|
352
|
+
return jsonify({"status": "error", "message": "Nenhum IP fornecido."}), 400
|
|
353
|
+
|
|
354
|
+
# 1. Validação (Regex)
|
|
355
|
+
ip_regex = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
|
|
356
|
+
valid_ips = set(ip for ip in raw_ips if ip_regex.match(ip))
|
|
357
|
+
|
|
358
|
+
if not valid_ips:
|
|
359
|
+
return jsonify({"status": "error", "message": "Nenhum IP válido encontrado."}), 400
|
|
360
|
+
|
|
361
|
+
session = get_session()
|
|
362
|
+
try:
|
|
363
|
+
# 2. Verifica Duplicatas no Banco
|
|
364
|
+
# Como sua coluna 'ip' é unique=True, precisamos filtrar o que já existe
|
|
365
|
+
existing_query = session.query(Whitelist.ip).filter(Whitelist.ip.in_(valid_ips)).all()
|
|
366
|
+
existing_ips = {row.ip for row in existing_query}
|
|
367
|
+
|
|
368
|
+
ips_to_insert = valid_ips - existing_ips
|
|
369
|
+
|
|
370
|
+
if not ips_to_insert:
|
|
371
|
+
return jsonify({"status": "success", "message": "Todos os IPs já estavam na whitelist."})
|
|
372
|
+
|
|
373
|
+
# 3. Prepara Bulk Insert e Cache
|
|
374
|
+
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
375
|
+
bulk_data = []
|
|
376
|
+
|
|
377
|
+
for ip in ips_to_insert:
|
|
378
|
+
bulk_data.append({
|
|
379
|
+
"ip": ip,
|
|
380
|
+
"added_at": now_str
|
|
381
|
+
})
|
|
382
|
+
# Cache Warm-up: Marca o IP como "VIP" no cache por 1 hora
|
|
383
|
+
# Isso evita consulta ao banco quando este IP acessar
|
|
384
|
+
waf_cache.set(f"whitelist_{ip}", True, expire=3600)
|
|
385
|
+
|
|
386
|
+
# 4. Grava no Banco
|
|
387
|
+
session.bulk_insert_mappings(Whitelist, bulk_data)
|
|
388
|
+
session.commit()
|
|
389
|
+
|
|
390
|
+
count = len(ips_to_insert)
|
|
391
|
+
print(f" * [WafaHell] Whitelist importada: {count} novos IPs.")
|
|
392
|
+
|
|
393
|
+
return jsonify({
|
|
394
|
+
"status": "success",
|
|
395
|
+
"message": f"Sucesso! {count} IPs adicionados à Whitelist."
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
except Exception as e:
|
|
399
|
+
session.rollback()
|
|
400
|
+
print(f"Erro whitelist: {e}")
|
|
401
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
|
402
|
+
finally:
|
|
403
|
+
session.close()
|
|
404
|
+
|
|
405
|
+
print(f" * [WafaHell] Dashboard e API de dados prontos em: {target_path}")
|
|
406
|
+
|