wafaHell 0.2.0__py3-none-any.whl → 1.0.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/model.py CHANGED
@@ -1,23 +1,100 @@
1
1
  from datetime import datetime
2
- from sqlalchemy import create_engine, Column, Integer, String
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(String, nullable=False, default=lambda: datetime.now().strftime("%H:%M:%S"))
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 f"<Blocked(ip='{self.ip}', user_agent='{self.user_agent}', blocked_at='{self.blocked_at}')>"
17
-
18
- def get_session(db_url="sqlite:///wafaHell.db"):
19
- engine = create_engine(db_url, echo=False)
20
- Base.metadata.create_all(engine)
21
- session_factory = sessionmaker(bind=engine)
22
- Session = scoped_session(session_factory)
23
- return Session
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
+
56
+ class WafLog(Base):
57
+ __tablename__ = "waf_logs"
58
+
59
+ id = Column(Integer, primary_key=True)
60
+ timestamp = Column(DateTime, default=datetime.utcnow)
61
+
62
+ time_bucket = Column(String(20), index=True)
63
+
64
+ attack_type = Column(String(50))
65
+ ip = Column(String(50))
66
+ path = Column(String(200))
67
+ method = Column(String(10))
68
+ payload = Column(Text)
69
+ attack_local = Column(String(50))
70
+ level = Column(String(20))
71
+
72
+ __table_args__ = (
73
+ UniqueConstraint('ip', 'attack_type', 'time_bucket', name='uix_ip_attack_time'),
74
+ )
75
+
76
+
77
+ class AdminUser(Base):
78
+ __tablename__ = "admin_user"
79
+
80
+ id = Column(Integer, primary_key=True, autoincrement=True)
81
+ login = Column(String(50), nullable=False, unique=True)
82
+ password = Column(String(255), nullable=False)
83
+
84
+ def __repr__(self):
85
+ return f"<AdminUser(login='{self.login}')>"
86
+
87
+
88
+ # =========================
89
+ # DB INIT / SESSION
90
+ # =========================
91
+
92
+ def init_db():
93
+ """Cria as tabelas uma única vez"""
94
+ Base.metadata.create_all(bind=engine)
95
+
96
+
97
+ def get_session():
98
+ """Retorna uma sessão reutilizável"""
99
+ return SessionLocal()
100
+
wafaHell/panel.py ADDED
@@ -0,0 +1,192 @@
1
+ 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
4
+ from sqlalchemy import func
5
+ import csv
6
+ import io
7
+ from utils import Admin, Dashboard, admin
8
+ from werkzeug.security import check_password_hash
9
+ dashboard = Dashboard()
10
+
11
+ def get_logs_and_stats(ip_filter=None, type_filter=None, limit=100):
12
+ session = get_session()
13
+ try:
14
+ log_query = session.query(WafLog).order_by(WafLog.id.desc())
15
+ stat_query = session.query(func.count(WafLog.id))
16
+
17
+ if ip_filter:
18
+ log_query = log_query.filter(WafLog.ip == ip_filter)
19
+ stat_query = stat_query.filter(WafLog.ip == ip_filter)
20
+ if type_filter:
21
+ log_query = log_query.filter(WafLog.attack_type == type_filter)
22
+ stat_query = stat_query.filter(WafLog.attack_type == type_filter)
23
+
24
+ db_logs = log_query.limit(limit).all()
25
+
26
+ # Estatísticas Dinâmicas
27
+ stats = {
28
+ "total": stat_query.scalar() or 0,
29
+ # Agora attacks conta tudo que não é 'Info' ou 'System'
30
+ "attacks": stat_query.filter(WafLog.attack_type.in_(['SQLI', 'XSS', 'RATE LIMIT'])).scalar() or 0,
31
+ "sqli": stat_query.filter(WafLog.attack_type == 'SQLI').scalar() or 0,
32
+ "xss": stat_query.filter(WafLog.attack_type == 'XSS').scalar() or 0,
33
+ "rate_limit": stat_query.filter(WafLog.attack_type == 'RATE LIMIT').scalar() or 0,
34
+ "blocks": stat_query.filter(WafLog.attack_type == 'IP BLOCK').scalar() or 0
35
+ }
36
+
37
+ formatted_logs = []
38
+ for log in db_logs:
39
+ # Determinamos o "Tipo" visual para o HTML
40
+ display_type = "INFO"
41
+ if log.attack_type in ['SQLI', 'XSS', 'RATE LIMIT']:
42
+ display_type = "ATTACK"
43
+ elif log.attack_type == 'IP BLOCK':
44
+ display_type = "BLOCKED" # Mudança aqui para o dashboard
45
+
46
+ formatted_logs.append({
47
+ "timestamp": log.timestamp.strftime("%H:%M:%S - %d/%m/%Y"),
48
+ "type": display_type,
49
+ "details": {
50
+ "attack_type": log.attack_type,
51
+ "ip": log.ip or "---",
52
+ "path": log.path or "---",
53
+ "method": log.method or "---",
54
+ "payload": log.payload or "---",
55
+ "attack_local": log.attack_local or "---"
56
+ }
57
+ })
58
+ return formatted_logs, stats
59
+ finally:
60
+ session.close()
61
+
62
+ def setup_dashboard(app, custom_path=None):
63
+ target_path = custom_path or '/admin/dashboard'
64
+
65
+ @app.route(target_path + "/login", methods=["GET", "POST"])
66
+ def login():
67
+ if request.method == "POST":
68
+ username = request.form.get("user")
69
+ password = request.form.get("password")
70
+ db = get_session()
71
+ try:
72
+ admin = db.query(AdminUser).filter(AdminUser.login == username).first()
73
+
74
+ # 3. Valida (Aqui você deveria usar hash, mas vamos focar na lógica)
75
+ if admin and check_password_hash(admin.password, password):
76
+ session["logged_in"] = True
77
+ next_page = request.args.get("next", target_path)
78
+ return redirect(next_page)
79
+ else:
80
+ flash("Acesso Negado: Credenciais Inválidas", "error")
81
+ return redirect(request.url)
82
+ finally:
83
+ db.close()
84
+
85
+ return render_template("login.html")
86
+
87
+ @app.route(target_path + '/data')
88
+ @admin
89
+ def wafahell_data():
90
+ ip_f = request.args.get('ip')
91
+ type_f = request.args.get('type')
92
+ logs, stats = get_logs_and_stats(ip_filter=ip_f, type_filter=type_f)
93
+ return jsonify({"logs": logs, "stats": stats})
94
+
95
+ # Rota 2: Retorna o HTML inicial
96
+ @app.route(target_path)
97
+ @admin
98
+ def wafahell_dashboard():
99
+ ip_f = request.args.get('ip')
100
+ type_f = request.args.get('type')
101
+ logs, stats = get_logs_and_stats(ip_filter=ip_f, type_filter=type_f)
102
+ return render_template('dashboard.html', logs=logs, stats=stats, filters={'ip': ip_f, 'type': type_f})
103
+
104
+ @app.route(target_path + '/export/csv')
105
+ @admin
106
+ 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)
118
+
119
+ logs = query.order_by(WafLog.timestamp.desc()).all()
120
+ session.close()
121
+
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
143
+
144
+ @app.route(target_path + '/block_ip', methods=['POST'])
145
+ @admin
146
+ def block_ip() -> bool:
147
+ data = request.get_json()
148
+ ip = data.get('ip')
149
+ block_time = data.get('block_time_minutes', 5) # Pega do JSON ou assume 5
150
+
151
+ if not ip:
152
+ return jsonify({"status": "error", "message": "IP ausente"}), 400
153
+
154
+ session = get_session()
155
+
156
+ try:
157
+ exists = session.query(Blocked).filter_by(ip=ip, user_agent="MANUAL_BLOCK").first()
158
+ if exists:
159
+ exists.blocked_until = datetime.now(timezone.utc) + timedelta(minutes=int(block_time))
160
+ session.commit()
161
+ return jsonify({"status": "success", "message": "Tempo de bloqueio atualizado"})
162
+
163
+ now = datetime.now(timezone.utc)
164
+ until = now + timedelta(minutes=int(block_time))
165
+
166
+ new_block = Blocked(
167
+ ip=ip,
168
+ user_agent="MANUAL_BLOCK",
169
+ blocked_until=until
170
+ )
171
+
172
+ session.add(new_block)
173
+ session.commit()
174
+ return jsonify({"status": "success", "message": "IP bloqueado com sucesso"})
175
+ except Exception as e:
176
+ session.rollback()
177
+ return jsonify({"status": "error", "message": str(e)}), 500
178
+ finally:
179
+ session.close()
180
+
181
+ @app.route(target_path + '/graphs', methods=['GET'])
182
+ @admin
183
+ def graphs():
184
+ return render_template('graphs.html')
185
+
186
+ @app.route(target_path + '/stats', methods=['GET'])
187
+ @admin
188
+ def api_stats():
189
+ return jsonify(dashboard.dashboard_setup())
190
+
191
+ print(f" * [WafaHell] Dashboard e API de dados prontos em: {target_path}")
192
+
wafaHell/rateLimiter.py CHANGED
@@ -17,5 +17,4 @@ class RateLimiter:
17
17
  self.requests_log[key].popleft()
