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/app.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from middleware import Wafahell
|
|
2
|
+
from flask import Flask, render_template, request
|
|
3
|
+
from werkzeug.middleware.proxy_fix import ProxyFix
|
|
4
|
+
|
|
5
|
+
app = Flask(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
app.wsgi_app = ProxyFix(
|
|
9
|
+
app.wsgi_app,
|
|
10
|
+
x_for=1,
|
|
11
|
+
x_proto=1,
|
|
12
|
+
x_host=1,
|
|
13
|
+
x_port=1
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
@app.route('/', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
|
|
17
|
+
def home():
|
|
18
|
+
return "<h1>Bem-vindo à minha aplicação Flask!</h1><p>Acesse /hello?nome=test</p>"
|
|
19
|
+
|
|
20
|
+
@app.route('/hello', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
|
|
21
|
+
def hello():
|
|
22
|
+
nome = request.args.get('nome', 'Visitante')
|
|
23
|
+
return f"<h1>Olá, {nome}!</h1>"
|
|
24
|
+
|
|
25
|
+
@app.route('/admin/dashboard')
|
|
26
|
+
def dashboard():
|
|
27
|
+
return "<h1>Dashboard Personalizado</h1><p>Este é o painel de controle personalizado.</p>"
|
|
28
|
+
|
|
29
|
+
if __name__ == '__main__':
|
|
30
|
+
Wafahell(app=app, dashboard_path='/hell/dashboard', block_durantion=15, rate_limit=True, block_ip=False)
|
|
31
|
+
app.run(debug=True, host='0.0.0.0', port=5001)
|
wafaHell/globals.py
ADDED
wafaHell/logger.py
CHANGED
|
@@ -1,34 +1,122 @@
|
|
|
1
|
+
from .model import WafLog, get_session
|
|
2
|
+
from datetime import datetime
|
|
1
3
|
import logging
|
|
4
|
+
import re
|
|
5
|
+
# Handler customizado para salvar no SQLite via SQLAlchemy
|
|
6
|
+
class SQLAlchemyHandler(logging.Handler):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super().__init__()
|
|
9
|
+
self.session = get_session()
|
|
10
|
+
# Regex para extrair dados da string formatada pelo parse_req
|
|
11
|
+
self.attr_pattern = re.compile(
|
|
12
|
+
r"Attack_type: (?P<type>.*?), IP: (?P<ip>.*?), .*?Path: (?P<path>.*?), Method: (?P<method>.*?), Payload: (?P<payload>.*?), attack_local: (?P<local>.*)"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def emit(self, record):
|
|
16
|
+
session = self.session # Certifique-se de instanciar a sessão
|
|
17
|
+
try:
|
|
18
|
+
msg = record.getMessage()
|
|
19
|
+
|
|
20
|
+
# Valores padrão
|
|
21
|
+
ip, path, method, payload, local, attack_type = (None, None, None, msg, None, "Info")
|
|
22
|
+
|
|
23
|
+
# Expandimos a condição para incluir [BLOCKED]
|
|
24
|
+
if any(tag in msg for tag in ["[ATTACK]", "[RATE LIMIT]", "[BLOCKED]"]):
|
|
25
|
+
|
|
26
|
+
def extract(key, text):
|
|
27
|
+
# Regex ajustada para capturar até a vírgula ou fim da tag, permitindo espaços internos
|
|
28
|
+
match = re.search(rf"{key}:?\s*([^,\]]+)", text)
|
|
29
|
+
return match.group(1).strip() if match else None
|
|
30
|
+
|
|
31
|
+
ip = extract("IP", msg)
|
|
32
|
+
|
|
33
|
+
if "[BLOCKED]" in msg:
|
|
34
|
+
attack_type = "IP BLOCK"
|
|
35
|
+
ua = extract("UA", msg)
|
|
36
|
+
payload = f"IP Bloqueado. User-Agent: {ua}" if ua else "IP Bloqueado"
|
|
37
|
+
path = "---"
|
|
38
|
+
method = "---"
|
|
39
|
+
elif "[RATE LIMIT]" in msg:
|
|
40
|
+
attack_type = "RATE LIMIT"
|
|
41
|
+
payload = "Exceeded request limit"
|
|
42
|
+
path = extract("Path", msg)
|
|
43
|
+
method = extract("Method", msg)
|
|
44
|
+
|
|
45
|
+
bucket = datetime.utcnow().strftime('%Y-%m-%d %H:%M')
|
|
46
|
+
log_entry = WafLog(
|
|
47
|
+
level=record.levelname,
|
|
48
|
+
attack_type=attack_type,
|
|
49
|
+
ip=ip,
|
|
50
|
+
path=path,
|
|
51
|
+
method=method,
|
|
52
|
+
payload=payload,
|
|
53
|
+
attack_local=local,
|
|
54
|
+
time_bucket=bucket
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
session.add(log_entry)
|
|
58
|
+
session.commit()
|
|
59
|
+
else:
|
|
60
|
+
# Lógica para [ATTACK]
|
|
61
|
+
attack_type = extract("Attack_type", msg) or "Unknown"
|
|
62
|
+
path = extract("Path", msg)
|
|
63
|
+
method = extract("Method", msg)
|
|
64
|
+
|
|
65
|
+
# Captura o local completo (ex: HEADER 'User-Agent')
|
|
66
|
+
local_match = re.search(r"attack_local:\s*(.+)$", msg)
|
|
67
|
+
local = local_match.group(1).strip() if local_match else extract("attack_local", msg)
|
|
68
|
+
|
|
69
|
+
# Regex específica para payloads que podem conter vírgulas
|
|
70
|
+
payload_match = re.search(r"Payload: (.*?), attack_local:", msg)
|
|
71
|
+
payload = payload_match.group(1) if payload_match else extract("Payload", msg)
|
|
72
|
+
|
|
73
|
+
if not "[RATE LIMIT]" in msg:
|
|
74
|
+
log_entry = WafLog(
|
|
75
|
+
level=record.levelname,
|
|
76
|
+
attack_type=attack_type,
|
|
77
|
+
ip=ip,
|
|
78
|
+
path=path,
|
|
79
|
+
method=method,
|
|
80
|
+
payload=payload,
|
|
81
|
+
attack_local=local,
|
|
82
|
+
# time_bucket fica None aqui para não filtrar ataques normais no banco
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
session.add(log_entry)
|
|
86
|
+
session.commit()
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
session.rollback()
|
|
90
|
+
finally:
|
|
91
|
+
session.close()
|
|
2
92
|
|
|
3
93
|
class Logger:
|
|
4
94
|
def __init__(self, name="WAF", log_file="waf.log", level=logging.INFO):
|
|
5
|
-
# Cria logger com nome
|
|
6
95
|
self.logger = logging.getLogger(name)
|
|
7
96
|
self.logger.setLevel(level)
|
|
97
|
+
self.logger.propagate = False
|
|
8
98
|
|
|
9
|
-
# Evita handlers duplicados ao reinicializar
|
|
10
99
|
if not self.logger.handlers:
|
|
11
|
-
# Handler para console
|
|
12
|
-
console_handler = logging.StreamHandler()
|
|
13
|
-
console_handler.setLevel(level)
|
|
14
|
-
|
|
15
|
-
# Handler para arquivo
|
|
16
|
-
file_handler = logging.FileHandler(log_file)
|
|
17
|
-
file_handler.setLevel(level)
|
|
18
|
-
|
|
19
|
-
# Formato
|
|
20
100
|
formatter = logging.Formatter(
|
|
21
101
|
"[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s",
|
|
22
102
|
datefmt="%H:%M:%S - %d/%m/%Y"
|
|
23
103
|
)
|
|
24
104
|
|
|
105
|
+
# Handler 1: Console
|
|
106
|
+
console_handler = logging.StreamHandler()
|
|
25
107
|
console_handler.setFormatter(formatter)
|
|
26
|
-
file_handler.setFormatter(formatter)
|
|
27
|
-
|
|
28
|
-
# Adiciona handlers
|
|
29
108
|
self.logger.addHandler(console_handler)
|
|
109
|
+
|
|
110
|
+
# Handler 2: Arquivo
|
|
111
|
+
file_handler = logging.FileHandler(log_file)
|
|
112
|
+
file_handler.setFormatter(formatter)
|
|
30
113
|
self.logger.addHandler(file_handler)
|
|
31
114
|
|
|
115
|
+
# # Handler 3: Banco de Dados (A Mágica acontece aqui)
|
|
116
|
+
# db_handler = SQLAlchemyHandler()
|
|
117
|
+
# db_handler.setLevel(logging.INFO) # Salva INFO, WARNING e acima no DB
|
|
118
|
+
# self.logger.addHandler(db_handler)
|
|
119
|
+
|
|
32
120
|
def info(self, msg):
|
|
33
121
|
self.logger.info(msg)
|
|
34
122
|
|