jerkjs 2.5.8 → 2.6.1
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.
- package/CHANGELOG.md +162 -99
- package/README.md +113 -190
- package/RESULTADOS_WAF.md +63 -0
- package/doc-2.5/MANUAL_MODULOS_ADMIN.md +287 -0
- package/doc-2.5/QUEUE_CLI_MODULE_MANUAL.md +289 -0
- package/doc-2.5/QUEUE_SYSTEM_MANUAL.md +320 -0
- package/doc-2.5/ROUTE_CACHE_MODULE_MANUAL.md +205 -0
- package/doc-2.5/WAF_MODULE_MANUAL.md +229 -0
- package/index.js +12 -3
- package/jerk-admin-client/README.md +69 -0
- package/jerk-admin-client/package.json +23 -0
- package/jerk-admin-client.js +257 -0
- package/lib/admin/AdminExtension.js +74 -19
- package/lib/admin/modules/ControllerGeneratorModule.js +414 -0
- package/lib/admin/modules/QueueManagementModule.js +265 -0
- package/lib/admin/modules/RouteCacheModule.js +227 -0
- package/lib/admin/modules/RouteManagerModule.js +468 -0
- package/lib/admin/modules/STATS_MODULE_README.md +15 -0
- package/lib/admin/modules/ViewCacheStatsModule.js +92 -0
- package/lib/admin/modules/WAFModule.js +737 -0
- package/lib/core/server.js +72 -69
- package/lib/middleware/firewall.js +112 -17
- package/lib/mvc/viewEngine.js +69 -10
- package/lib/queue/GlobalQueueStorage.js +38 -0
- package/lib/queue/QueueSystem.js +451 -0
- package/lib/queue/admin_example.js +114 -0
- package/lib/queue/example.js +268 -0
- package/lib/queue/integration.js +109 -0
- package/lib/utils/globalViewCacheInfo.js +16 -0
- package/lib/utils/globalWAFStats.js +54 -0
- package/package.json +2 -2
- package/test-colors.js +46 -0
- package/test-help-alias.js +31 -0
- package/ESTADISTICAS_RENDIMIENTO.md +0 -106
- package/debug_hook.js +0 -11
- package/docs/CACHE_SYSTEM_MAP.md +0 -206
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de Web Application Firewall (WAF) para la Extensión de Administración de JERK Framework
|
|
3
|
+
* Módulo personalizado para mostrar estadísticas del firewall
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class WAFModule {
|
|
7
|
+
/**
|
|
8
|
+
* Constructor del módulo WAF
|
|
9
|
+
* @param {Object} adminExtension - Instancia de la extensión de administración
|
|
10
|
+
*/
|
|
11
|
+
constructor(adminExtension) {
|
|
12
|
+
this.adminExtension = adminExtension;
|
|
13
|
+
this.name = 'WAF Module';
|
|
14
|
+
this.description = 'Módulo para mostrar estadísticas del Web Application Firewall';
|
|
15
|
+
this.commands = ['waf-status', 'waf-stats', 'waf-blocked', 'waf-security', 'waf-block-ip', 'waf-unblock-ip', 'waf-whitelist', 'waf-blacklist', 'waf-x-headers', 'create-x-rule', 'remove-x-rule', 'save-rules', 'load-rules'];
|
|
16
|
+
|
|
17
|
+
// Mapa para almacenar el estado de procesos interactivos
|
|
18
|
+
this.currentProcess = new Map();
|
|
19
|
+
|
|
20
|
+
// Cargar el objeto global de estadísticas del firewall
|
|
21
|
+
this.globalWAFStats = require('../../utils/globalWAFStats').globalWAFStats;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Manejador para los comandos del módulo WAF
|
|
26
|
+
* @param {string} command - Comando a ejecutar
|
|
27
|
+
* @param {Object} socket - Socket de la conexión
|
|
28
|
+
*/
|
|
29
|
+
handleCommand(command, socket) {
|
|
30
|
+
switch (command) {
|
|
31
|
+
case 'waf-status':
|
|
32
|
+
case 'waf-stats':
|
|
33
|
+
this.showWAFStats(socket);
|
|
34
|
+
break;
|
|
35
|
+
case 'waf-blocked':
|
|
36
|
+
case 'waf-security':
|
|
37
|
+
this.showWAFBlockedStats(socket);
|
|
38
|
+
break;
|
|
39
|
+
case 'waf-block-ip':
|
|
40
|
+
this.startInteractiveProcess(socket, 'block_ip');
|
|
41
|
+
this.promptForIPBlock(socket);
|
|
42
|
+
break;
|
|
43
|
+
case 'waf-unblock-ip':
|
|
44
|
+
this.startInteractiveProcess(socket, 'unblock_ip');
|
|
45
|
+
this.promptForIPUnblock(socket);
|
|
46
|
+
break;
|
|
47
|
+
case 'waf-whitelist':
|
|
48
|
+
this.startInteractiveProcess(socket, 'whitelist_ip');
|
|
49
|
+
this.promptForWhitelist(socket);
|
|
50
|
+
break;
|
|
51
|
+
case 'waf-blacklist':
|
|
52
|
+
this.startInteractiveProcess(socket, 'blacklist_ip');
|
|
53
|
+
this.promptForBlacklist(socket);
|
|
54
|
+
break;
|
|
55
|
+
case 'waf-x-headers':
|
|
56
|
+
this.showXHeaderRules(socket);
|
|
57
|
+
break;
|
|
58
|
+
case 'create-x-rule':
|
|
59
|
+
this.startInteractiveProcess(socket, 'x_header_name');
|
|
60
|
+
this.promptForXHeaderRule(socket);
|
|
61
|
+
break;
|
|
62
|
+
case 'remove-x-rule':
|
|
63
|
+
this.listXHeaderRulesToRemove(socket);
|
|
64
|
+
break;
|
|
65
|
+
case 'save-rules':
|
|
66
|
+
this.saveRulesToFile(socket);
|
|
67
|
+
break;
|
|
68
|
+
case 'load-rules':
|
|
69
|
+
this.loadRulesFromFile(socket);
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
socket.write(`Comando desconocido para el módulo WAF: ${command}\n`);
|
|
73
|
+
socket.write(`Comandos disponibles: waf-status, waf-stats, waf-blocked, waf-security, waf-block-ip, waf-unblock-ip, waf-whitelist, waf-blacklist, waf-x-headers, create-x-rule, remove-x-rule, save-rules, load-rules\n\n`);
|
|
74
|
+
socket.write(`> `);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Muestra estadísticas generales del WAF
|
|
80
|
+
* @param {Object} socket - Socket de la conexión
|
|
81
|
+
*/
|
|
82
|
+
showWAFStats(socket) {
|
|
83
|
+
const formattedRequestBytes = this.formatBytes(this.globalWAFStats.requestBytes);
|
|
84
|
+
const formattedResponseBytes = this.formatBytes(this.globalWAFStats.responseBytes);
|
|
85
|
+
|
|
86
|
+
socket.write('\n=== Estadísticas del WAF ===\n');
|
|
87
|
+
socket.write(`Solicitudes procesadas: ${this.globalWAFStats.requestsProcessed}\n`);
|
|
88
|
+
socket.write(`Solicitudes procesadas (KB): ${formattedRequestBytes}\n`);
|
|
89
|
+
socket.write(`Respuestas enviadas: ${this.globalWAFStats.responsesSent}\n`);
|
|
90
|
+
socket.write(`Respuestas enviadas (KB): ${formattedResponseBytes}\n`);
|
|
91
|
+
socket.write(`IPs en lista blanca: ${this.globalWAFStats.whitelist.size}\n`);
|
|
92
|
+
socket.write(`IPs en lista negra: ${this.globalWAFStats.blacklist.size}\n`);
|
|
93
|
+
socket.write(`Reglas de seguridad activas: ${this.globalWAFStats.securityRules.size}\n\n`);
|
|
94
|
+
socket.write(`> `);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Muestra estadísticas de bloqueos del WAF
|
|
99
|
+
* @param {Object} socket - Socket de la conexión
|
|
100
|
+
*/
|
|
101
|
+
showWAFBlockedStats(socket) {
|
|
102
|
+
socket.write('\n=== Estadísticas de Seguridad del WAF ===\n');
|
|
103
|
+
socket.write(`IPs bloqueadas temporalmente: ${this.globalWAFStats.blockedIPs.size}\n`);
|
|
104
|
+
socket.write(`Intentos de ataque detectados: ${this.globalWAFStats.attackAttempts}\n`);
|
|
105
|
+
socket.write(`Tipos de ataques detectados: ${this.globalWAFStats.attackTypes.size}\n`);
|
|
106
|
+
socket.write(`Registros de seguridad: ${this.globalWAFStats.securityLogs.length}\n`);
|
|
107
|
+
|
|
108
|
+
// Mostrar accesos a rutas
|
|
109
|
+
if (this.globalWAFStats.routeAccesses.size > 0) {
|
|
110
|
+
socket.write('\nAccesos a rutas (Input/Output):\n');
|
|
111
|
+
for (const [route, count] of this.globalWAFStats.routeAccesses) {
|
|
112
|
+
socket.write(` ${route}: ${count} accesos\n`);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
socket.write('\nNo hay accesos a rutas registrados.\n');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Mostrar endpoints más accedidos
|
|
119
|
+
if (this.globalWAFStats.endpointHits.size > 0) {
|
|
120
|
+
socket.write('\nEndpoints más accedidos:\n');
|
|
121
|
+
|
|
122
|
+
// Convertir el mapa a array y ordenar por número de hits
|
|
123
|
+
const sortedEndpoints = Array.from(this.globalWAFStats.endpointHits.entries())
|
|
124
|
+
.sort((a, b) => b[1] - a[1])
|
|
125
|
+
.slice(0, 10); // Mostrar top 10
|
|
126
|
+
|
|
127
|
+
for (const [endpoint, hits] of sortedEndpoints) {
|
|
128
|
+
socket.write(` ${endpoint}: ${hits} hits\n`);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
socket.write('\nNo hay datos de endpoints accedidos.\n');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
socket.write('\n> ');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Inicia un proceso interactivo
|
|
139
|
+
* @param {Object} socket - Socket de la conexión
|
|
140
|
+
* @param {string} step - Paso del proceso
|
|
141
|
+
*/
|
|
142
|
+
startInteractiveProcess(socket, step) {
|
|
143
|
+
const sessionId = this.getSessionId(socket);
|
|
144
|
+
this.currentProcess.set(sessionId, { step });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Solicita la IP para bloquear
|
|
149
|
+
* @param {Object} socket - Socket de la conexión
|
|
150
|
+
*/
|
|
151
|
+
promptForIPBlock(socket) {
|
|
152
|
+
socket.write('\nIntroduce la IP a bloquear temporalmente (ej: 192.168.1.100):\n> ');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Solicita la IP para desbloquear
|
|
157
|
+
* @param {Object} socket - Socket de la conexión
|
|
158
|
+
*/
|
|
159
|
+
promptForIPUnblock(socket) {
|
|
160
|
+
socket.write('\nIntroduce la IP a desbloquear (ej: 192.168.1.100):\n> ');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Solicita la IP para agregar a la lista blanca
|
|
165
|
+
* @param {Object} socket - Socket de la conexión
|
|
166
|
+
*/
|
|
167
|
+
promptForWhitelist(socket) {
|
|
168
|
+
socket.write('\nIntroduce la IP a agregar a la lista blanca (ej: 192.168.1.100):\n> ');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Solicita la IP para agregar a la lista negra
|
|
173
|
+
* @param {Object} socket - Socket de la conexión
|
|
174
|
+
*/
|
|
175
|
+
promptForBlacklist(socket) {
|
|
176
|
+
socket.write('\nIntroduce la IP a agregar a la lista negra (ej: 192.168.1.100):\n> ');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Muestra las reglas de headers X-
|
|
181
|
+
* @param {Object} socket - Socket de la conexión
|
|
182
|
+
*/
|
|
183
|
+
showXHeaderRules(socket) {
|
|
184
|
+
socket.write('\n=== Reglas de Headers X- ===\n');
|
|
185
|
+
|
|
186
|
+
if (this.globalWAFStats.xHeaderRules.size === 0) {
|
|
187
|
+
socket.write('No hay reglas de headers X- configuradas.\n\n');
|
|
188
|
+
socket.write('¿Deseas crear una nueva regla? (sí/no):\n> ');
|
|
189
|
+
// Aquí se podría implementar la lógica para crear reglas interactivamente
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
socket.write(`Total de reglas: ${this.globalWAFStats.xHeaderRules.size}\n\n`);
|
|
194
|
+
|
|
195
|
+
for (const [ruleId, rule] of this.globalWAFStats.xHeaderRules) {
|
|
196
|
+
socket.write(`ID: ${ruleId}\n`);
|
|
197
|
+
socket.write(` Header: ${rule.headerName}\n`);
|
|
198
|
+
socket.write(` Patrón: ${rule.pattern}\n`);
|
|
199
|
+
socket.write(` Acción: ${rule.action}\n`);
|
|
200
|
+
socket.write(` Razón: ${rule.reason}\n\n`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
socket.write(`> `);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Solicita la creación de una regla de header X-
|
|
208
|
+
* @param {Object} socket - Socket de la conexión
|
|
209
|
+
*/
|
|
210
|
+
promptForXHeaderRule(socket) {
|
|
211
|
+
const sessionId = this.getSessionId(socket);
|
|
212
|
+
|
|
213
|
+
// Iniciar proceso interactivo para crear regla
|
|
214
|
+
this.currentProcess.set(sessionId, {
|
|
215
|
+
step: 'x_header_name',
|
|
216
|
+
action: 'create_rule'
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
socket.write('\n=== Crear Regla de Header X- ===\n');
|
|
220
|
+
socket.write('Introduce el nombre del header X- (ej: X-Forwarded-For, X-Real-IP):\n> ');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Maneja la entrada del nombre del header X-
|
|
225
|
+
* @param {Object} socket - Socket de la conexión
|
|
226
|
+
* @param {string} headerName - Nombre del header
|
|
227
|
+
*/
|
|
228
|
+
handleXHeaderName(socket, headerName) {
|
|
229
|
+
if (!headerName.toLowerCase().startsWith('x-')) {
|
|
230
|
+
socket.write('El nombre del header debe comenzar con "X-". Introduce un nombre válido:\n> ');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const sessionId = this.getSessionId(socket);
|
|
235
|
+
const processState = this.currentProcess.get(sessionId);
|
|
236
|
+
processState.headerName = headerName.toLowerCase();
|
|
237
|
+
processState.step = 'x_header_pattern';
|
|
238
|
+
|
|
239
|
+
socket.write(`\nHeader seleccionado: ${headerName}\n`);
|
|
240
|
+
socket.write('Introduce el patrón para coincidir (cadena o expresión regular):\n> ');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Maneja la entrada del patrón para la regla X-
|
|
245
|
+
* @param {Object} socket - Socket de la conexión
|
|
246
|
+
* @param {string} pattern - Patrón para coincidir
|
|
247
|
+
*/
|
|
248
|
+
handleXHeaderPattern(socket, pattern) {
|
|
249
|
+
if (!pattern) {
|
|
250
|
+
socket.write('El patrón no puede estar vacío. Introduce un patrón válido:\n> ');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const sessionId = this.getSessionId(socket);
|
|
255
|
+
const processState = this.currentProcess.get(sessionId);
|
|
256
|
+
processState.pattern = pattern;
|
|
257
|
+
processState.step = 'x_header_action';
|
|
258
|
+
|
|
259
|
+
socket.write(`\nPatrón: ${pattern}\n`);
|
|
260
|
+
socket.write('Selecciona la acción a tomar:\n');
|
|
261
|
+
socket.write('1. block - Bloquear solicitudes que coincidan\n');
|
|
262
|
+
socket.write('2. log - Registrar solicitudes que coincidan\n');
|
|
263
|
+
socket.write('3. monitor - Monitorear solicitudes que coincidan\n');
|
|
264
|
+
socket.write('Escribe el número o el nombre de la acción:\n> ');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Maneja la selección de acción para la regla X-
|
|
269
|
+
* @param {Object} socket - Socket de la conexión
|
|
270
|
+
* @param {string} action - Acción seleccionada
|
|
271
|
+
*/
|
|
272
|
+
handleXHeaderAction(socket, action) {
|
|
273
|
+
const actionMap = {
|
|
274
|
+
'1': 'block',
|
|
275
|
+
'2': 'log',
|
|
276
|
+
'3': 'monitor',
|
|
277
|
+
'block': 'block',
|
|
278
|
+
'log': 'log',
|
|
279
|
+
'monitor': 'monitor'
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const selectedAction = actionMap[action.toLowerCase()];
|
|
283
|
+
if (!selectedAction) {
|
|
284
|
+
socket.write('Acción inválida. Por favor selecciona 1 (block), 2 (log) o 3 (monitor):\n> ');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const sessionId = this.getSessionId(socket);
|
|
289
|
+
const processState = this.currentProcess.get(sessionId);
|
|
290
|
+
processState.action = selectedAction;
|
|
291
|
+
processState.step = 'x_header_reason';
|
|
292
|
+
|
|
293
|
+
socket.write(`\nAcción seleccionada: ${selectedAction}\n`);
|
|
294
|
+
socket.write('Introduce la razón para la regla (opcional):\n> ');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Maneja la entrada de la razón para la regla X-
|
|
299
|
+
* @param {Object} socket - Socket de la conexión
|
|
300
|
+
* @param {string} reason - Razón para la regla
|
|
301
|
+
*/
|
|
302
|
+
handleXHeaderReason(socket, reason) {
|
|
303
|
+
const sessionId = this.getSessionId(socket);
|
|
304
|
+
const processState = this.currentProcess.get(sessionId);
|
|
305
|
+
|
|
306
|
+
const ruleReason = reason || `Regla de header X-${processState.headerName} creada por administrador`;
|
|
307
|
+
processState.reason = ruleReason;
|
|
308
|
+
|
|
309
|
+
// Crear la regla
|
|
310
|
+
const ruleId = `xheader_${processState.headerName.replace(/[^a-z0-9]/g, '_')}_${Date.now()}`;
|
|
311
|
+
this.globalWAFStats.xHeaderRules.set(ruleId, {
|
|
312
|
+
headerName: processState.headerName,
|
|
313
|
+
pattern: processState.pattern,
|
|
314
|
+
action: processState.action,
|
|
315
|
+
reason: processState.reason,
|
|
316
|
+
createdAt: new Date().toISOString()
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
socket.write(`\n✅ Regla de header X- creada exitosamente:\n`);
|
|
320
|
+
socket.write(` ID: ${ruleId}\n`);
|
|
321
|
+
socket.write(` Header: ${processState.headerName}\n`);
|
|
322
|
+
socket.write(` Patrón: ${processState.pattern}\n`);
|
|
323
|
+
socket.write(` Acción: ${processState.action}\n`);
|
|
324
|
+
socket.write(` Razón: ${processState.reason}\n\n`);
|
|
325
|
+
|
|
326
|
+
socket.write(`> `);
|
|
327
|
+
|
|
328
|
+
// Limpiar el proceso
|
|
329
|
+
this.currentProcess.delete(sessionId);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Obtiene el ID de sesión para el socket
|
|
334
|
+
* @param {Object} socket - Socket de la conexión
|
|
335
|
+
* @returns {string} - ID de sesión
|
|
336
|
+
*/
|
|
337
|
+
getSessionId(socket) {
|
|
338
|
+
return `${socket.remoteAddress}:${socket.remotePort}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Procesa la entrada del usuario durante el proceso interactivo
|
|
343
|
+
* @param {Object} socket - Socket de la conexión
|
|
344
|
+
* @param {string} input - Entrada del usuario
|
|
345
|
+
*/
|
|
346
|
+
processInput(socket, input) {
|
|
347
|
+
const sessionId = this.getSessionId(socket);
|
|
348
|
+
const processState = this.currentProcess.get(sessionId);
|
|
349
|
+
|
|
350
|
+
if (!processState) {
|
|
351
|
+
socket.write('No hay proceso interactivo activo.\n> ');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const userInput = input.trim();
|
|
356
|
+
|
|
357
|
+
if (userInput.toLowerCase() === 'cancel' || userInput.toLowerCase() === 'back') {
|
|
358
|
+
socket.write('\nOperación cancelada.\n> ');
|
|
359
|
+
this.currentProcess.delete(sessionId);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Validar formato de IP solo para operaciones de IP
|
|
364
|
+
if (['block_ip', 'unblock_ip', 'whitelist_ip', 'blacklist_ip'].includes(processState.step)) {
|
|
365
|
+
const ipPattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
366
|
+
if (!ipPattern.test(userInput)) {
|
|
367
|
+
socket.write('\nFormato de IP inválido.\n> ');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
switch (processState.step) {
|
|
373
|
+
case 'block_ip':
|
|
374
|
+
this.handleIPBlock(socket, sessionId, userInput);
|
|
375
|
+
break;
|
|
376
|
+
case 'unblock_ip':
|
|
377
|
+
this.handleIPUnblock(socket, sessionId, userInput);
|
|
378
|
+
break;
|
|
379
|
+
case 'whitelist_ip':
|
|
380
|
+
this.handleWhitelistIP(socket, sessionId, userInput);
|
|
381
|
+
break;
|
|
382
|
+
case 'blacklist_ip':
|
|
383
|
+
this.handleBlacklistIP(socket, sessionId, userInput);
|
|
384
|
+
break;
|
|
385
|
+
case 'x_header_name':
|
|
386
|
+
this.handleXHeaderName(socket, userInput);
|
|
387
|
+
break;
|
|
388
|
+
case 'x_header_pattern':
|
|
389
|
+
this.handleXHeaderPattern(socket, userInput);
|
|
390
|
+
break;
|
|
391
|
+
case 'x_header_action':
|
|
392
|
+
this.handleXHeaderAction(socket, userInput);
|
|
393
|
+
break;
|
|
394
|
+
case 'x_header_reason':
|
|
395
|
+
this.handleXHeaderReason(socket, userInput);
|
|
396
|
+
break;
|
|
397
|
+
default:
|
|
398
|
+
socket.write(`\nEstado desconocido: ${processState.step}\n> `);
|
|
399
|
+
this.currentProcess.delete(sessionId);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Maneja el bloqueo de una IP
|
|
405
|
+
* @param {Object} socket - Socket de la conexión
|
|
406
|
+
* @param {string} sessionId - ID de la sesión
|
|
407
|
+
* @param {string} ip - IP a bloquear
|
|
408
|
+
*/
|
|
409
|
+
handleIPBlock(socket, sessionId, ip) {
|
|
410
|
+
// Agregar IP a la lista negra temporal
|
|
411
|
+
this.globalWAFStats.blockedIPs.set(ip, {
|
|
412
|
+
blockedUntil: Date.now() + 900000, // 15 minutos
|
|
413
|
+
reason: 'Bloqueada manualmente por administrador',
|
|
414
|
+
attempts: 1,
|
|
415
|
+
timestamp: new Date().toISOString()
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
socket.write(`\nIP ${ip} bloqueada temporalmente.\n\n> `);
|
|
419
|
+
this.currentProcess.delete(sessionId);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Maneja el desbloqueo de una IP
|
|
424
|
+
* @param {Object} socket - Socket de la conexión
|
|
425
|
+
* @param {string} sessionId - ID de la sesión
|
|
426
|
+
* @param {string} ip - IP a desbloquear
|
|
427
|
+
*/
|
|
428
|
+
handleIPUnblock(socket, sessionId, ip) {
|
|
429
|
+
if (this.globalWAFStats.blockedIPs.has(ip)) {
|
|
430
|
+
this.globalWAFStats.blockedIPs.delete(ip);
|
|
431
|
+
socket.write(`\nIP ${ip} desbloqueada.\n\n> `);
|
|
432
|
+
} else {
|
|
433
|
+
socket.write(`\nLa IP ${ip} no está bloqueada.\n\n> `);
|
|
434
|
+
}
|
|
435
|
+
this.currentProcess.delete(sessionId);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Maneja la adición de una IP a la lista blanca
|
|
440
|
+
* @param {Object} socket - Socket de la conexión
|
|
441
|
+
* @param {string} sessionId - ID de la sesión
|
|
442
|
+
* @param {string} ip - IP a agregar a la lista blanca
|
|
443
|
+
*/
|
|
444
|
+
handleWhitelistIP(socket, sessionId, ip) {
|
|
445
|
+
this.globalWAFStats.whitelist.add(ip);
|
|
446
|
+
socket.write(`\nIP ${ip} agregada a la lista blanca.\n\n> `);
|
|
447
|
+
this.currentProcess.delete(sessionId);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Maneja la adición de una IP a la lista negra
|
|
452
|
+
* @param {Object} socket - Socket de la conexión
|
|
453
|
+
* @param {string} sessionId - ID de la sesión
|
|
454
|
+
* @param {string} ip - IP a agregar a la lista negra
|
|
455
|
+
*/
|
|
456
|
+
handleBlacklistIP(socket, sessionId, ip) {
|
|
457
|
+
this.globalWAFStats.blacklist.add(ip);
|
|
458
|
+
// También eliminar de la lista blanca si estaba
|
|
459
|
+
this.globalWAFStats.whitelist.delete(ip);
|
|
460
|
+
// Y eliminar de la lista de bloqueadas temporalmente si estaba
|
|
461
|
+
this.globalWAFStats.blockedIPs.delete(ip);
|
|
462
|
+
|
|
463
|
+
socket.write(`\nIP ${ip} agregada a la lista negra.\n\n> `);
|
|
464
|
+
this.currentProcess.delete(sessionId);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Lista las reglas X-Header disponibles para remover
|
|
469
|
+
* @param {Object} socket - Socket de la conexión
|
|
470
|
+
*/
|
|
471
|
+
listXHeaderRulesToRemove(socket) {
|
|
472
|
+
if (this.globalWAFStats.xHeaderRules.size === 0) {
|
|
473
|
+
socket.write('\nNo hay reglas X-Header para remover.\n\n> ');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
socket.write('\n=== Reglas X-Header Disponibles para Remover ===\n');
|
|
478
|
+
const rulesArray = Array.from(this.globalWAFStats.xHeaderRules.entries());
|
|
479
|
+
|
|
480
|
+
for (let i = 0; i < rulesArray.length; i++) {
|
|
481
|
+
const [ruleId, rule] = rulesArray[i];
|
|
482
|
+
socket.write(`${i + 1}. ${ruleId}\n`);
|
|
483
|
+
socket.write(` Header: ${rule.headerName}\n`);
|
|
484
|
+
socket.write(` Patrón: ${rule.pattern}\n`);
|
|
485
|
+
socket.write(` Acción: ${rule.action}\n`);
|
|
486
|
+
socket.write(` Razón: ${rule.reason}\n\n`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
socket.write('Introduce el número de la regla a remover (o "cancel" para cancelar):\n> ');
|
|
490
|
+
|
|
491
|
+
// Iniciar proceso interactivo para remover regla
|
|
492
|
+
const sessionId = this.getSessionId(socket);
|
|
493
|
+
this.currentProcess.set(sessionId, { step: 'remove_x_rule_selection' });
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Guarda las reglas a un archivo
|
|
498
|
+
* @param {Object} socket - Socket de la conexión
|
|
499
|
+
*/
|
|
500
|
+
saveRulesToFile(socket) {
|
|
501
|
+
const fs = require('fs');
|
|
502
|
+
const path = require('path');
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
// Convertir las reglas a un formato serializable
|
|
506
|
+
const rulesData = {
|
|
507
|
+
xHeaderRules: Array.from(this.globalWAFStats.xHeaderRules.entries()),
|
|
508
|
+
timestamp: new Date().toISOString(),
|
|
509
|
+
version: '2.5.8'
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const rulesDir = path.join(process.cwd(), 'waf-rules');
|
|
513
|
+
if (!fs.existsSync(rulesDir)) {
|
|
514
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const filePath = path.join(rulesDir, `waf-rules-${Date.now()}.json`);
|
|
518
|
+
fs.writeFileSync(filePath, JSON.stringify(rulesData, null, 2));
|
|
519
|
+
|
|
520
|
+
socket.write(`\n✅ Reglas guardadas exitosamente en: ${filePath}\n`);
|
|
521
|
+
socket.write(`Total de reglas guardadas: ${this.globalWAFStats.xHeaderRules.size}\n\n> `);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
socket.write(`\n❌ Error al guardar reglas: ${error.message}\n\n> `);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Carga reglas desde un archivo
|
|
529
|
+
* @param {Object} socket - Socket de la conexión
|
|
530
|
+
*/
|
|
531
|
+
loadRulesFromFile(socket) {
|
|
532
|
+
const fs = require('fs');
|
|
533
|
+
const path = require('path');
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
const rulesDir = path.join(process.cwd(), 'waf-rules');
|
|
537
|
+
if (!fs.existsSync(rulesDir)) {
|
|
538
|
+
socket.write('\nNo hay directorio de reglas para cargar.\n\n> ');
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const files = fs.readdirSync(rulesDir);
|
|
543
|
+
const jsonFiles = files.filter(file => file.endsWith('.json'));
|
|
544
|
+
|
|
545
|
+
if (jsonFiles.length === 0) {
|
|
546
|
+
socket.write('\nNo hay archivos de reglas para cargar.\n\n> ');
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
socket.write('\n=== Archivos de Reglas Disponibles ===\n');
|
|
551
|
+
for (let i = 0; i < jsonFiles.length; i++) {
|
|
552
|
+
socket.write(`${i + 1}. ${jsonFiles[i]}\n`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
socket.write('\nIntroduce el número del archivo a cargar (o "cancel" para cancelar):\n> ');
|
|
556
|
+
|
|
557
|
+
// Iniciar proceso interactivo para cargar regla
|
|
558
|
+
const sessionId = this.getSessionId(socket);
|
|
559
|
+
this.currentProcess.set(sessionId, { step: 'load_rules_selection', files: jsonFiles });
|
|
560
|
+
} catch (error) {
|
|
561
|
+
socket.write(`\n❌ Error al cargar reglas: ${error.message}\n\n> `);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Formatea bytes a una unidad legible
|
|
567
|
+
* @param {number} bytes - Cantidad en bytes
|
|
568
|
+
* @returns {string} - Cantidad formateada con unidad
|
|
569
|
+
*/
|
|
570
|
+
formatBytes(bytes) {
|
|
571
|
+
// Manejar valores nulos o indefinidos
|
|
572
|
+
if (bytes == null || isNaN(bytes) || bytes === 0) return '0 Bytes';
|
|
573
|
+
const k = 1024;
|
|
574
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
575
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
576
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Procesa la entrada del usuario durante el proceso interactivo
|
|
581
|
+
* @param {Object} socket - Socket de la conexión
|
|
582
|
+
* @param {string} input - Entrada del usuario
|
|
583
|
+
*/
|
|
584
|
+
processInput(socket, input) {
|
|
585
|
+
const sessionId = this.getSessionId(socket);
|
|
586
|
+
const processState = this.currentProcess.get(sessionId);
|
|
587
|
+
|
|
588
|
+
if (!processState) {
|
|
589
|
+
socket.write('No hay proceso interactivo activo.\n> ');
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const userInput = input.trim();
|
|
594
|
+
|
|
595
|
+
if (userInput.toLowerCase() === 'cancel') {
|
|
596
|
+
socket.write('\nOperación cancelada.\n> ');
|
|
597
|
+
this.currentProcess.delete(sessionId);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
switch (processState.step) {
|
|
602
|
+
case 'x_header_name':
|
|
603
|
+
this.handleXHeaderName(socket, userInput);
|
|
604
|
+
break;
|
|
605
|
+
case 'x_header_pattern':
|
|
606
|
+
this.handleXHeaderPattern(socket, userInput);
|
|
607
|
+
break;
|
|
608
|
+
case 'x_header_action':
|
|
609
|
+
this.handleXHeaderAction(socket, userInput);
|
|
610
|
+
break;
|
|
611
|
+
case 'x_header_reason':
|
|
612
|
+
this.handleXHeaderReason(socket, userInput);
|
|
613
|
+
break;
|
|
614
|
+
case 'remove_x_rule_selection':
|
|
615
|
+
this.handleRemoveXRuleSelection(socket, sessionId, userInput);
|
|
616
|
+
break;
|
|
617
|
+
case 'load_rules_selection':
|
|
618
|
+
this.handleLoadRulesSelection(socket, sessionId, userInput, processState.files);
|
|
619
|
+
break;
|
|
620
|
+
case 'confirm_load_rules':
|
|
621
|
+
// Actualizar el estado para usar el método correcto
|
|
622
|
+
this.currentProcess.set(sessionId, {
|
|
623
|
+
step: 'waiting_confirmation',
|
|
624
|
+
rulesData: processState.rulesData,
|
|
625
|
+
filePath: processState.filePath
|
|
626
|
+
});
|
|
627
|
+
if (userInput.toLowerCase() === 'sí' || userInput.toLowerCase() === 'si' || userInput.toLowerCase() === 'yes') {
|
|
628
|
+
// Reemplazar las reglas actuales con las del archivo
|
|
629
|
+
this.globalWAFStats.xHeaderRules.clear();
|
|
630
|
+
for (const [ruleId, rule] of processState.rulesData) {
|
|
631
|
+
this.globalWAFStats.xHeaderRules.set(ruleId, rule);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
socket.write(`\n✅ Reglas cargadas exitosamente desde: ${processState.filePath}\n`);
|
|
635
|
+
socket.write(`Total de reglas cargadas: ${processState.rulesData.length}\n\n> `);
|
|
636
|
+
} else {
|
|
637
|
+
socket.write('\nCarga de reglas cancelada.\n\n> ');
|
|
638
|
+
}
|
|
639
|
+
this.currentProcess.delete(sessionId);
|
|
640
|
+
break;
|
|
641
|
+
default:
|
|
642
|
+
socket.write(`\nEstado desconocido: ${processState.step}\n> `);
|
|
643
|
+
this.currentProcess.delete(sessionId);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Maneja la selección de regla X-Header a remover
|
|
649
|
+
* @param {Object} socket - Socket de la conexión
|
|
650
|
+
* @param {string} sessionId - ID de la sesión
|
|
651
|
+
* @param {string} selection - Selección del usuario
|
|
652
|
+
*/
|
|
653
|
+
handleRemoveXRuleSelection(socket, sessionId, selection) {
|
|
654
|
+
const rulesArray = Array.from(this.globalWAFStats.xHeaderRules.entries());
|
|
655
|
+
const ruleIndex = parseInt(selection) - 1;
|
|
656
|
+
|
|
657
|
+
if (isNaN(ruleIndex) || ruleIndex < 0 || ruleIndex >= rulesArray.length) {
|
|
658
|
+
socket.write('\nNúmero de regla inválido.\n> ');
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const [ruleId] = rulesArray[ruleIndex];
|
|
663
|
+
|
|
664
|
+
// Remover la regla
|
|
665
|
+
this.globalWAFStats.xHeaderRules.delete(ruleId);
|
|
666
|
+
|
|
667
|
+
socket.write(`\n✅ Regla "${ruleId}" removida exitosamente.\n\n> `);
|
|
668
|
+
this.currentProcess.delete(sessionId);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Maneja la selección de archivo de reglas a cargar
|
|
673
|
+
* @param {Object} socket - Socket de la conexión
|
|
674
|
+
* @param {string} sessionId - ID de la sesión
|
|
675
|
+
* @param {string} selection - Selección del usuario
|
|
676
|
+
* @param {Array} files - Lista de archivos disponibles
|
|
677
|
+
*/
|
|
678
|
+
handleLoadRulesSelection(socket, sessionId, selection, files) {
|
|
679
|
+
const fileIndex = parseInt(selection) - 1;
|
|
680
|
+
|
|
681
|
+
if (isNaN(fileIndex) || fileIndex < 0 || fileIndex >= files.length) {
|
|
682
|
+
socket.write('\nNúmero de archivo inválido.\n> ');
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const fs = require('fs');
|
|
687
|
+
const path = require('path');
|
|
688
|
+
const filePath = path.join(process.cwd(), 'waf-rules', files[fileIndex]);
|
|
689
|
+
|
|
690
|
+
try {
|
|
691
|
+
const rulesData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
692
|
+
|
|
693
|
+
// Cargar las reglas
|
|
694
|
+
if (rulesData.xHeaderRules && Array.isArray(rulesData.xHeaderRules)) {
|
|
695
|
+
// Limpiar reglas existentes si se desea
|
|
696
|
+
socket.write('\n¿Deseas reemplazar las reglas actuales? (sí/no):\n> ');
|
|
697
|
+
|
|
698
|
+
// Actualizar el estado para esperar confirmación
|
|
699
|
+
this.currentProcess.set(sessionId, {
|
|
700
|
+
step: 'confirm_load_rules',
|
|
701
|
+
rulesData: rulesData.xHeaderRules,
|
|
702
|
+
filePath: filePath
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
} catch (error) {
|
|
706
|
+
socket.write(`\n❌ Error al cargar archivo: ${error.message}\n\n> `);
|
|
707
|
+
this.currentProcess.delete(sessionId);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Maneja la confirmación de carga de reglas
|
|
713
|
+
* @param {Object} socket - Socket de la conexión
|
|
714
|
+
* @param {string} sessionId - ID de la sesión
|
|
715
|
+
* @param {string} confirmation - Confirmación del usuario
|
|
716
|
+
* @param {Array} rulesData - Datos de reglas a cargar
|
|
717
|
+
* @param {string} filePath - Ruta del archivo
|
|
718
|
+
*/
|
|
719
|
+
handleLoadRulesConfirmation(socket, sessionId, confirmation, rulesData, filePath) {
|
|
720
|
+
if (confirmation.toLowerCase() === 'sí' || confirmation.toLowerCase() === 'si' || confirmation.toLowerCase() === 'yes') {
|
|
721
|
+
// Reemplazar las reglas actuales con las del archivo
|
|
722
|
+
this.globalWAFStats.xHeaderRules.clear();
|
|
723
|
+
for (const [ruleId, rule] of rulesData) {
|
|
724
|
+
this.globalWAFStats.xHeaderRules.set(ruleId, rule);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
socket.write(`\n✅ Reglas cargadas exitosamente desde: ${filePath}\n`);
|
|
728
|
+
socket.write(`Total de reglas cargadas: ${rulesData.length}\n\n> `);
|
|
729
|
+
} else {
|
|
730
|
+
socket.write('\nCarga de reglas cancelada.\n\n> ');
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
this.currentProcess.delete(sessionId);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
module.exports = WAFModule;
|