18
18
 
19
19
  self.requests_log[key].append(now)
20
-
21
20
  return len(self.requests_log[key]) >= self.limit
wafaHell/utils.py CHANGED
@@ -1,10 +1,312 @@
1
- from datetime import datetime, timedelta
1
+ from datetime import datetime, timedelta, timezone
2
+ import secrets
3
+ import string
4
+ import time
5
+ from werkzeug.security import generate_password_hash
6
+ from sqlalchemy.orm import Session
7
+ from sqlalchemy import text, func, case
8
+ from model import WafLog, Blocked
9
+ from model import AdminUser
10
+ from functools import wraps
11
+ from flask import session, redirect, url_for, request
12
+ from model import get_session
13
+ from globals import waf_cache
14
+ import geoip2.database
15
+ import os
2
16
 
3
- def is_block_expired(blocked_time_str: str) -> bool:
4
- blocked_time = datetime.strptime(blocked_time_str, "%H:%M:%S")
5
- now = datetime.now()
6
- blocked_time = blocked_time.replace(year=now.year, month=now.month, day=now.day)
17
+ class Admin:
18
+ @staticmethod
19
+ def generate_secure_password(length=64):
20
+ alphabet = string.ascii_letters + string.digits + string.punctuation
21
+ return ''.join(secrets.choice(alphabet) for _ in range(length))
7
22
 
