jerkjs 2.0.0
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/LICENSE +200 -0
- package/README.md +171 -0
- package/doc/EXTENSION_MANUAL.md +958 -0
- package/doc/FIREWALL_MANUAL.md +419 -0
- package/doc/HOOKS_REFERENCE_IMPROVED.md +599 -0
- package/doc/MANUAL_API_SDK.md +539 -0
- package/doc/MANUAL_MVC.md +397 -0
- package/doc/MARIADB_TOKENS_IMPLEMENTATION.md +113 -0
- package/doc/MIDDLEWARE_MANUAL.md +521 -0
- package/doc/OAUTH2_GOOGLE_MANUAL.md +408 -0
- package/doc/frontend-and-sessions.md +356 -0
- package/examples/advanced/controllers/productController.js +64 -0
- package/examples/advanced/controllers/userController.js +85 -0
- package/examples/advanced/routes.json +51 -0
- package/examples/advanced_example.js +93 -0
- package/examples/basic/controllers/userController.js +85 -0
- package/examples/basic_example.js +72 -0
- package/examples/frontend/README.md +71 -0
- package/examples/frontend/app.js +71 -0
- package/examples/frontend/controllers/apiController.js +39 -0
- package/examples/frontend/controllers/authController.js +220 -0
- package/examples/frontend/controllers/formController.js +47 -0
- package/examples/frontend/controllers/messageController.js +96 -0
- package/examples/frontend/controllers/pageController.js +178 -0
- package/examples/frontend/controllers/staticController.js +167 -0
- package/examples/frontend/routes.json +90 -0
- package/examples/mvc_example/app.js +138 -0
- package/examples/mvc_example/views/home/index.html +26 -0
- package/examples/mvc_example/views/home/simple.html +3 -0
- package/examples/mvc_example/views/layout.html +23 -0
- package/examples/mvc_example/views/test.html +3 -0
- package/examples/mvc_example/views/user/invalid.html +6 -0
- package/examples/mvc_example/views/user/list.html +36 -0
- package/examples/mvc_example/views/user/notfound.html +6 -0
- package/examples/mvc_example/views/user/profile.html +11 -0
- package/examples/mvc_routes_example/app.js +34 -0
- package/examples/mvc_routes_example/controllers/mainController.js +27 -0
- package/examples/mvc_routes_example/controllers/productController.js +47 -0
- package/examples/mvc_routes_example/controllers/userController.js +76 -0
- package/examples/mvc_routes_example/routes.json +30 -0
- package/examples/mvc_routes_example/views/layout.html +31 -0
- package/examples/mvc_routes_example/views/main/index.html +11 -0
- package/examples/mvc_routes_example/views/product/catalog.html +24 -0
- package/examples/mvc_routes_example/views/user/invalid.html +6 -0
- package/examples/mvc_routes_example/views/user/list.html +40 -0
- package/examples/mvc_routes_example/views/user/notfound.html +6 -0
- package/examples/mvc_routes_example/views/user/profile.html +18 -0
- package/examples/public/README.md +92 -0
- package/examples/public/app.js +72 -0
- package/examples/public/controllers/healthController.js +20 -0
- package/examples/public/controllers/mainController.js +22 -0
- package/examples/public/controllers/userController.js +139 -0
- package/examples/public/routes.json +51 -0
- package/examples/v2/README.md +72 -0
- package/examples/v2/app.js +74 -0
- package/examples/v2/app_fixed.js +74 -0
- package/examples/v2/controllers/authController.js +64 -0
- package/examples/v2/controllers/mainController.js +24 -0
- package/examples/v2/controllers/protectedController.js +12 -0
- package/examples/v2/controllers/userController.js +16 -0
- package/examples/v2/package.json +27 -0
- package/examples/v2/routes.json +30 -0
- package/examples/v2/test_api.sh +47 -0
- package/examples/v2/tokens_example.sqlite +0 -0
- package/examples/v2.1_firewall_demo/README.md +113 -0
- package/examples/v2.1_firewall_demo/app.js +182 -0
- package/examples/v2.1_firewall_demo/package.json +27 -0
- package/examples/v2.1_hooks_demo/README.md +85 -0
- package/examples/v2.1_hooks_demo/app.js +101 -0
- package/examples/v2.1_hooks_demo/controllers/hooksController.js +29 -0
- package/examples/v2.1_hooks_demo/controllers/mainController.js +18 -0
- package/examples/v2.1_hooks_demo/package.json +27 -0
- package/examples/v2.1_hooks_demo/routes.json +16 -0
- package/examples/v2.1_openapi_demo/README.md +82 -0
- package/examples/v2.1_openapi_demo/app.js +296 -0
- package/examples/v2.1_openapi_demo/package.json +26 -0
- package/examples/v2_cors/README.md +82 -0
- package/examples/v2_cors/app.js +108 -0
- package/examples/v2_cors/package.json +23 -0
- package/examples/v2_json_auth/README.md +83 -0
- package/examples/v2_json_auth/app.js +72 -0
- package/examples/v2_json_auth/controllers/authController.js +67 -0
- package/examples/v2_json_auth/controllers/mainController.js +16 -0
- package/examples/v2_json_auth/controllers/protectedController.js +12 -0
- package/examples/v2_json_auth/controllers/tokenController.js +28 -0
- package/examples/v2_json_auth/controllers/userController.js +15 -0
- package/examples/v2_json_auth/package.json +26 -0
- package/examples/v2_json_auth/routes.json +37 -0
- package/examples/v2_json_auth/tokens.json +20 -0
- package/examples/v2_mariadb_auth/README.md +94 -0
- package/examples/v2_mariadb_auth/app.js +81 -0
- package/examples/v2_mariadb_auth/controllers/authController.js +95 -0
- package/examples/v2_mariadb_auth/controllers/mainController.js +31 -0
- package/examples/v2_mariadb_auth/controllers/protectedController.js +12 -0
- package/examples/v2_mariadb_auth/controllers/userController.js +17 -0
- package/examples/v2_mariadb_auth/package.json +27 -0
- package/examples/v2_mariadb_auth/routes.json +37 -0
- package/examples/v2_no_auth/README.md +75 -0
- package/examples/v2_no_auth/app.js +72 -0
- package/examples/v2_no_auth/controllers/healthController.js +14 -0
- package/examples/v2_no_auth/controllers/mainController.js +19 -0
- package/examples/v2_no_auth/controllers/productController.js +31 -0
- package/examples/v2_no_auth/controllers/publicController.js +16 -0
- package/examples/v2_no_auth/package.json +22 -0
- package/examples/v2_no_auth/routes.json +37 -0
- package/examples/v2_oauth/README.md +70 -0
- package/examples/v2_oauth/app.js +90 -0
- package/examples/v2_oauth/controllers/mainController.js +45 -0
- package/examples/v2_oauth/controllers/oauthController.js +247 -0
- package/examples/v2_oauth/controllers/protectedController.js +13 -0
- package/examples/v2_oauth/controllers/userController.js +17 -0
- package/examples/v2_oauth/package.json +26 -0
- package/examples/v2_oauth/routes.json +44 -0
- package/examples/v2_openapi/README.md +77 -0
- package/examples/v2_openapi/app.js +222 -0
- package/examples/v2_openapi/controllers/authController.js +52 -0
- package/examples/v2_openapi/controllers/mainController.js +26 -0
- package/examples/v2_openapi/controllers/productController.js +17 -0
- package/examples/v2_openapi/controllers/userController.js +27 -0
- package/examples/v2_openapi/package.json +26 -0
- package/examples/v2_openapi/routes.json +37 -0
- package/generate_token.js +10 -0
- package/index.js +85 -0
- package/jerk.jpg +0 -0
- package/lib/core/handler.js +86 -0
- package/lib/core/hooks.js +224 -0
- package/lib/core/router.js +204 -0
- package/lib/core/securityEnhancedServer.js +752 -0
- package/lib/core/server.js +369 -0
- package/lib/loader/controllerLoader.js +175 -0
- package/lib/loader/routeLoader.js +341 -0
- package/lib/middleware/auditLogger.js +208 -0
- package/lib/middleware/authenticator.js +565 -0
- package/lib/middleware/compressor.js +218 -0
- package/lib/middleware/cors.js +135 -0
- package/lib/middleware/firewall.js +443 -0
- package/lib/middleware/rateLimiter.js +210 -0
- package/lib/middleware/session.js +301 -0
- package/lib/middleware/validator.js +193 -0
- package/lib/mvc/controllerBase.js +207 -0
- package/lib/mvc/viewEngine.js +752 -0
- package/lib/utils/configParser.js +223 -0
- package/lib/utils/logger.js +145 -0
- package/lib/utils/mariadbTokenAdapter.js +226 -0
- package/lib/utils/openapiGenerator.js +140 -0
- package/lib/utils/sqliteTokenAdapter.js +224 -0
- package/lib/utils/tokenManager.js +254 -0
- package/package.json +47 -0
- package/v2examplle/v2_json_auth/README.md +83 -0
- package/v2examplle/v2_json_auth/app.js +72 -0
- package/v2examplle/v2_json_auth/controllers/authController.js +67 -0
- package/v2examplle/v2_json_auth/controllers/mainController.js +16 -0
- package/v2examplle/v2_json_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_json_auth/controllers/tokenController.js +28 -0
- package/v2examplle/v2_json_auth/controllers/userController.js +15 -0
- package/v2examplle/v2_json_auth/package.json +26 -0
- package/v2examplle/v2_json_auth/routes.json +37 -0
- package/v2examplle/v2_json_auth/tokens.json +20 -0
- package/v2examplle/v2_mariadb_auth/README.md +94 -0
- package/v2examplle/v2_mariadb_auth/app.js +81 -0
- package/v2examplle/v2_mariadb_auth/controllers/authController.js +95 -0
- package/v2examplle/v2_mariadb_auth/controllers/mainController.js +31 -0
- package/v2examplle/v2_mariadb_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_mariadb_auth/controllers/userController.js +17 -0
- package/v2examplle/v2_mariadb_auth/package.json +27 -0
- package/v2examplle/v2_mariadb_auth/routes.json +37 -0
- package/v2examplle/v2_sqlite_auth/README.md +72 -0
- package/v2examplle/v2_sqlite_auth/app.js +74 -0
- package/v2examplle/v2_sqlite_auth/app_fixed.js +74 -0
- package/v2examplle/v2_sqlite_auth/controllers/authController.js +64 -0
- package/v2examplle/v2_sqlite_auth/controllers/mainController.js +24 -0
- package/v2examplle/v2_sqlite_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_sqlite_auth/controllers/userController.js +16 -0
- package/v2examplle/v2_sqlite_auth/package.json +27 -0
- package/v2examplle/v2_sqlite_auth/routes.json +30 -0
- package/v2examplle/v2_sqlite_auth/test_api.sh +47 -0
- package/v2examplle/v2_sqlite_auth/tokens_example.sqlite +0 -0
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementación completa del sistema de seguridad avanzada (WAF)
|
|
3
|
+
* usando el sistema de hooks y filters para extensibilidad
|
|
4
|
+
* Web Application Firewall con capacidades de detección y prevención de ataques
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const APIServer = require('../core/server');
|
|
8
|
+
const Authenticator = require('../middleware/authenticator');
|
|
9
|
+
const RateLimiter = require('../middleware/rateLimiter');
|
|
10
|
+
const { Logger } = require('../utils/logger');
|
|
11
|
+
const HookSystem = require('./hooks');
|
|
12
|
+
|
|
13
|
+
class SecurityEnhancedServer {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.server = new APIServer(options);
|
|
16
|
+
this.logger = new Logger({ level: 'info' });
|
|
17
|
+
this.authenticator = new Authenticator({ logger: this.logger });
|
|
18
|
+
this.rateLimiter = new RateLimiter(options.rateLimiter || {});
|
|
19
|
+
this.hooks = new HookSystem();
|
|
20
|
+
|
|
21
|
+
// Inicializar componentes de seguridad
|
|
22
|
+
this.attackDetector = new AttackDetector({ logger: this.logger });
|
|
23
|
+
this.clientFingerprinter = new ClientFingerprinter({ logger: this.logger });
|
|
24
|
+
this.firewall = new Firewall({
|
|
25
|
+
logger: this.logger,
|
|
26
|
+
maxAttempts: options.maxAttempts || 5,
|
|
27
|
+
blockDuration: options.blockDuration || 900000, // 15 minutos
|
|
28
|
+
whitelist: options.whitelist || [],
|
|
29
|
+
blacklist: options.blacklist || [],
|
|
30
|
+
rules: options.rules || []
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Registrar hooks y filtros de seguridad
|
|
34
|
+
this.registerSecurityHooks();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Registra los hooks y filtros de seguridad
|
|
39
|
+
*/
|
|
40
|
+
registerSecurityHooks() {
|
|
41
|
+
// Hook para detectar posibles ataques antes de procesar la solicitud
|
|
42
|
+
this.hooks.addAction('before_request_processing', async (req, res) => {
|
|
43
|
+
// Usar el detector de ataque avanzado
|
|
44
|
+
const attackResult = this.attackDetector.detect(req);
|
|
45
|
+
|
|
46
|
+
if (attackResult) {
|
|
47
|
+
this.logger.warn(`Patrón de ataque detectado: ${attackResult} desde IP: ${this.getClientIP(req)}`);
|
|
48
|
+
|
|
49
|
+
// Disparar hook de evento de seguridad
|
|
50
|
+
this.hooks.doAction('security_attack_detected', attackResult, req, res);
|
|
51
|
+
|
|
52
|
+
// Bloquear IP
|
|
53
|
+
this.firewall.blockIP(this.getClientIP(req), attackResult);
|
|
54
|
+
|
|
55
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
56
|
+
res.end(JSON.stringify({ error: 'Solicitud bloqueada por seguridad', reason: attackResult }));
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Filtro para mejorar la huella digital del cliente
|
|
64
|
+
this.hooks.addFilter('client_fingerprint', (fingerprint, req) => {
|
|
65
|
+
const enhancedFingerprint = {
|
|
66
|
+
...fingerprint,
|
|
67
|
+
userAgentHash: this.hashString(req.headers['user-agent'] || ''),
|
|
68
|
+
acceptHeaders: req.headers['accept'] || '',
|
|
69
|
+
language: req.headers['accept-language'] || '',
|
|
70
|
+
encoding: req.headers['accept-encoding'] || '',
|
|
71
|
+
contentType: req.headers['content-type'] || '',
|
|
72
|
+
forwardedFor: req.headers['x-forwarded-for'] || '',
|
|
73
|
+
realIP: req.headers['x-real-ip'] || '',
|
|
74
|
+
// Agregar información adicional para WAF
|
|
75
|
+
acceptCharset: req.headers['accept-charset'] || '',
|
|
76
|
+
connection: req.headers['connection'] || '',
|
|
77
|
+
pragma: req.headers['pragma'] || '',
|
|
78
|
+
cacheControl: req.headers['cache-control'] || '',
|
|
79
|
+
userAgentTokens: this.tokenizeUserAgent(req.headers['user-agent'] || ''),
|
|
80
|
+
suspiciousHeaders: this.detectSuspiciousHeaders(req.headers)
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Permitir que otros módulos modifiquen la huella digital
|
|
84
|
+
return this.hooks.applyFilters('enhanced_client_fingerprint', enhancedFingerprint, req);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Hook para aplicar firewall a la solicitud
|
|
88
|
+
this.hooks.addAction('request_validation', (req, res) => {
|
|
89
|
+
const clientIP = this.getClientIP(req);
|
|
90
|
+
|
|
91
|
+
// Verificar si la IP está bloqueada
|
|
92
|
+
const blockInfo = this.firewall.isBlocked(clientIP);
|
|
93
|
+
if (blockInfo.blocked) {
|
|
94
|
+
this.logger.warn(`Solicitud bloqueada desde IP: ${clientIP}, razón: ${blockInfo.reason}`);
|
|
95
|
+
|
|
96
|
+
// Disparar hook de evento de seguridad
|
|
97
|
+
this.hooks.doAction('security_ip_blocked', clientIP, blockInfo, req, res);
|
|
98
|
+
|
|
99
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
100
|
+
res.end(JSON.stringify({
|
|
101
|
+
error: 'IP bloqueada por violaciones de seguridad',
|
|
102
|
+
reason: blockInfo.reason,
|
|
103
|
+
blockedUntil: blockInfo.blockedUntil
|
|
104
|
+
}));
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Verificar reglas personalizadas de firewall
|
|
109
|
+
const ruleMatch = this.firewall.checkRules(req);
|
|
110
|
+
if (ruleMatch) {
|
|
111
|
+
this.logger.warn(`Regla de firewall activada: ${ruleMatch.rule} para IP: ${clientIP}, razón: ${ruleMatch.reason}`);
|
|
112
|
+
|
|
113
|
+
if (ruleMatch.action === 'block') {
|
|
114
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
115
|
+
res.end(JSON.stringify({
|
|
116
|
+
error: 'Solicitud bloqueada por regla de firewall',
|
|
117
|
+
reason: ruleMatch.reason,
|
|
118
|
+
rule: ruleMatch.rule
|
|
119
|
+
}));
|
|
120
|
+
return false;
|
|
121
|
+
} else if (ruleMatch.action === 'monitor') {
|
|
122
|
+
// Registrar pero permitir continuar
|
|
123
|
+
this.logger.info(`Solicitud monitoreada por regla: ${ruleMatch.rule}, IP: ${clientIP}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return true;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Hook para aplicar rate limiting basado en huella digital
|
|
131
|
+
this.hooks.addAction('apply_rate_limiting', (req, res, next) => {
|
|
132
|
+
// Obtener huella digital del cliente
|
|
133
|
+
const fingerprint = this.clientFingerprinter.generate(req);
|
|
134
|
+
const enhancedFingerprint = this.hooks.applyFilters('client_fingerprint', fingerprint, req);
|
|
135
|
+
const clientId = this.generateClientId(enhancedFingerprint);
|
|
136
|
+
|
|
137
|
+
// Aplicar rate limiting basado en huella digital
|
|
138
|
+
this.applyRateLimiting(clientId, req, res, next);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Hook para auditoría de seguridad
|
|
142
|
+
this.hooks.addAction('security_audit', (req, res, action, details) => {
|
|
143
|
+
const auditLog = {
|
|
144
|
+
timestamp: new Date().toISOString(),
|
|
145
|
+
clientIP: this.getClientIP(req),
|
|
146
|
+
userAgent: req.headers['user-agent'],
|
|
147
|
+
method: req.method,
|
|
148
|
+
url: req.url,
|
|
149
|
+
action: action,
|
|
150
|
+
details: details,
|
|
151
|
+
fingerprint: this.clientFingerprinter.generate(req)
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Disparar hook para procesamiento de logs de auditoría
|
|
155
|
+
this.hooks.doAction('security_log_recorded', auditLog);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Aplica rate limiting basado en huella digital
|
|
161
|
+
*/
|
|
162
|
+
applyRateLimiting(clientId, req, res, next) {
|
|
163
|
+
// Permitir que otros módulos modifiquen el comportamiento de rate limiting
|
|
164
|
+
const rateLimitResult = this.hooks.applyFilters('modify_rate_limit_behavior', {
|
|
165
|
+
clientId,
|
|
166
|
+
currentCount: this.getCurrentRequestCount(clientId),
|
|
167
|
+
maxRequests: this.rateLimiter.maxRequests,
|
|
168
|
+
windowMs: this.rateLimiter.windowMs
|
|
169
|
+
}, req);
|
|
170
|
+
|
|
171
|
+
if (rateLimitResult.currentCount >= rateLimitResult.maxRequests) {
|
|
172
|
+
// Disparar hook de evento de seguridad
|
|
173
|
+
this.hooks.doAction('rate_limit_exceeded', clientId, req, res);
|
|
174
|
+
|
|
175
|
+
const timeLeft = this.getTimeUntilReset(clientId);
|
|
176
|
+
if (!res.headersSent) {
|
|
177
|
+
res.writeHead(429, { 'Content-Type': 'application/json' });
|
|
178
|
+
res.end(JSON.stringify({
|
|
179
|
+
error: 'Límite de solicitudes excedido',
|
|
180
|
+
retryAfter: Math.floor(timeLeft / 1000) + ' segundos'
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Incrementar conteo y continuar
|
|
187
|
+
this.incrementRequestCount(clientId);
|
|
188
|
+
if (next) {
|
|
189
|
+
next();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Obtiene la IP del cliente
|
|
195
|
+
*/
|
|
196
|
+
getClientIP(req) {
|
|
197
|
+
return req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
|
198
|
+
req.headers['x-real-ip'] ||
|
|
199
|
+
req.connection.remoteAddress ||
|
|
200
|
+
req.socket.remoteAddress ||
|
|
201
|
+
(req.connection?.socket ? req.connection.socket.remoteAddress : 'unknown');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Tokeniza el user agent para análisis
|
|
206
|
+
*/
|
|
207
|
+
tokenizeUserAgent(userAgent) {
|
|
208
|
+
// Dividir el user agent en tokens para análisis
|
|
209
|
+
return userAgent
|
|
210
|
+
.toLowerCase()
|
|
211
|
+
.replace(/[()]/g, ' ')
|
|
212
|
+
.split(/[\s;/_\-,.]+/)
|
|
213
|
+
.filter(token => token.length > 0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Detecta headers sospechosos
|
|
218
|
+
*/
|
|
219
|
+
detectSuspiciousHeaders(headers) {
|
|
220
|
+
const suspiciousHeaders = [
|
|
221
|
+
'x-forwarded-for',
|
|
222
|
+
'x-real-ip',
|
|
223
|
+
'x-originating-ip',
|
|
224
|
+
'x-remote-ip',
|
|
225
|
+
'x-remote-addr',
|
|
226
|
+
'x-proxy-user-ip',
|
|
227
|
+
'cf-connecting-ip',
|
|
228
|
+
'true-client-ip',
|
|
229
|
+
'x-cluster-client-ip',
|
|
230
|
+
'x-forwarded',
|
|
231
|
+
'x-forwarded-host',
|
|
232
|
+
'x-forwarded-server',
|
|
233
|
+
'x-original-forwarded-for',
|
|
234
|
+
'x-original-host',
|
|
235
|
+
'x-proxy-id'
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const detected = [];
|
|
239
|
+
for (const [header, value] of Object.entries(headers)) {
|
|
240
|
+
if (suspiciousHeaders.includes(header.toLowerCase())) {
|
|
241
|
+
detected.push({ header, value });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return detected;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Detecta patrones de ataque en la solicitud
|
|
250
|
+
*/
|
|
251
|
+
detectAttackPatterns(req) {
|
|
252
|
+
const path = req.url;
|
|
253
|
+
const body = req.body || '';
|
|
254
|
+
const headers = req.headers;
|
|
255
|
+
|
|
256
|
+
// Patrones de SQL Injection
|
|
257
|
+
const sqlPatterns = [
|
|
258
|
+
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT|TRUNCATE|DECLARE|MERGE|GRANT|REVOKE|CALL|LOAD|COPY|BULK|INTO|OUTFILE|DUMPFILE)\b)/gi,
|
|
259
|
+
/('|--|#|\/\*|\*\/|;|xp_|sp_|sysobjects|syscolumns|information_schema)/gi
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
// Patrones de XSS
|
|
263
|
+
const xssPatterns = [
|
|
264
|
+
/(<script|javascript:|vbscript:|onload|onerror|onmouseover|onclick|onfocus|onblur|onchange|onselect|onsubmit|onkeydown|onkeypress|onkeyup|onabort|onafterprint|onbeforeprint|onbeforeunload|onblur|oncanplay|oncanplaythrough|onchange|onclick|oncontextmenu|oncopy|oncut|ondblclick|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|ondurationchange|onended|onerror|onfocus|onfocusin|onfocusout|onfullscreenchange|onfullscreenerror|onhashchange|oninput|oninvalid|onkeydown|onkeypress|onkeyup|onload|onloadeddata|onloadedmetadata|onloadstart|onmessage|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseout|onmouseover|onmouseup|onmousewheel|onoffline|ononline|onpagehide|onpageshow|onpaste|onpause|onplay|onplaying|onpopstate|onprogress|onratechange|onreset|onresize|onscroll|onsearch|onseeked|onseeking|onselect|onshow|onstalled|onstorage|onsubmit|onsuspend|ontimeupdate|ontoggle|onunload|onvolumechange|onwaiting|onwheel)/gi,
|
|
265
|
+
/(src|href|background|action)=["']?\s*(javascript:|data:|vbscript:)/gi,
|
|
266
|
+
/<iframe/gi,
|
|
267
|
+
/<img[^>]*src[\\s]*=[\\s]*["'][\\s]*(javascript:|data:)/gi,
|
|
268
|
+
/eval\s*\(/gi,
|
|
269
|
+
/expression\s*\(/gi
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
// Patrones de Path Traversal
|
|
273
|
+
const pathTraversal = /(\.\.\/|\.\.\\|%2e%2e%2f|%2e%2e%5c|%c0%ae%c0%ae%c0%af|%uff0e%uff0e%u2215|%uff0e%uff0e%u2216)/gi;
|
|
274
|
+
|
|
275
|
+
// Patrones de Command Injection
|
|
276
|
+
const cmdInjection = [
|
|
277
|
+
/(;|\||`|>|<|&)/g,
|
|
278
|
+
/\b(cat|ls|dir|pwd|whoami|uname|ps|kill|rm|mv|cp|touch|mkdir|rmdir|chmod|chown|wget|curl|nc|netcat|ping|traceroute|nslookup|dig|ifconfig|ip|route|netstat|crontab|passwd|shadow|sudo|su)\b/gi
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
// Verificar patrones en URL
|
|
282
|
+
for (const pattern of sqlPatterns) {
|
|
283
|
+
if (pattern.test(path)) {
|
|
284
|
+
return { type: 'sql_injection', pattern: pattern.toString() };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (const pattern of xssPatterns) {
|
|
289
|
+
if (pattern.test(path)) {
|
|
290
|
+
return { type: 'xss_attack', pattern: pattern.toString() };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (pathTraversal.test(path)) {
|
|
295
|
+
return { type: 'path_traversal', pattern: pathTraversal.toString() };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
for (const pattern of cmdInjection) {
|
|
299
|
+
if (pattern.test(path)) {
|
|
300
|
+
return { type: 'command_injection', pattern: pattern.toString() };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Verificar patrones en body si es string
|
|
305
|
+
if (typeof body === 'string') {
|
|
306
|
+
for (const pattern of sqlPatterns) {
|
|
307
|
+
if (pattern.test(body)) {
|
|
308
|
+
return { type: 'sql_injection', pattern: pattern.toString() };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
for (const pattern of xssPatterns) {
|
|
313
|
+
if (pattern.test(body)) {
|
|
314
|
+
return { type: 'xss_attack', pattern: pattern.toString() };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
for (const pattern of cmdInjection) {
|
|
319
|
+
if (pattern.test(body)) {
|
|
320
|
+
return { type: 'command_injection', pattern: pattern.toString() };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Verificar headers sospechosos
|
|
326
|
+
const suspiciousHeaders = [
|
|
327
|
+
'x-forwarded-for',
|
|
328
|
+
'x-real-ip',
|
|
329
|
+
'x-originating-ip',
|
|
330
|
+
'x-remote-ip',
|
|
331
|
+
'x-remote-addr',
|
|
332
|
+
'x-proxy-user-ip',
|
|
333
|
+
'cf-connecting-ip'
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
let suspiciousHeaderCount = 0;
|
|
337
|
+
for (const header of suspiciousHeaders) {
|
|
338
|
+
if (headers[header]) {
|
|
339
|
+
suspiciousHeaderCount++;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (suspiciousHeaderCount > 3) {
|
|
344
|
+
return { type: 'header_spoofing', count: suspiciousHeaderCount };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Genera un ID de cliente basado en huella digital
|
|
352
|
+
*/
|
|
353
|
+
generateClientId(fingerprint) {
|
|
354
|
+
// Generar un hash único basado en la huella digital
|
|
355
|
+
return this.hashString(JSON.stringify(fingerprint));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Hash simple para cadenas
|
|
360
|
+
*/
|
|
361
|
+
hashString(str) {
|
|
362
|
+
let hash = 0;
|
|
363
|
+
for (let i = 0; i < str.length; i++) {
|
|
364
|
+
const char = str.charCodeAt(i);
|
|
365
|
+
hash = ((hash << 5) - hash) + char;
|
|
366
|
+
hash = hash & hash; // Convertir a 32-bit integer
|
|
367
|
+
}
|
|
368
|
+
return hash.toString();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Obtiene el conteo actual de solicitudes
|
|
373
|
+
*/
|
|
374
|
+
getCurrentRequestCount(clientId) {
|
|
375
|
+
if (!this.requestCounts) this.requestCounts = new Map();
|
|
376
|
+
const entry = this.requestCounts.get(clientId);
|
|
377
|
+
if (!entry) return 0;
|
|
378
|
+
|
|
379
|
+
// Verificar si ha expirado
|
|
380
|
+
if (Date.now() - entry.startTime > this.rateLimiter.windowMs) {
|
|
381
|
+
this.requestCounts.delete(clientId);
|
|
382
|
+
return 0;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return entry.count;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Incrementa el conteo de solicitudes
|
|
390
|
+
*/
|
|
391
|
+
incrementRequestCount(clientId) {
|
|
392
|
+
if (!this.requestCounts) this.requestCounts = new Map();
|
|
393
|
+
|
|
394
|
+
const entry = this.requestCounts.get(clientId);
|
|
395
|
+
const now = Date.now();
|
|
396
|
+
|
|
397
|
+
if (!entry) {
|
|
398
|
+
this.requestCounts.set(clientId, { count: 1, startTime: now });
|
|
399
|
+
} else {
|
|
400
|
+
if (now - entry.startTime > this.rateLimiter.windowMs) {
|
|
401
|
+
this.requestCounts.set(clientId, { count: 1, startTime: now });
|
|
402
|
+
} else {
|
|
403
|
+
entry.count++;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Obtiene el tiempo restante hasta el reset del rate limit
|
|
410
|
+
*/
|
|
411
|
+
getTimeUntilReset(clientId) {
|
|
412
|
+
if (!this.requestCounts) this.requestCounts = new Map();
|
|
413
|
+
const entry = this.requestCounts.get(clientId);
|
|
414
|
+
if (!entry) return this.rateLimiter.windowMs;
|
|
415
|
+
|
|
416
|
+
const elapsed = Date.now() - entry.startTime;
|
|
417
|
+
return Math.max(0, this.rateLimiter.windowMs - elapsed);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Middleware de seguridad avanzada
|
|
422
|
+
*/
|
|
423
|
+
securityMiddleware() {
|
|
424
|
+
return async (req, res, next) => {
|
|
425
|
+
try {
|
|
426
|
+
// Asegurarse de que req tenga las propiedades necesarias
|
|
427
|
+
if (!req || typeof req !== 'object') {
|
|
428
|
+
this.logger.error('Solicitud inválida recibida en middleware de seguridad');
|
|
429
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
430
|
+
res.end(JSON.stringify({ error: 'Solicitud inválida' }));
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Asegurarse de que req.headers exista
|
|
435
|
+
if (!req.headers) {
|
|
436
|
+
req.headers = {};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Disparar hook antes de procesar la solicitud
|
|
440
|
+
this.hooks.doAction('pre_request_processing', req, res);
|
|
441
|
+
|
|
442
|
+
// Aplicar hooks de seguridad en orden
|
|
443
|
+
const continueProcessing = await this.hooks.applyFilters('before_request_processing', true, req, res);
|
|
444
|
+
|
|
445
|
+
if (!continueProcessing) {
|
|
446
|
+
return; // La solicitud fue bloqueada por seguridad
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Generar huella digital del cliente
|
|
450
|
+
const fingerprint = this.clientFingerprinter.generate(req);
|
|
451
|
+
const enhancedFingerprint = this.hooks.applyFilters('client_fingerprint', fingerprint, req);
|
|
452
|
+
|
|
453
|
+
// Validar con firewall - el firewall modifica directamente la respuesta si es necesario
|
|
454
|
+
this.hooks.doAction('request_validation', req, res);
|
|
455
|
+
// Si headers ya fueron enviados, significa que el firewall bloqueó la solicitud
|
|
456
|
+
if (res.headersSent) {
|
|
457
|
+
return; // La solicitud fue bloqueada por el firewall
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Aplicar rate limiting - el rate limiter llama a next() internamente si no hay límite excedido
|
|
461
|
+
this.hooks.doAction('apply_rate_limiting', req, res, next);
|
|
462
|
+
|
|
463
|
+
// Disparar hook después de procesar la solicitud (pero antes de next)
|
|
464
|
+
this.hooks.doAction('post_request_processing', req, res);
|
|
465
|
+
} catch (error) {
|
|
466
|
+
this.logger.error('Error en middleware de seguridad:', error.message);
|
|
467
|
+
if (!res.headersSent) {
|
|
468
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
469
|
+
res.end(JSON.stringify({ error: 'Error interno del servidor' }));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Inicia el servidor con seguridad avanzada
|
|
477
|
+
*/
|
|
478
|
+
start() {
|
|
479
|
+
// Disparar hook antes de iniciar el servidor
|
|
480
|
+
this.hooks.doAction('pre_server_start', this.server);
|
|
481
|
+
|
|
482
|
+
// Aplicar middleware de seguridad antes de otros middlewares
|
|
483
|
+
this.server.use(this.securityMiddleware());
|
|
484
|
+
|
|
485
|
+
// Iniciar el servidor
|
|
486
|
+
this.server.start();
|
|
487
|
+
|
|
488
|
+
// Disparar hook después de iniciar el servidor
|
|
489
|
+
this.hooks.doAction('post_server_start', this.server);
|
|
490
|
+
|
|
491
|
+
this.logger.info('Servidor iniciado con funcionalidades de seguridad avanzada');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Registra una regla personalizada de firewall
|
|
496
|
+
* @param {string} name - Nombre de la regla
|
|
497
|
+
* @param {Function} condition - Función de condición que evalúa la solicitud
|
|
498
|
+
* @param {string} action - Acción a tomar ('block', 'monitor', etc.)
|
|
499
|
+
* @param {string} reason - Razón para el bloqueo o monitoreo
|
|
500
|
+
*/
|
|
501
|
+
addFirewallRule(name, condition, action = 'block', reason = 'Violación de regla personalizada') {
|
|
502
|
+
this.firewall.addRule(name, condition, action, reason);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Registra un detector de ataques personalizado
|
|
507
|
+
* @param {string} name - Nombre del detector
|
|
508
|
+
* @param {Function} detector - Función que detecta patrones de ataque
|
|
509
|
+
*/
|
|
510
|
+
addAttackDetector(name, detector) {
|
|
511
|
+
this.attackDetector.addCustomDetector(name, detector);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Clases auxiliares para las funcionalidades de seguridad
|
|
516
|
+
|
|
517
|
+
class AttackDetector {
|
|
518
|
+
constructor(options = {}) {
|
|
519
|
+
this.logger = options.logger || console;
|
|
520
|
+
this.customDetectors = new Map(); // Map<name, detectorFunction>
|
|
521
|
+
|
|
522
|
+
// Patrones comunes de ataque
|
|
523
|
+
this.sqlPatterns = [
|
|
524
|
+
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/gi,
|
|
525
|
+
/('|--|#|\/\*|\*\/|;)/g
|
|
526
|
+
];
|
|
527
|
+
|
|
528
|
+
this.xssPatterns = [
|
|
529
|
+
/(<script|javascript:|vbscript:|onload|onerror|onmouseover|onclick|onfocus|onblur)/gi,
|
|
530
|
+
/(src|href)=["']javascript:/gi,
|
|
531
|
+
/<iframe/gi,
|
|
532
|
+
/<img[^>]*src[\\s]*=[\\s]*["'][\\s]*(javascript:|data:)/gi
|
|
533
|
+
];
|
|
534
|
+
|
|
535
|
+
this.pathTraversal = /\.\.\//g;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Detecta patrones de ataque en la solicitud
|
|
540
|
+
* @param {Object} req - Objeto de solicitud
|
|
541
|
+
* @returns {string|null} - Tipo de ataque detectado o null si ninguno
|
|
542
|
+
*/
|
|
543
|
+
detect(req) {
|
|
544
|
+
// Usar el método de detección de patrones avanzados
|
|
545
|
+
const attackResult = this.detectAttackPatterns(req);
|
|
546
|
+
|
|
547
|
+
if (attackResult) {
|
|
548
|
+
return attackResult.type;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Ejecutar detectores personalizados
|
|
552
|
+
for (const [name, detector] of this.customDetectors) {
|
|
553
|
+
const result = detector(req);
|
|
554
|
+
if (result) {
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Agrega un detector de ataques personalizado
|
|
564
|
+
* @param {string} name - Nombre del detector
|
|
565
|
+
* @param {Function} detector - Función de detección
|
|
566
|
+
*/
|
|
567
|
+
addCustomDetector(name, detector) {
|
|
568
|
+
this.customDetectors.set(name, detector);
|
|
569
|
+
this.logger.info(`Detector de ataque personalizado agregado: ${name}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
class ClientFingerprinter {
|
|
574
|
+
constructor(options = {}) {
|
|
575
|
+
this.logger = options.logger || console;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Genera una huella digital única para el cliente
|
|
580
|
+
* @param {Object} req - Objeto de solicitud
|
|
581
|
+
* @returns {Object} - Huella digital del cliente
|
|
582
|
+
*/
|
|
583
|
+
generate(req) {
|
|
584
|
+
return {
|
|
585
|
+
ip: req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
|
586
|
+
req.headers['x-real-ip'] ||
|
|
587
|
+
req.connection.remoteAddress ||
|
|
588
|
+
req.socket.remoteAddress ||
|
|
589
|
+
(req.connection?.socket ? req.connection.socket.remoteAddress : 'unknown'),
|
|
590
|
+
userAgent: req.headers['user-agent'] || '',
|
|
591
|
+
acceptLanguage: req.headers['accept-language'] || '',
|
|
592
|
+
acceptEncoding: req.headers['accept-encoding'] || '',
|
|
593
|
+
accept: req.headers['accept'] || '',
|
|
594
|
+
referer: req.headers['referer'] || '',
|
|
595
|
+
contentType: req.headers['content-type'] || '',
|
|
596
|
+
timestamp: Date.now()
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
class Firewall {
|
|
602
|
+
constructor(options = {}) {
|
|
603
|
+
this.logger = options.logger || console;
|
|
604
|
+
this.blockedIPs = new Map(); // Map<IP, { blockedUntil, reason, attempts }>
|
|
605
|
+
this.maxAttempts = options.maxAttempts || 5;
|
|
606
|
+
this.blockDuration = options.blockDuration || 900000; // 15 minutos
|
|
607
|
+
this.whitelist = options.whitelist || []; // IPs que no deben ser bloqueadas
|
|
608
|
+
this.blacklist = options.blacklist || []; // IPs que siempre deben ser bloqueadas
|
|
609
|
+
this.rules = options.rules || []; // Reglas personalizadas de firewall
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Verifica si una IP está bloqueada
|
|
614
|
+
* @param {string} ip - IP a verificar
|
|
615
|
+
* @returns {Object} - Información del bloqueo
|
|
616
|
+
*/
|
|
617
|
+
isBlocked(ip) {
|
|
618
|
+
// Verificar si está en la blacklist
|
|
619
|
+
if (this.blacklist.includes(ip)) {
|
|
620
|
+
return {
|
|
621
|
+
blocked: true,
|
|
622
|
+
reason: 'IP en lista negra',
|
|
623
|
+
permanent: true,
|
|
624
|
+
blockedUntil: null
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Verificar si está en la whitelist
|
|
629
|
+
if (this.whitelist.includes(ip)) {
|
|
630
|
+
return { blocked: false };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const blockInfo = this.blockedIPs.get(ip);
|
|
634
|
+
if (!blockInfo) {
|
|
635
|
+
return { blocked: false };
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Verificar si el bloqueo ha expirado
|
|
639
|
+
if (Date.now() > blockInfo.blockedUntil) {
|
|
640
|
+
this.blockedIPs.delete(ip); // Limpiar bloqueo expirado
|
|
641
|
+
return { blocked: false };
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return {
|
|
645
|
+
blocked: true,
|
|
646
|
+
reason: blockInfo.reason,
|
|
647
|
+
blockedUntil: blockInfo.blockedUntil,
|
|
648
|
+
attempts: blockInfo.attempts
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Bloquea una IP
|
|
654
|
+
* @param {string} ip - IP a bloquear
|
|
655
|
+
* @param {string} reason - Razón del bloqueo
|
|
656
|
+
*/
|
|
657
|
+
blockIP(ip, reason) {
|
|
658
|
+
if (this.whitelist.includes(ip)) {
|
|
659
|
+
this.logger.info(`IP ${ip} está en la whitelist, no se bloqueará`);
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const blockedUntil = Date.now() + this.blockDuration;
|
|
664
|
+
const currentInfo = this.blockedIPs.get(ip);
|
|
665
|
+
const attempts = currentInfo ? currentInfo.attempts + 1 : 1;
|
|
666
|
+
|
|
667
|
+
this.blockedIPs.set(ip, {
|
|
668
|
+
blockedUntil,
|
|
669
|
+
reason,
|
|
670
|
+
attempts
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
this.logger.warn(`IP ${ip} bloqueada por: ${reason}. Intentos: ${attempts}`);
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Incrementa el contador de intentos fallidos para una IP
|
|
679
|
+
* @param {string} ip - IP a incrementar intentos
|
|
680
|
+
* @param {string} reason - Razón del intento fallido
|
|
681
|
+
*/
|
|
682
|
+
incrementFailedAttempts(ip, reason) {
|
|
683
|
+
if (this.whitelist.includes(ip)) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const currentInfo = this.blockedIPs.get(ip);
|
|
688
|
+
const attempts = currentInfo ? currentInfo.attempts + 1 : 1;
|
|
689
|
+
const blockedUntil = currentInfo ? currentInfo.blockedUntil : Date.now() + this.blockDuration;
|
|
690
|
+
|
|
691
|
+
this.blockedIPs.set(ip, {
|
|
692
|
+
blockedUntil,
|
|
693
|
+
reason,
|
|
694
|
+
attempts
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Si se alcanza el límite de intentos, bloquear permanentemente
|
|
698
|
+
if (attempts >= this.maxAttempts) {
|
|
699
|
+
this.logger.warn(`IP ${ip} bloqueada permanentemente tras ${attempts} intentos fallidos`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Agrega una regla personalizada de firewall
|
|
705
|
+
* @param {string} name - Nombre de la regla
|
|
706
|
+
* @param {Function} condition - Condición que evalúa la solicitud
|
|
707
|
+
* @param {string} action - Acción a tomar ('block', 'monitor', etc.)
|
|
708
|
+
* @param {string} reason - Razón para la acción
|
|
709
|
+
*/
|
|
710
|
+
addRule(name, condition, action = 'block', reason = 'Violación de regla personalizada') {
|
|
711
|
+
this.rules.push({ name, condition, action, reason });
|
|
712
|
+
this.logger.info(`Regla de firewall agregada: ${name}`);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Verifica si una solicitud coincide con alguna regla de firewall
|
|
717
|
+
* @param {Object} req - Objeto de solicitud
|
|
718
|
+
* @returns {Object|null} - Regla que coincide o null si ninguna
|
|
719
|
+
*/
|
|
720
|
+
checkRules(req) {
|
|
721
|
+
const clientIP = this.getClientIP(req);
|
|
722
|
+
|
|
723
|
+
// Verificar reglas personalizadas
|
|
724
|
+
for (const rule of this.rules) {
|
|
725
|
+
if (rule.condition(req, clientIP)) {
|
|
726
|
+
return {
|
|
727
|
+
matched: true,
|
|
728
|
+
rule: rule.name,
|
|
729
|
+
action: rule.action || 'block',
|
|
730
|
+
reason: rule.reason || 'Violación de regla de firewall'
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Obtiene la IP del cliente
|
|
740
|
+
* @param {Object} req - Objeto de solicitud
|
|
741
|
+
* @returns {string} - IP del cliente
|
|
742
|
+
*/
|
|
743
|
+
getClientIP(req) {
|
|
744
|
+
return req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
|
745
|
+
req.headers['x-real-ip'] ||
|
|
746
|
+
req.connection.remoteAddress ||
|
|
747
|
+
req.socket.remoteAddress ||
|
|
748
|
+
(req.connection?.socket ? req.connection.socket.remoteAddress : 'unknown');
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
module.exports = SecurityEnhancedServer;
|