wafaHell 1.0.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/panel.py CHANGED
@@ -1,11 +1,14 @@
1
+ from .model import AdminUser, WafLog, Whitelist, get_session, Blocked
2
+ from .globals import waf_cache
3
+ from .utils import Dashboard, admin
1
4
  from datetime import datetime, timedelta, timezone
2
- from flask import render_template_string, request, jsonify, make_response, flash, session, redirect, render_template
3
- from model import AdminUser, WafLog, get_session, Blocked
5
+ import re
6
+ from flask import request, jsonify, make_response, flash, session, redirect, render_template
4
7
  from sqlalchemy import func
5
8
  import csv
6
9
  import io
7
- from utils import Admin, Dashboard, admin
8
10
  from werkzeug.security import check_password_hash
11
+
9
12
  dashboard = Dashboard()
10
13
 
11
14
  def get_logs_and_stats(ip_filter=None, type_filter=None, limit=100):
@@ -67,9 +70,9 @@ def setup_dashboard(app, custom_path=None):
67
70
  if request.method == "POST":
68
71
  username = request.form.get("user")
69
72
  password = request.form.get("password")
70
- db = get_session()
73
+ db_session = get_session()
71
74
  try:
72
- admin = db.query(AdminUser).filter(AdminUser.login == username).first()
75
+ admin = db_session.query(AdminUser).filter(AdminUser.login == username).first()
73
76
 
74
77
  # 3. Valida (Aqui você deveria usar hash, mas vamos focar na lógica)
75
78
  if admin and check_password_hash(admin.password, password):
@@ -80,7 +83,7 @@ def setup_dashboard(app, custom_path=None):
80
83
  flash("Acesso Negado: Credenciais Inválidas", "error")
81
84
  return redirect(request.url)
82
85
  finally:
83
- db.close()
86
+ db_session.close()
84
87
 
85
88
  return render_template("login.html")
86
89
 
@@ -104,43 +107,48 @@ def setup_dashboard(app, custom_path=None):
104
107
  @app.route(target_path + '/export/csv')
105
108
  @admin
106
109
  def export_csv():
107
-
108
- ip_filter = request.args.get('ip')
109
- type_filter = request.args.get('type')
110
-
111
- session = get_session()
112
- query = session.query(WafLog)
113
-
114
- if ip_filter:
115
- query = query.filter(WafLog.ip == ip_filter)
116
- if type_filter:
117
- query = query.filter(WafLog.attack_type == type_filter)
110
+ try:
111
+ ip_filter = request.args.get('ip')
112
+ type_filter = request.args.get('type')
118
113
 
119
- logs = query.order_by(WafLog.timestamp.desc()).all()
120
- session.close()
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()
121
124
 
122
- # Criamos o CSV em memória
123
- output = io.StringIO()
124
- writer = csv.writer(output)
125
-
126
- # Cabeçalho
127
- writer.writerow(['Data/Hora', 'Tipo', 'IP', 'Endpoint', 'Metodo', 'Payload'])
128
-
129
- for log in logs:
130
- writer.writerow([
131
- log.timestamp,
132
- log.attack_type,
133
- log.ip,
134
- log.path,
135
- log.method,
136
- log.payload
137
- ])
138
-
139
- response = make_response(output.getvalue())
140
- response.headers["Content-Disposition"] = f"attachment; filename=waf_logs_{datetime.now().strftime('%Y%m%d_%H%M')}.csv"
141
- response.headers["Content-type"] = "text/csv"
142
- return response
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
+ ])
143
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
+
144
152
  @app.route(target_path + '/block_ip', methods=['POST'])
145
153
  @admin
146
154
  def block_ip() -> bool:
@@ -178,6 +186,47 @@ def setup_dashboard(app, custom_path=None):
178
186
  finally:
179
187
  session.close()
180
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
+
181
230
  @app.route(target_path + '/graphs', methods=['GET'])
182
231
  @admin
183
232
  def graphs():
@@ -187,6 +236,171 @@ def setup_dashboard(app, custom_path=None):
187
236
  @admin
188
237
  def api_stats():
189
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()
190
404
 
191
405
  print(f" * [WafaHell] Dashboard e API de dados prontos em: {target_path}")
192
406