8
- diff = now - blocked_time
9
- return diff >= timedelta(minutes=1)
23
+ @staticmethod
24
+ def create_admin_user(session: Session):
25
+ admin = session.query(AdminUser).filter_by(login="admin").first()
26
+ if admin:
27
+ return
28
+ raw_password = "admin" #Admin.generate_secure_password(64)
29
+ hashed_password = generate_password_hash(raw_password)
10
30
 
31
+ admin = AdminUser(
32
+ login="admin",
33
+ password=hashed_password
34
+ )
35
+
36
+ session.add(admin)
37
+ session.commit()
38
+
39
+ print("* [WafaHell] Usuario admin criado com sucesso.")
40
+ print("* [WafaHell] Salve essa senha em um lugar seguro, não será mostrada novamente")
41
+ print("Senha: ", raw_password)
42
+
43
+ def admin(fn):
44
+ @wraps(fn)
45
+ def wrapper(*args, **kwargs):
46
+ print(f"DEBUG: Session logged_in status: {session.get('logged_in')}") # Adicione isso
47
+ if not session.get("logged_in"):
48
+ print("DEBUG: Redirecting to login...")
49
+ return redirect(url_for("login", next=request.path))
50
+ return fn(*args, **kwargs)
51
+ return wrapper
52
+
53
+ class Dashboard:
54
+ def __init__(self):
55
+ self.db_session = get_session()
56
+ self.geo_db_path = os.path.join(os.path.dirname(__file__), 'GeoLite2-Country.mmdb')
57
+
58
+ def dashboard_setup(self):
59
+ json = {}
60
+ def get_server_info():
61
+ server_time = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
62
+ node_id = "WAF-01"
63
+ avg_latency = waf_cache.get('latency_avg', default=0.0)
64
+ system_status = "critical" if avg_latency > 500 else "degraded" if avg_latency > 200 else "healthy"
65
+ return {
66
+ "server_time": server_time,
67
+ "node_id": node_id,
68
+ "average_latency_ms": round(float(avg_latency), 2),
69
+ "system_status": system_status
70
+ }
71
+
72
+ def get_kpis():
73
+ now = datetime.now(timezone.utc)
74
+ last_24h = now - timedelta(hours=24)
75
+ prev_24h = now - timedelta(hours=48)
76
+
77
+ # --- TOTAIS DE HOJE ---
78
+ total_today = self.db_session.query(func.count(WafLog.id)).filter(WafLog.timestamp >= last_24h).scalar() or 0
79
+ blocked_today = self.db_session.query(func.count(WafLog.id)).filter(
80
+ WafLog.timestamp >= last_24h,
81
+ WafLog.attack_type != 'INFO'
82
+ ).scalar() or 0
83
+
84
+ # --- TOTAIS DE ONTEM (Para Tendência) ---
85
+ total_yesterday = self.db_session.query(func.count(WafLog.id)).filter(
86
+ WafLog.timestamp >= prev_24h,
87
+ WafLog.timestamp < last_24h
88
+ ).scalar() or 0
89
+
90
+ blocked_yesterday = self.db_session.query(func.count(WafLog.id)).filter(
91
+ WafLog.timestamp >= prev_24h,
92
+ WafLog.timestamp < last_24h,
93
+ WafLog.attack_type != 'INFO'
94
+ ).scalar() or 0
95
+
96
+ # --- CÁLCULO DE TENDÊNCIA (%) ---
97
+ def calc_trend(current, previous):
98
+ if previous == 0:
99
+ return 100.0 if current > 0 else 0.0
100
+ return round(((current - previous) / previous * 100), 1)
101
+
102
+ trend_total = calc_trend(total_today, total_yesterday)
103
+ trend_attacks = calc_trend(blocked_today, blocked_yesterday)
104
+
105
+ # --- INFOS COMPLEMENTARES ---
106
+ from globals import waf_cache
107
+ # Latência e RPS vindos do Cache Global
108
+ last_second_timestamp = int(time.time()) - 1
109
+ rps_key = f"rps_{last_second_timestamp}"
110
+
111
+ # 2. Lê o valor real do cache para esse segundo específico
112
+ current_rps = waf_cache.get(rps_key, default=0)
113
+
114
+ # 3. Lógica do Pico (Peak)
115
+ # Aqui continuamos usando uma chave fixa 'rps_peak_hour' para guardar o recorde
116
+ peak_rps = waf_cache.get('rps_peak_hour', default=0)
117
+
118
+ if current_rps > peak_rps:
119
+ peak_rps = current_rps
120
+ # Atualiza o recorde no cache por 1 hora
121
+ waf_cache.set('rps_peak_hour', peak_rps, expire=3600)
122
+
123
+ # Dados da Blacklist
124
+ total_blacklist = self.db_session.query(func.count(Blocked.id)).scalar() or 0
125
+ # Como blocked_at é String formatada no seu modelo, comparamos com o horário
126
+ added_today = self.db_session.query(func.count(Blocked.id)).filter(
127
+ Blocked.blocked_until >= now # Um IP "adicionado hoje" é tecnicamente um ainda bloqueado
128
+ ).scalar() or 0
129
+
130
+ return {
131
+ "total_requests_24h": {
132
+ "value": total_today,
133
+ "trend_percent": trend_total
134
+ },
135
+ "attacks_mitigated_24h": {
136
+ "value": blocked_today,
137
+ "trend_percent": trend_attacks # Adicionado aqui
138
+ },
139
+ "throughput": {
140
+ "current_req_per_sec": current_rps,
141
+ "peak_last_hour": peak_rps
142
+ },
143
+ "blacklist_count": {
144
+ "total_ips": total_blacklist,
145
+ "added_today": added_today
146
+ }
147
+ }
148
+
149
+ def get_traffic_chart():
150
+ now = datetime.now(timezone.utc)
151
+ start_time = now - timedelta(minutes=40)
152
+
153
+ # Query para agrupar por minuto
154
+ # No SQLite usamos strftime, no Postgres/MySQL seria date_format ou similar
155
+ query = self.db_session.query(
156
+ func.strftime('%H:%M', WafLog.timestamp).label('minute'),
157
+ func.count(WafLog.id).label('total'),
158
+ func.sum(case({WafLog.attack_type == 'INFO': 1}, else_=0)).label('legit'),
159
+ func.sum(case({WafLog.attack_type != 'INFO': 1}, else_=0)).label('attacks')
160
+ ).filter(WafLog.timestamp >= start_time)\
161
+ .group_by('minute')\
162
+ .order_by('minute').all()
163
+
164
+ labels = []
165
+ series_legit = []
166
+ series_attacks = []
167
+
168
+ # Preenche os arrays para o gráfico
169
+ for row in query:
170
+ labels.append(row.minute)
171
+ series_legit.append(row.legit or 0)
172
+ series_attacks.append(row.attacks or 0)
173
+
174
+ # Caso não existam dados nos últimos 40 min, retorna arrays vazios para não quebrar o front
175
+ return {
176
+ "labels": labels,
177
+ "series_legit": series_legit,
178
+ "series_attacks": series_attacks
179
+ }
180
+
181
+ def get_distribution_vectors():
182
+ now = datetime.now(timezone.utc)
183
+ last_24h = now - timedelta(hours=24)
184
+
185
+ # 1. Buscamos a contagem agrupada por tipo de ataque
186
+ # Filtramos para não incluir tráfego legítimo (INFO)
187
+ query = self.db_session.query(
188
+ WafLog.attack_type,
189
+ func.count(WafLog.id).label('count')
190
+ ).filter(
191
+ WafLog.timestamp >= last_24h,
192
+ WafLog.attack_type != 'INFO'
193
+ ).group_by(WafLog.attack_type).all()
194
+
195
+ # 2. Calculamos o total de ataques para obter a porcentagem
196
+ total_attacks = sum(row.count for row in query)
197
+
198
+ distribution = []
199
+
200
+ for row in query:
201
+ percentage = round((row.count / total_attacks * 100), 1) if total_attacks > 0 else 0
202
+ distribution.append({
203
+ "label": row.attack_type,
204
+ "count": row.count,
205
+ "percentage": percentage
206
+ })
207
+
208
+ # Caso não haja ataques, retornamos uma lista vazia ou um placeholder
209
+ return distribution
210
+
211
+ def get_top_geo():
212
+ def resolve_ip(ip):
213
+ try:
214
+ if not os.path.exists(self.geo_db_path):
215
+ print("ERRO: Arquivo GeoLite2-Country.mmdb não encontrado!")
216
+ return "XX", "Unknown"
217
+
218
+ with geoip2.database.Reader(self.geo_db_path) as reader:
219
+ response = reader.country(ip)
220
+ return response.country.iso_code, response.country.name
221
+ except Exception as e:
222
+ print(f"Erro na consulta GeoIP: {e}") # Isso vai te dizer se o banco está corrompido ou o IP é inválido
223
+ return "XX", "Unknown"
224
+
225
+ now = datetime.now(timezone.utc)
226
+ last_24h = now - timedelta(hours=24)
227
+
228
+ # 1. Busca todos os ataques agrupados por IP
229
+ query = self.db_session.query(
230
+ WafLog.ip,
231
+ func.count(WafLog.id).label('count')
232
+ ).filter(
233
+ WafLog.timestamp >= last_24h,
234
+ WafLog.attack_type != 'INFO'
235
+ ).group_by(WafLog.ip).all()
236
+
237
+ geo_stats = {}
238
+ total_attacks = 0
239
+
240
+ # 2. Processa cada IP real usando o GeoIP2
241
+ for row in query:
242
+ code, name = resolve_ip(row.ip)
243
+
244
+ if code not in geo_stats:
245
+ geo_stats[code] = {"name": name, "count": 0}
246
+
247
+ geo_stats[code]["count"] += row.count
248
+ total_attacks += row.count
249
+
250
+ # 3. Formata o Top 5
251
+ top_geo = []
252
+ sorted_geo = sorted(geo_stats.items(), key=lambda x: x[1]['count'], reverse=True)[:5]
253
+
254
+ for code, data in sorted_geo:
255
+ percentage = round((data["count"] / total_attacks * 100), 1) if total_attacks > 0 else 0
256
+ top_geo.append({
257
+ "country_code": code,
258
+ "country_name": data["name"],
259
+ "count": data["count"],
260
+ "percentage": percentage
261
+ })
262
+
263
+ return top_geo
264
+
265
+ def get_top_offenders():
266
+ now = datetime.now(timezone.utc)
267
+ last_24h = now - timedelta(hours=24)
268
+
269
+ # 1. Agrupamos por IP e contamos os hits e os tipos de ataques diferentes
270
+ # Ignoramos tráfego INFO
271
+ query = self.db_session.query(
272
+ WafLog.ip,
273
+ func.count(WafLog.id).label('hits_count'),
274
+ func.count(func.distinct(WafLog.attack_type)).label('unique_vectors')
275
+ ).filter(
276
+ WafLog.timestamp >= last_24h,
277
+ WafLog.attack_type != 'INFO'
278
+ ).group_by(WafLog.ip).order_by(text('hits_count DESC')).limit(5).all()
279
+
280
+ offenders = []
281
+ for row in query:
282
+ # 2. Lógica de Risk Score (0 a 100)
283
+ # Baseada em volume e diversidade de ataques
284
+ # Ex: Cada vetor único vale 20 pontos + 1 ponto para cada 50 hits (até o teto de 100)
285
+ vector_points = row.unique_vectors * 20
286
+ hit_points = row.hits_count // 50
287
+ risk_score = min(100, vector_points + hit_points)
288
+
289
+ offenders.append({
290
+ "ip": row.ip,
291
+ "risk_score": int(risk_score),
292
+ "hits_count": row.hits_count
293
+ })
294
+
295
+ return offenders
296
+
297
+ try:
298
+ json['meta'] = get_server_info()
299
+ json['kpis'] = get_kpis()
300
+ json['traffic_chart'] = get_traffic_chart()
301
+ json['distribution_vectors'] = get_distribution_vectors()
302
+ json['top_geo'] = get_top_geo()
303
+ json['top_offenders'] = get_top_offenders()
304
+
305
+ return json
306
+
307
+ except Exception as e:
308
+ print(f"Erro no Dashboard: {e}")
309
+ return {"error": str(e)}
310
+
311
+ finally:
312
+ self.db_session.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wafaHell
3
- Version: 0.2.0
3
+ Version: 1.0.0
4
4
  Summary: Middleware WAF to Flask
