yusufkhon-guard 1.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 +21 -0
- package/README.md +734 -0
- package/bin/cli.js +39 -0
- package/bin/renew.js +149 -0
- package/bin/scan.js +124 -0
- package/bin/ssl.js +181 -0
- package/package.json +80 -0
- package/src/index.js +206 -0
- package/src/modules/acme/client.js +240 -0
- package/src/modules/acme/csr.js +172 -0
- package/src/modules/acme/detectDomains.js +132 -0
- package/src/modules/acme/index.js +211 -0
- package/src/modules/acme/jose.js +125 -0
- package/src/modules/acme/renew.js +89 -0
- package/src/modules/adaptiveRateLimiter.js +154 -0
- package/src/modules/alerts.js +168 -0
- package/src/modules/bodyLimit.js +106 -0
- package/src/modules/botDetector.js +144 -0
- package/src/modules/connectionGuard.js +128 -0
- package/src/modules/csrf.js +130 -0
- package/src/modules/dashboard.js +257 -0
- package/src/modules/dashboardHtml.js +235 -0
- package/src/modules/datacenterRanges.js +121 -0
- package/src/modules/ddosGuard.js +323 -0
- package/src/modules/firewall.js +239 -0
- package/src/modules/geoBlock.js +169 -0
- package/src/modules/headers.js +130 -0
- package/src/modules/honeypot.js +149 -0
- package/src/modules/httpsRedirect.js +67 -0
- package/src/modules/integrity.js +164 -0
- package/src/modules/ipBlocker.js +372 -0
- package/src/modules/ja3.js +155 -0
- package/src/modules/logExport.js +134 -0
- package/src/modules/nosqlGuard.js +143 -0
- package/src/modules/osFirewall.js +182 -0
- package/src/modules/rateLimiter.js +196 -0
- package/src/modules/sanitizer.js +253 -0
- package/src/modules/threatDetector.js +202 -0
- package/src/scanner/checks/dependencies.js +125 -0
- package/src/scanner/checks/environment.js +118 -0
- package/src/scanner/checks/exposure.js +99 -0
- package/src/scanner/checks/headers.js +119 -0
- package/src/scanner/checks/ssl.js +151 -0
- package/src/scanner/index.js +209 -0
- package/src/utils/cidr.js +140 -0
- package/src/utils/logger.js +194 -0
- package/src/utils/privilege.js +212 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file firewall.js
|
|
5
|
+
* @description "Blue Team" aktiv himoya yadrosi. `threatDetector` (aniqlash) va
|
|
6
|
+
* `ipBlocker` (bloklash) ni birlashtiradi hamda serverni 24/7
|
|
7
|
+
* kuzatib boradi. Har bir so'rovni real vaqtda tahlil qiladi,
|
|
8
|
+
* tahdid sezilsa avtomat aks-ta'sir ko'rsatadi (IP'ni bloklaydi)
|
|
9
|
+
* va hodisalarni tashqariga (webhook/telegram/email) uzatish uchun
|
|
10
|
+
* EventEmitter chiqaradi.
|
|
11
|
+
* @module modules/firewall
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { EventEmitter } = require('events');
|
|
15
|
+
const threatDetector = require('./threatDetector');
|
|
16
|
+
const { IpBlocker } = require('./ipBlocker');
|
|
17
|
+
const { defaultKeyGenerator } = require('./rateLimiter');
|
|
18
|
+
const { normalizeIp } = require('../utils/cidr');
|
|
19
|
+
const { defaultLogger } = require('../utils/logger');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* So'rovning HAQIQIY ulanish (TCP soket) manzilini qaytaradi. Whitelist
|
|
23
|
+
* tekshiruvi shu manzil bo'yicha bo'lishi kerak — `X-Forwarded-For` sarlavhasi
|
|
24
|
+
* mijoz tomonidan soxtalashtirilishi mumkin, shu bois uni whitelist uchun
|
|
25
|
+
* ishonchli deb bo'lmaydi (aks holda hujumchi o'zini localhost qilib ko'rsatib
|
|
26
|
+
* himoyani chetlab o'tadi).
|
|
27
|
+
* @param {import('express').Request} req
|
|
28
|
+
* @returns {string} Normallashtirilgan soket manzili yoki bo'sh satr.
|
|
29
|
+
*/
|
|
30
|
+
function realClientIp(req) {
|
|
31
|
+
const sock = req.socket || req.connection;
|
|
32
|
+
const addr = sock && sock.remoteAddress;
|
|
33
|
+
return addr ? normalizeIp(addr) : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} FirewallOptions
|
|
38
|
+
* @property {number} [banThreshold=100] - Bloklash uchun kerakli to'plangan ball.
|
|
39
|
+
* @property {number} [instantBanScore=80] - Bitta so'rovda shu balldan oshsa — darhol blok.
|
|
40
|
+
* @property {number} [strikeWindowMs=600000] - Ballar to'planadigan oyna (ms).
|
|
41
|
+
* @property {string[]} [whitelist] - Hech qachon bloklanmaydigan IP'lar.
|
|
42
|
+
* @property {string|null} [persistPath=null] - Bloklarni saqlash uchun fayl yo'li.
|
|
43
|
+
* @property {number} [statusCode=403] - Bloklangan IP uchun HTTP status.
|
|
44
|
+
* @property {string} [message] - Bloklangan IP uchun xabar.
|
|
45
|
+
* @property {boolean} [monitor=true] - 24/7 davriy holat monitoringini yoqish.
|
|
46
|
+
* @property {number} [monitorIntervalMs=300000] - Monitoring hisoboti oralig'i (5 daqiqa).
|
|
47
|
+
* @property {import('../utils/logger').Logger} [logger=defaultLogger] - Logger.
|
|
48
|
+
* @property {(req: import('express').Request) => string} [keyGenerator] - IP aniqlash.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Standart sozlamalar.
|
|
53
|
+
* @type {FirewallOptions}
|
|
54
|
+
*/
|
|
55
|
+
const DEFAULT_OPTIONS = {
|
|
56
|
+
banThreshold: 100,
|
|
57
|
+
instantBanScore: 80,
|
|
58
|
+
strikeWindowMs: 10 * 60 * 1000,
|
|
59
|
+
statusCode: 403,
|
|
60
|
+
message: 'Access denied — bu IP xavfsizlik tizimi tomonidan bloklangan.',
|
|
61
|
+
monitor: true,
|
|
62
|
+
monitorIntervalMs: 5 * 60 * 1000,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Aktiv himoya (firewall) tizimi.
|
|
67
|
+
* `EventEmitter` dan meros oladi — quyidagi hodisalarni chiqaradi:
|
|
68
|
+
* - `'threat'` ({ ip, report, req }) — tahdid aniqlandi.
|
|
69
|
+
* - `'ban'` ({ ip, reason, record }) — IP bloklandi.
|
|
70
|
+
* - `'blocked'` ({ ip, path }) — bloklangan IP qaytarildi.
|
|
71
|
+
* - `'tick'` (stats) — davriy monitoring hisoboti.
|
|
72
|
+
*
|
|
73
|
+
* @class
|
|
74
|
+
* @extends EventEmitter
|
|
75
|
+
*/
|
|
76
|
+
class Firewall extends EventEmitter {
|
|
77
|
+
/**
|
|
78
|
+
* @param {FirewallOptions} [userOptions={}]
|
|
79
|
+
*/
|
|
80
|
+
constructor(userOptions = {}) {
|
|
81
|
+
super();
|
|
82
|
+
/** @type {FirewallOptions} */
|
|
83
|
+
this.options = { ...DEFAULT_OPTIONS, ...userOptions };
|
|
84
|
+
/** @type {import('../utils/logger').Logger} */
|
|
85
|
+
this.logger = this.options.logger || defaultLogger;
|
|
86
|
+
/** @type {(req: import('express').Request) => string} */
|
|
87
|
+
this.keyGenerator = this.options.keyGenerator || defaultKeyGenerator;
|
|
88
|
+
|
|
89
|
+
/** @type {IpBlocker} */
|
|
90
|
+
this.blocker = new IpBlocker({
|
|
91
|
+
banThreshold: this.options.banThreshold,
|
|
92
|
+
strikeWindowMs: this.options.strikeWindowMs,
|
|
93
|
+
whitelist: this.options.whitelist,
|
|
94
|
+
persistPath: this.options.persistPath,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/** @type {{requests: number, threats: number, bans: number, blocked: number}} */
|
|
98
|
+
this.metrics = { requests: 0, threats: 0, bans: 0, blocked: 0 };
|
|
99
|
+
|
|
100
|
+
if (this.options.monitor) this._startMonitor();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 24/7 davriy monitoringni ishga tushiradi — holat statistikasini loglaydi.
|
|
105
|
+
* @private
|
|
106
|
+
* @returns {void}
|
|
107
|
+
*/
|
|
108
|
+
_startMonitor() {
|
|
109
|
+
this._monitorTimer = setInterval(() => {
|
|
110
|
+
const stats = { ...this.metrics, ...this.blocker.stats() };
|
|
111
|
+
this.logger.info('🛡️ Firewall monitoring', stats);
|
|
112
|
+
this.emit('tick', stats);
|
|
113
|
+
}, this.options.monitorIntervalMs);
|
|
114
|
+
if (typeof this._monitorTimer.unref === 'function') this._monitorTimer.unref();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Monitoringni to'xtatadi (test/graceful shutdown uchun).
|
|
119
|
+
* @returns {void}
|
|
120
|
+
*/
|
|
121
|
+
stop() {
|
|
122
|
+
if (this._monitorTimer) clearInterval(this._monitorTimer);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Bloklangan IP uchun standart javob yuboradi.
|
|
127
|
+
* @private
|
|
128
|
+
* @param {import('express').Response} res
|
|
129
|
+
* @param {string} ip
|
|
130
|
+
* @returns {void}
|
|
131
|
+
*/
|
|
132
|
+
_reject(res, ip) {
|
|
133
|
+
const secondsLeft = this.blocker.banSecondsLeft(ip);
|
|
134
|
+
if (secondsLeft > 0) res.setHeader('Retry-After', secondsLeft);
|
|
135
|
+
res.status(this.options.statusCode).json({
|
|
136
|
+
success: false,
|
|
137
|
+
error: this.options.message,
|
|
138
|
+
bannedFor: secondsLeft === -1 ? 'permanent' : `${secondsLeft}s`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Firewall Express middleware'ini qaytaradi.
|
|
144
|
+
* @returns {import('express').RequestHandler}
|
|
145
|
+
*/
|
|
146
|
+
middleware() {
|
|
147
|
+
const self = this;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {import('express').Request} req
|
|
151
|
+
* @param {import('express').Response} res
|
|
152
|
+
* @param {import('express').NextFunction} next
|
|
153
|
+
*/
|
|
154
|
+
return function firewallMiddleware(req, res, next) {
|
|
155
|
+
self.metrics.requests += 1;
|
|
156
|
+
const ip = self.keyGenerator(req);
|
|
157
|
+
const realIp = realClientIp(req);
|
|
158
|
+
const reqPath = req.originalUrl || req.url;
|
|
159
|
+
|
|
160
|
+
// 1. Ishonchli IP — tekshiruvsiz o'tkazamiz.
|
|
161
|
+
// MUHIM: whitelist HAQIQIY ulanish manzili (socket) bo'yicha tekshiriladi.
|
|
162
|
+
// Bu spoofed `X-Forwarded-For: 127.0.0.1` orqali o'zini whitelist qilib
|
|
163
|
+
// firewall'ni chetlab o'tishning oldini oladi. Soket manzili bo'lmasa
|
|
164
|
+
// (kamdan-kam holat), atributsiya kalitiga qaytamiz.
|
|
165
|
+
if (realIp ? self.blocker.isWhitelisted(realIp) : self.blocker.isWhitelisted(ip)) {
|
|
166
|
+
return next();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 2. Allaqachon bloklangan bo'lsa — darhol rad etamiz.
|
|
170
|
+
if (self.blocker.isBanned(ip)) {
|
|
171
|
+
self.metrics.blocked += 1;
|
|
172
|
+
self.logger.blocked(ip, 'Bloklangan IP qayta urindi', { path: reqPath });
|
|
173
|
+
self.emit('blocked', { ip, path: reqPath });
|
|
174
|
+
return self._reject(res, ip);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 3. So'rovni tahlil qilamiz.
|
|
178
|
+
const report = threatDetector.analyze(req);
|
|
179
|
+
|
|
180
|
+
if (report.score > 0) {
|
|
181
|
+
self.metrics.threats += 1;
|
|
182
|
+
self.logger.warn(`Tahdid aniqlandi [${report.severity}] ${ip}`, {
|
|
183
|
+
score: report.score,
|
|
184
|
+
path: reqPath,
|
|
185
|
+
types: report.signals.map((s) => s.type),
|
|
186
|
+
});
|
|
187
|
+
self.emit('threat', { ip, report, req });
|
|
188
|
+
|
|
189
|
+
// 3a. Kritik tahdid — darhol bloklaymiz.
|
|
190
|
+
const instant = report.score >= self.options.instantBanScore;
|
|
191
|
+
const primaryReason = report.signals[0] ? report.signals[0].detail : 'Threat detected';
|
|
192
|
+
|
|
193
|
+
const outcome = instant
|
|
194
|
+
? { banned: true, record: self.blocker.ban(ip, primaryReason) }
|
|
195
|
+
: self.blocker.addStrike(ip, report.score, primaryReason);
|
|
196
|
+
|
|
197
|
+
if (outcome.banned) {
|
|
198
|
+
self.metrics.bans += 1;
|
|
199
|
+
self.logger.blocked(ip, `AVTO-BLOK (${report.severity})`, {
|
|
200
|
+
path: reqPath,
|
|
201
|
+
reason: primaryReason,
|
|
202
|
+
});
|
|
203
|
+
self.emit('ban', { ip, reason: primaryReason, record: outcome.record });
|
|
204
|
+
return self._reject(res, ip);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
next();
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Firewall middleware yaratuvchi qulay funksiya.
|
|
215
|
+
* Yaratilgan middleware'ga `.firewall` orqali Firewall nusxasi biriktiriladi
|
|
216
|
+
* (hodisalarga obuna bo'lish va boshqarish uchun).
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* const { firewall } = require('yusufkhon-guard');
|
|
220
|
+
* const fw = firewall({ persistPath: './logs/bans.json' });
|
|
221
|
+
* fw.firewall.on('ban', ({ ip, reason }) => {
|
|
222
|
+
* // Telegram/Email/Webhook orqali xabar berish
|
|
223
|
+
* console.log('Bloklandi:', ip, reason);
|
|
224
|
+
* });
|
|
225
|
+
* app.use(fw);
|
|
226
|
+
*
|
|
227
|
+
* @param {FirewallOptions} [options={}]
|
|
228
|
+
* @returns {import('express').RequestHandler & { firewall: Firewall }}
|
|
229
|
+
*/
|
|
230
|
+
function firewall(options = {}) {
|
|
231
|
+
const instance = new Firewall(options);
|
|
232
|
+
const mw = instance.middleware();
|
|
233
|
+
mw.firewall = instance;
|
|
234
|
+
return mw;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = firewall;
|
|
238
|
+
module.exports.firewall = firewall;
|
|
239
|
+
module.exports.Firewall = Firewall;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file geoBlock.js
|
|
5
|
+
* @description Hududiy (Geo-IP) va datacenter/VPN himoyasi. Faqat ruxsat berilgan
|
|
6
|
+
* davlatlardan kirishga ruxsat berish, yoki ma'lum davlatlarni
|
|
7
|
+
* bloklash, hamda VPN/proxy/datacenter IP'larini cheklash.
|
|
8
|
+
*
|
|
9
|
+
* Davlatni aniqlash uchun `lookup(ip) => countryCode` funksiyasi
|
|
10
|
+
* beriladi. Agar berilmasa va ixtiyoriy `geoip-lite` paketi
|
|
11
|
+
* o'rnatilgan bo'lsa — o'sha ishlatiladi (zero-dependency saqlanadi).
|
|
12
|
+
* @module modules/geoBlock
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const datacenter = require('./datacenterRanges');
|
|
16
|
+
const { normalizeIp } = require('../utils/cidr');
|
|
17
|
+
const { defaultLogger } = require('../utils/logger');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* So'rovni baholash uchun ISHONCHLI mijoz IP'sini aniqlaydi.
|
|
21
|
+
* Ustuvorlik: `req.ip` (Express'ning `trust proxy` sozlamasiga bo'ysunadi —
|
|
22
|
+
* administrator belgilagan ishonchli manba), so'ng haqiqiy TCP soket manzili.
|
|
23
|
+
* Xom `X-Forwarded-For` sarlavhasiga TAYANMAYDI — u mijoz tomonidan
|
|
24
|
+
* soxtalashtirilishi mumkin (spoofing).
|
|
25
|
+
* @private
|
|
26
|
+
* @param {import('express').Request} req
|
|
27
|
+
* @returns {string} Normallashtirilgan IP yoki bo'sh satr.
|
|
28
|
+
*/
|
|
29
|
+
function trustedClientIp(req) {
|
|
30
|
+
if (req.ip) return normalizeIp(req.ip);
|
|
31
|
+
const sock = req.socket || req.connection;
|
|
32
|
+
const addr = sock && sock.remoteAddress;
|
|
33
|
+
return addr ? normalizeIp(addr) : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `geoip-lite` (ixtiyoriy peer) mavjud bo'lsa, undan lookup funksiyasi yasaydi.
|
|
38
|
+
* @private
|
|
39
|
+
* @returns {((ip: string) => string|null)|null}
|
|
40
|
+
*/
|
|
41
|
+
function tryGeoipLite() {
|
|
42
|
+
try {
|
|
43
|
+
// eslint-disable-next-line global-require
|
|
44
|
+
const geoip = require('geoip-lite');
|
|
45
|
+
return (ip) => {
|
|
46
|
+
const r = geoip.lookup(ip);
|
|
47
|
+
return r ? r.country : null;
|
|
48
|
+
};
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {Object} GeoBlockOptions
|
|
56
|
+
* @property {string[]} [allowCountries] - Faqat shu davlatlardan (ISO-2, masalan 'UZ','US') ruxsat.
|
|
57
|
+
* @property {string[]} [denyCountries] - Shu davlatlarni bloklash (allowCountries berilmaganda).
|
|
58
|
+
* @property {boolean} [blockDatacenter=false] - Datacenter/hosting IP'larini bloklash.
|
|
59
|
+
* @property {boolean} [blockVpn=false] - Ma'lum VPN diapazonlarini bloklash (blockDatacenter ichida ham bor).
|
|
60
|
+
* @property {(ip: string) => (string|null)} [lookup] - IP -> davlat kodi funksiyasi.
|
|
61
|
+
* @property {string[]} [whitelist] - Hech qachon bloklanmaydigan IP'lar.
|
|
62
|
+
* @property {boolean} [allowUnknown] - Davlati aniqlanmagan (null) IP'ga ruxsat. STANDART: denylist'da `true`, allowlist'da `false` (xavfsiz).
|
|
63
|
+
* @property {boolean} [failClosed] - `lookup` XATO (exception) tashlasa RAD etilsinmi. STANDART: allowlist'da `true`, denylist'da `false`.
|
|
64
|
+
* @property {number} [statusCode=403] - Bloklashda status kodi.
|
|
65
|
+
* @property {string} [message] - Bloklash xabari.
|
|
66
|
+
* @property {import('./firewall').Firewall} [firewall] - Berilsa, bloklangan IP firewall'ga strike qo'shadi.
|
|
67
|
+
* @property {import('../utils/logger').Logger} [logger=defaultLogger] - Logger.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Geo-IP / datacenter / VPN himoya middleware'ini yaratadi.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* const { geoBlock } = require('yusufkhon-guard');
|
|
75
|
+
* app.use(geoBlock({ allowCountries: ['UZ','US'], blockDatacenter: true }));
|
|
76
|
+
*
|
|
77
|
+
* @param {GeoBlockOptions} [options={}]
|
|
78
|
+
* @returns {import('express').RequestHandler}
|
|
79
|
+
*/
|
|
80
|
+
function geoBlock(options = {}) {
|
|
81
|
+
const logger = options.logger || defaultLogger;
|
|
82
|
+
const allow = (options.allowCountries || []).map((c) => c.toUpperCase());
|
|
83
|
+
const deny = (options.denyCountries || []).map((c) => c.toUpperCase());
|
|
84
|
+
const whitelist = new Set(options.whitelist || ['127.0.0.1', '::1']);
|
|
85
|
+
|
|
86
|
+
// XAVFSIZ-BY-DEFAULT: allowlist (allowCountries) rejimida davlatni aniqlab
|
|
87
|
+
// bo'lmasa (null natija YOKI lookup xatosi) — RAD etamiz; aks holda geo-bazada
|
|
88
|
+
// bo'lmagan istalgan IP allowlist'ni jimgina chetlab o'tar edi. Denylist
|
|
89
|
+
// rejimida esa noma'lumlarni o'tkazamiz (permissive). Ikkalasini ham ochiq
|
|
90
|
+
// override qilish mumkin (allowUnknown / failClosed).
|
|
91
|
+
const isAllowlist = allow.length > 0;
|
|
92
|
+
const allowUnknown = options.allowUnknown !== undefined ? options.allowUnknown : !isAllowlist;
|
|
93
|
+
const failClosed = options.failClosed !== undefined ? options.failClosed : isAllowlist;
|
|
94
|
+
|
|
95
|
+
const statusCode = options.statusCode || 403;
|
|
96
|
+
const message = options.message || 'Access denied — sizning hududingizdan kirish cheklangan.';
|
|
97
|
+
|
|
98
|
+
const lookup = options.lookup || tryGeoipLite();
|
|
99
|
+
if ((allow.length || deny.length) && !lookup) {
|
|
100
|
+
logger.warn('geoBlock: davlat bo\'yicha filtrlash uchun `lookup` funksiyasi yoki `geoip-lite` kerak. Geo-qism o\'tkazib yuboriladi.');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {import('express').Request} req
|
|
105
|
+
* @param {import('express').Response} res
|
|
106
|
+
* @param {import('express').NextFunction} next
|
|
107
|
+
*/
|
|
108
|
+
return function geoBlockMiddleware(req, res, next) {
|
|
109
|
+
// ISHONCHLI IP (req.ip yoki soket) — spooflanadigan xom XFF'ga tayanmaymiz.
|
|
110
|
+
const ip = trustedClientIp(req);
|
|
111
|
+
if (!ip || whitelist.has(ip)) return next();
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* So'rovni rad etadi (va ixtiyoriy firewall'ga strike qo'shadi).
|
|
115
|
+
* @param {string} reason
|
|
116
|
+
*/
|
|
117
|
+
const reject = (reason) => {
|
|
118
|
+
logger.blocked(ip, `Geo/DC blok: ${reason}`, { path: req.originalUrl || req.url });
|
|
119
|
+
if (options.firewall) {
|
|
120
|
+
options.firewall.blocker.addStrike(ip, 60, `Geo/DC: ${reason}`);
|
|
121
|
+
}
|
|
122
|
+
res.status ? res.status(statusCode).json({ success: false, error: message, reason })
|
|
123
|
+
: (res.statusCode = statusCode, res.end(JSON.stringify({ success: false, error: message })));
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// 1. Datacenter / VPN tekshiruvi.
|
|
127
|
+
if (options.blockDatacenter || options.blockVpn) {
|
|
128
|
+
const dc = datacenter.detect(ip);
|
|
129
|
+
if (dc) {
|
|
130
|
+
return reject(`${dc.provider} (datacenter/VPN)`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 2. Davlat bo'yicha filtrlash.
|
|
135
|
+
if (lookup && (allow.length || deny.length)) {
|
|
136
|
+
let country = null;
|
|
137
|
+
let lookupFailed = false;
|
|
138
|
+
try { country = lookup(ip); } catch { lookupFailed = true; }
|
|
139
|
+
|
|
140
|
+
// lookup XATO tashladi. Qat'iy rejimda (failClosed) — RAD. Aks holda
|
|
141
|
+
// "davlat noaniq" sifatida allowUnknown qoidasiga o'tamiz.
|
|
142
|
+
if (lookupFailed && failClosed) {
|
|
143
|
+
return reject('geo-lookup xatosi (fail-closed)');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!country) {
|
|
147
|
+
if (!allowUnknown) return reject('davlat aniqlanmadi');
|
|
148
|
+
return next();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
country = country.toUpperCase();
|
|
152
|
+
|
|
153
|
+
// Allow-list rejimi: faqat ruxsat berilganlar o'tadi.
|
|
154
|
+
if (allow.length && !allow.includes(country)) {
|
|
155
|
+
return reject(`davlat ${country} ruxsat etilmagan`);
|
|
156
|
+
}
|
|
157
|
+
// Deny-list rejimi.
|
|
158
|
+
if (deny.length && deny.includes(country)) {
|
|
159
|
+
return reject(`davlat ${country} bloklangan`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
next();
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = geoBlock;
|
|
168
|
+
module.exports.geoBlock = geoBlock;
|
|
169
|
+
module.exports.datacenter = datacenter;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file headers.js
|
|
5
|
+
* @description Server javobiga zamonaviy xavfsizlik sarlavhalarini
|
|
6
|
+
* (Security Headers) avtomatik biriktiruvchi Express middleware.
|
|
7
|
+
* @module modules/headers
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} HeadersOptions
|
|
12
|
+
* @property {boolean} [frameGuard=true] - `X-Frame-Options: DENY` (clickjacking himoyasi).
|
|
13
|
+
* @property {boolean} [noSniff=true] - `X-Content-Type-Options: nosniff` (MIME-sniffing himoyasi).
|
|
14
|
+
* @property {boolean} [xssProtection=true] - `X-XSS-Protection: 1; mode=block`.
|
|
15
|
+
* @property {boolean|Object} [hsts=true] - HTTP Strict Transport Security sozlamasi.
|
|
16
|
+
* @property {number} [hsts.maxAge=15552000] - HSTS amal qilish muddati (soniyalarda, ~180 kun).
|
|
17
|
+
* @property {boolean} [hsts.includeSubDomains=true] - Subdomenlarni ham qamrab olsinmi.
|
|
18
|
+
* @property {boolean} [hsts.preload=false] - `preload` bayrog'ini qo'shsinmi.
|
|
19
|
+
* @property {boolean|string} [contentSecurityPolicy="default-src 'self'"] - CSP qiymati (false — o'chirilgan).
|
|
20
|
+
* @property {string} [poweredBy='yusufkhon-guard'] - `X-Powered-By` sarlavhasi qiymati.
|
|
21
|
+
* @property {string} [referrerPolicy='no-referrer'] - `Referrer-Policy` qiymati.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Standart (default) sozlamalar.
|
|
26
|
+
* @type {HeadersOptions}
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_OPTIONS = {
|
|
29
|
+
frameGuard: true,
|
|
30
|
+
noSniff: true,
|
|
31
|
+
xssProtection: true,
|
|
32
|
+
hsts: true,
|
|
33
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
34
|
+
poweredBy: 'yusufkhon-guard',
|
|
35
|
+
referrerPolicy: 'no-referrer',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* HSTS uchun standart qiymatlar.
|
|
40
|
+
* @type {{maxAge: number, includeSubDomains: boolean, preload: boolean}}
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_HSTS = {
|
|
43
|
+
maxAge: 15552000, // ~180 kun
|
|
44
|
+
includeSubDomains: true,
|
|
45
|
+
preload: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* HSTS sarlavhasi qiymatini tuzadi.
|
|
50
|
+
* @private
|
|
51
|
+
* @param {boolean|Object} hsts - Foydalanuvchi bergan HSTS sozlamasi.
|
|
52
|
+
* @returns {string|null} Tayyor sarlavha qiymati yoki null (o'chirilgan bo'lsa).
|
|
53
|
+
*/
|
|
54
|
+
function buildHstsValue(hsts) {
|
|
55
|
+
if (!hsts) return null;
|
|
56
|
+
|
|
57
|
+
const conf = hsts === true ? DEFAULT_HSTS : { ...DEFAULT_HSTS, ...hsts };
|
|
58
|
+
const parts = [`max-age=${conf.maxAge}`];
|
|
59
|
+
|
|
60
|
+
if (conf.includeSubDomains) parts.push('includeSubDomains');
|
|
61
|
+
if (conf.preload) parts.push('preload');
|
|
62
|
+
|
|
63
|
+
return parts.join('; ');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Xavfsizlik sarlavhalarini o'rnatuvchi Express middleware yaratadi.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* const { securityHeaders } = require('yusufkhon-guard');
|
|
71
|
+
* app.use(securityHeaders({ contentSecurityPolicy: "default-src 'self'" }));
|
|
72
|
+
*
|
|
73
|
+
* @param {HeadersOptions} [userOptions={}] - Sozlamalar (standart qiymatlar bilan birlashtiriladi).
|
|
74
|
+
* @returns {import('express').RequestHandler} Express middleware funksiyasi.
|
|
75
|
+
*/
|
|
76
|
+
function securityHeaders(userOptions = {}) {
|
|
77
|
+
const options = { ...DEFAULT_OPTIONS, ...userOptions };
|
|
78
|
+
const hstsValue = buildHstsValue(options.hsts);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {import('express').Request} req
|
|
82
|
+
* @param {import('express').Response} res
|
|
83
|
+
* @param {import('express').NextFunction} next
|
|
84
|
+
*/
|
|
85
|
+
return function securityHeadersMiddleware(req, res, next) {
|
|
86
|
+
// Clickjacking himoyasi.
|
|
87
|
+
if (options.frameGuard) {
|
|
88
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// MIME-sniffing himoyasi.
|
|
92
|
+
if (options.noSniff) {
|
|
93
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Eski brauzerlar uchun XSS filtri.
|
|
97
|
+
if (options.xssProtection) {
|
|
98
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// HTTPS'ni majburlash (faqat HTTPS orqali kelgan bo'lsa mantiqan to'g'ri,
|
|
102
|
+
// lekin sarlavhani har doim qo'yish xavfsiz — brauzer HTTPS'da hisobga oladi).
|
|
103
|
+
if (hstsValue) {
|
|
104
|
+
res.setHeader('Strict-Transport-Security', hstsValue);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Content Security Policy.
|
|
108
|
+
if (options.contentSecurityPolicy) {
|
|
109
|
+
res.setHeader('Content-Security-Policy', options.contentSecurityPolicy);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Referrer siyosati.
|
|
113
|
+
if (options.referrerPolicy) {
|
|
114
|
+
res.setHeader('Referrer-Policy', options.referrerPolicy);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Server texnologiyasini yashirib, o'z brendimizni qo'yamiz.
|
|
118
|
+
if (options.poweredBy) {
|
|
119
|
+
res.setHeader('X-Powered-By', options.poweredBy);
|
|
120
|
+
} else {
|
|
121
|
+
res.removeHeader('X-Powered-By');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
next();
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = securityHeaders;
|
|
129
|
+
module.exports.securityHeaders = securityHeaders;
|
|
130
|
+
module.exports.buildHstsValue = buildHstsValue;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file honeypot.js
|
|
5
|
+
* @description HoneyPot (tuzoq) tizimi. Saytda ko'rinmaydigan, lekin avtomatik
|
|
6
|
+
* skanerlar va hujumchilar qidiradigan soxta yo'llar (masalan,
|
|
7
|
+
* `/.env`, `/admin/config.php`, `/wp-login.php`) o'rnatadi.
|
|
8
|
+
*
|
|
9
|
+
* Bunday yo'lga so'rov yuborilishi 100% zararli niyat belgisidir —
|
|
10
|
+
* shu bois so'rov yuboruvchi IP DARHOL bloklanadi (firewall +
|
|
11
|
+
* ixtiyoriy OS firewall orqali).
|
|
12
|
+
* @module modules/honeypot
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { defaultLogger } = require('../utils/logger');
|
|
16
|
+
const { defaultKeyGenerator } = require('./rateLimiter');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Standart tuzoq yo'llari — real ilova ulardan foydalanmasligi kerak.
|
|
20
|
+
* @type {string[]}
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_TRAPS = [
|
|
23
|
+
'/.env',
|
|
24
|
+
'/.env.local',
|
|
25
|
+
'/.git/config',
|
|
26
|
+
'/wp-login.php',
|
|
27
|
+
'/wp-admin',
|
|
28
|
+
'/admin/config.php',
|
|
29
|
+
'/phpmyadmin',
|
|
30
|
+
'/config.php',
|
|
31
|
+
'/.aws/credentials',
|
|
32
|
+
'/backup.sql',
|
|
33
|
+
'/.ssh/id_rsa',
|
|
34
|
+
'/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Yo'lni (bounded) iterativ dekodlaydi — hujumchi tuzoqni URL-encoding bilan
|
|
39
|
+
* yashirishga urinishi mumkin (masalan `/%2eenv` yoki ikki marta kodlangan
|
|
40
|
+
* `/%252eenv` -> `/.env`). Dekodlash barqarorlashguncha (yoki 5 martagacha)
|
|
41
|
+
* takrorlanadi. Buzuq (invalid) foizli kodlashda o'zgarishsiz qaytadi.
|
|
42
|
+
* @private
|
|
43
|
+
* @param {string} value
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function decodePathDeep(value) {
|
|
47
|
+
let cur = value;
|
|
48
|
+
let prev;
|
|
49
|
+
let i = 0;
|
|
50
|
+
do {
|
|
51
|
+
prev = cur;
|
|
52
|
+
try {
|
|
53
|
+
cur = decodeURIComponent(cur);
|
|
54
|
+
} catch {
|
|
55
|
+
break; // buzuq kodlash — bor holicha qoldiramiz
|
|
56
|
+
}
|
|
57
|
+
i += 1;
|
|
58
|
+
} while (cur !== prev && i < 5);
|
|
59
|
+
return cur;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {Object} HoneypotOptions
|
|
64
|
+
* @property {string[]} [traps] - Tuzoq yo'llari (standart ro'yxat qo'shiladi).
|
|
65
|
+
* @property {boolean} [useDefaults=true] - Standart tuzoqlarni ham qo'shish.
|
|
66
|
+
* @property {import('./firewall').Firewall} [firewall] - Darhol bloklash uchun (majburiy tavsiya etiladi).
|
|
67
|
+
* @property {import('./osFirewall')} [osFirewall] - Berilsa, OS firewall'da ham bloklaydi.
|
|
68
|
+
* @property {boolean} [osBlock=false] - OS darajasida ham bloklansinmi (rozilik/huquq talab qiladi).
|
|
69
|
+
* @property {number} [statusCode=404] - Tuzoq javobi (odatda 404 — tabiiy ko'rinishi uchun).
|
|
70
|
+
* @property {import('../utils/logger').Logger} [logger=defaultLogger] - Logger.
|
|
71
|
+
* @property {(info: {ip:string, path:string}) => void} [onTrap] - Tuzoqqa tushganda chaqiriladigan hook.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Honeypot middleware'ini yaratadi.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* const { honeypot } = require('yusufkhon-guard');
|
|
79
|
+
* app.use(honeypot({ firewall: shield.firewall }));
|
|
80
|
+
*
|
|
81
|
+
* @param {HoneypotOptions} [options={}]
|
|
82
|
+
* @returns {import('express').RequestHandler}
|
|
83
|
+
*/
|
|
84
|
+
function honeypot(options = {}) {
|
|
85
|
+
const logger = options.logger || defaultLogger;
|
|
86
|
+
const keyGen = defaultKeyGenerator;
|
|
87
|
+
const statusCode = options.statusCode || 404;
|
|
88
|
+
|
|
89
|
+
const traps = new Set(
|
|
90
|
+
(options.useDefaults === false ? [] : DEFAULT_TRAPS).concat(options.traps || [])
|
|
91
|
+
.map((t) => t.toLowerCase())
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {import('express').Request} req
|
|
96
|
+
* @param {import('express').Response} res
|
|
97
|
+
* @param {import('express').NextFunction} next
|
|
98
|
+
*/
|
|
99
|
+
return function honeypotMiddleware(req, res, next) {
|
|
100
|
+
const raw = (req.originalUrl || req.url || '').split('?')[0];
|
|
101
|
+
// URL-encoding (va ikki marta kodlash) bilan chetlab o'tishga qarshi:
|
|
102
|
+
// yo'lni dekodlab, so'ng registrsizlantiramiz.
|
|
103
|
+
const rawPath = decodePathDeep(raw).toLowerCase();
|
|
104
|
+
|
|
105
|
+
// To'liq yo'l yoki uning prefiksi tuzoqqa mos kelsa.
|
|
106
|
+
let trapped = traps.has(rawPath);
|
|
107
|
+
if (!trapped) {
|
|
108
|
+
for (const t of traps) {
|
|
109
|
+
if (rawPath === t || rawPath.startsWith(t + '/')) { trapped = true; break; }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!trapped) return next();
|
|
114
|
+
|
|
115
|
+
const ip = keyGen(req);
|
|
116
|
+
logger.blocked(ip, 'HONEYPOT tuzog\'iga tushdi (instant ban)', { path: rawPath });
|
|
117
|
+
|
|
118
|
+
// 1. Firewall'da darhol bloklash.
|
|
119
|
+
if (options.firewall) {
|
|
120
|
+
// `ban()` haqiqiy BanRecord qaytaradi — uni 'ban' hodisasida ham yuboramiz,
|
|
121
|
+
// shunda iste'molchilar (dashboard/Alerter/custom handler) record.offenseCount
|
|
122
|
+
// kabi maydonlarga tayana oladi (firewall'ning o'z bani bilan izchil).
|
|
123
|
+
const record = options.firewall.blocker.ban(ip, `Honeypot: ${rawPath}`);
|
|
124
|
+
if (typeof options.firewall.emit === 'function') {
|
|
125
|
+
options.firewall.emit('ban', { ip, reason: `Honeypot: ${rawPath}`, record });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 2. OS firewall'da ham bloklash (ixtiyoriy).
|
|
130
|
+
if (options.osBlock && options.osFirewall) {
|
|
131
|
+
Promise.resolve(options.osFirewall.block(ip, { autoApprove: true }))
|
|
132
|
+
.catch((e) => logger.error(`Honeypot OS-blok xatosi: ${e.message}`));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 3. Foydalanuvchi hook'i.
|
|
136
|
+
if (typeof options.onTrap === 'function') {
|
|
137
|
+
try { options.onTrap({ ip, path: rawPath }); } catch { /* ignore */ }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Tabiiy ko'rinishi uchun oddiy 404 qaytaramiz (hujumchiga bilintirmaymiz).
|
|
141
|
+
return res.status
|
|
142
|
+
? res.status(statusCode).json({ error: 'Not found' })
|
|
143
|
+
: (res.statusCode = statusCode, res.end('Not found'));
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = honeypot;
|
|
148
|
+
module.exports.honeypot = honeypot;
|
|
149
|
+
module.exports.DEFAULT_TRAPS = DEFAULT_TRAPS;
|