5
5
  Author-email: Yago Martins <yagomartins30@gmail.com>
6
6
  License: MIT License
@@ -33,6 +33,10 @@ Requires-Python: >=3.9
33
33
  Description-Content-Type: text/markdown
34
34
  License-File: LICENSE
35
35
  Requires-Dist: Flask>=2.0
36
+ Requires-Dist: sqlalchemy>=2.0.0
37
+ Requires-Dist: requests
38
+ Requires-Dist: diskcache
39
+ Requires-Dist: geoip2
36
40
  Dynamic: license-file
37
41
 
38
42
  # WafHell
@@ -0,0 +1,15 @@
1
+ wafaHell/__init__.py,sha256=bdafr3LRK7_frr-V6umEJZUqt8RMrOiLJy72DSQfs2o,60
2
+ wafaHell/app.py,sha256=mXUCyOVrbU2Cdugh-UIDSQf-6zqvT2aazZtJQp_KKJI,980
3
+ wafaHell/globals.py,sha256=keIe0YCNsb5si0yUhUjd6nlk1s0YgfH5G7lH5-05Zws,160
4
+ wafaHell/logger.py,sha256=keT-Iw6g9w1dtrk0GhldxhgKtmNUVClgFGkSkAWXS5Y,5686
5
+ wafaHell/middleware.py,sha256=cse81wj9ocUV11MPfrmvleD5nYq6asjDx6dqXToIcIs,14348
6
+ wafaHell/mock.py,sha256=EgfSCKYV3rUTerKoEyFQPSf47wrOo-E0yqe7JpQUe3w,2105
7
+ wafaHell/model.py,sha256=OB4sEucMSgO-DBLgLhqh0oBx5fZRug23MDnRc8vS5w4,2501
8
+ wafaHell/panel.py,sha256=NpbvgXy5Regm4dQDoMapr-otW27TvC7Q-vdPrZDD-5k,7465
9
+ wafaHell/rateLimiter.py,sha256=p4IDxha-ZrRPROFf8Fa1gRTbbUTQdbD9TgBTmyMR2hQ,664
10
+ wafaHell/utils.py,sha256=v9NXo94AQiHjjCOgC38vwrlLoQOCZqp_qOjZVz00NEI,12750
11
+ wafahell-1.0.0.dist-info/licenses/LICENSE,sha256=6bv9v4HamenV3rqm3mhaGOecwGFrgxtVTW7JPfFDmeY,1086
12
+ wafahell-1.0.0.dist-info/METADATA,sha256=2EA_4vTwwoXimX_etFdZo_x_Z2_qijnbSjuIHfwE10Q,2087
13
+ wafahell-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
+ wafahell-1.0.0.dist-info/top_level.txt,sha256=VGBo2g3pOeTH2qIXfZDJCSblJgijemMHUHmI0bBgrls,9
15
+ wafahell-1.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
wafaHell/teste.py DELETED
@@ -1,16 +0,0 @@
1
- from middleware import WafaHell
2
- from flask import Flask
3
- import logging
4
-
5
- app = Flask(__name__)
6
- waf = WafaHell(app, monitor_mode=True, block_ip=True, rate_limit=True)
7
-
8
- # log = logging.getLogger('werkzeug')
9
- # log.setLevel(logging.ERROR)
10
-
11
- @app.route('/')
12
- def home():
13
- return "Bem-vindo ao site seguro!"
14
-
15
- if __name__ == '__main__':
16
- app.run(host='0.0.0.0', port=5000, debug=False)