mm_os 3.3.0 → 4.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 -201
- package/README.md +491 -99
- package/README_EN.md +498 -0
- package/adapter/adapter.js +431 -0
- package/adapter/custom_persistence.js +660 -0
- package/adapter/mqtt.js +273 -0
- package/adapter/socket.js +113 -0
- package/adapter/web.js +67 -0
- package/adapter/websocket.js +146 -0
- package/com/api/com.json +5 -0
- package/{core/com → com}/api/config.tpl.json +8 -8
- package/com/api/drive.js +708 -0
- package/com/api/index.js +198 -0
- package/com/api/oauth.js +200 -0
- package/com/api/script.tpl.js +32 -0
- package/com/cmd/README.md +11 -0
- package/com/cmd/com.json +5 -0
- package/com/cmd/config.tpl.json +122 -0
- package/com/cmd/drive.js +1548 -0
- package/com/cmd/index.js +1066 -0
- package/com/cmd/msg.json +48 -0
- package/com/cmd/nlp.js +525 -0
- package/com/cmd/script.tpl.js +32 -0
- package/com/db/com.json +5 -0
- package/com/db/drive.js +1999 -0
- package/com/db/index.js +242 -0
- package/com/event/com.json +5 -0
- package/{core/com → com}/event/config.tpl.json +8 -8
- package/com/event/drive.js +59 -0
- package/com/event/index.js +409 -0
- package/com/event/script.tpl.js +23 -0
- package/com/mqtt/com.json +5 -0
- package/{core/com → com}/mqtt/config.tpl.json +3 -5
- package/com/mqtt/drive.js +676 -0
- package/com/mqtt/index.js +822 -0
- package/com/mqtt/mm_mqtt.js +425 -0
- package/com/mqtt/script.tpl.js +723 -0
- package/com/nav/com.json +5 -0
- package/com/nav/config.tpl.json +84 -0
- package/com/nav/drive.js +702 -0
- package/com/nav/index.js +231 -0
- package/{core/com → com}/nav/tpl/admin_pc/page_config.vue +280 -280
- package/{core/com → com}/nav/tpl/admin_pc/page_config_form.vue +194 -194
- package/com/nav/tpl/admin_pc/page_form.vue +180 -0
- package/com/nav/tpl/admin_pc/page_view.vue +124 -0
- package/com/nav/tpl/dev_pc/page_default.vue +247 -0
- package/com/nav/tpl/dev_pc/page_type.vue +313 -0
- package/com/nav/tpl/home_pc/page_default.vue +234 -0
- package/com/nav/tpl/home_pc/page_form.vue +137 -0
- package/com/nav/tpl/home_pc/page_list.vue +234 -0
- package/com/nav/tpl/home_pc/page_nav.vue +221 -0
- package/com/nav/tpl/home_pc/page_type.vue +234 -0
- package/com/nav/tpl/home_pc/page_view.vue +125 -0
- package/com/nav/tpl/home_phone/page_channel.vue +234 -0
- package/com/nav/tpl/home_phone/page_default.vue +234 -0
- package/com/nav/tpl/home_phone/page_form.vue +137 -0
- package/com/nav/tpl/home_phone/page_nav.vue +237 -0
- package/com/nav/tpl/home_phone/page_type.vue +234 -0
- package/com/nav/tpl/home_phone/page_view.vue +125 -0
- package/com/nav/viewmodel.js +446 -0
- package/com/param/com.json +5 -0
- package/{core/com → com}/param/config.tpl.json +7 -1
- package/com/param/drive.js +502 -0
- package/com/param/index.js +155 -0
- package/com/param/script.tpl.js +12 -0
- package/com/pendant/com.json +5 -0
- package/{core/com/component → com/pendant}/config.tpl.json +15 -13
- package/com/pendant/drive.js +204 -0
- package/com/pendant/index.js +441 -0
- package/com/pendant/pendant.html +16 -0
- package/com/pendant/script.tpl.js +18 -0
- package/com/socket/com.json +5 -0
- package/com/socket/config.tpl.json +12 -0
- package/com/socket/drive.js +651 -0
- package/com/socket/index.js +351 -0
- package/com/socket/script.tpl.js +41 -0
- package/com/sql/com.json +5 -0
- package/{core/com → com}/sql/config.tpl.json +13 -9
- package/com/sql/drive.js +1259 -0
- package/com/sql/index.js +150 -0
- package/com/sql/script.tpl.js +47 -0
- package/com/static/com.json +5 -0
- package/{core/com → com}/static/config.tpl.json +10 -6
- package/com/static/drive.js +194 -0
- package/com/static/index.js +226 -0
- package/com/static/script.tpl.js +28 -0
- package/com/task/com.json +5 -0
- package/{core/com → com}/task/config.tpl.json +4 -6
- package/com/task/drive.js +405 -0
- package/com/task/index.js +148 -0
- package/com/task/script.tpl.js +37 -0
- package/com/template/com.json +5 -0
- package/com/template/config.tpl.json +16 -0
- package/com/template/drive.js +80 -0
- package/com/template/index.js +141 -0
- package/com.js +156 -0
- package/common/README.md +2 -0
- package/common/handler/msg/handler.json +22 -0
- package/common/handler/msg/index.js +23 -0
- package/common/handler/player/handler.json +22 -0
- package/common/handler/player/index.js +287 -0
- package/common/handler/user/handler.json +22 -0
- package/common/handler/user/index.js +23 -0
- package/common/middleware/web_after/index.js +29 -0
- package/common/middleware/web_after/middleware.json +9 -0
- package/common/middleware/web_base/index.js +113 -0
- package/common/middleware/web_base/middleware.json +19 -0
- package/common/middleware/web_before/index.js +33 -0
- package/common/middleware/web_before/middleware.json +9 -0
- package/common/middleware/web_cors/index.js +87 -0
- package/common/middleware/web_cors/middleware.json +24 -0
- package/common/middleware/web_error/index.js +119 -0
- package/common/middleware/web_error/middleware.json +18 -0
- package/common/middleware/web_ip/index.js +15 -0
- package/common/middleware/web_ip/middleware.json +14 -0
- package/common/middleware/web_logger/index.js +156 -0
- package/common/middleware/web_logger/middleware.json +14 -0
- package/common/middleware/web_main/index.js +24 -0
- package/common/middleware/web_main/middleware.json +9 -0
- package/common/middleware/web_static/index.js +73 -0
- package/common/middleware/web_static/middleware.json +54 -0
- package/common/middleware/web_waf/index.js +385 -0
- package/common/middleware/web_waf/middleware.json +13 -0
- package/common/model/msg/index.js +88 -0
- package/common/model/msg/model.json +401 -0
- package/common/model/player/index.js +63 -0
- package/common/model/player/model.json +185 -0
- package/common/model/user/index.js +11 -0
- package/common/model/user/model.json +219 -0
- package/core/app/config.tpl.json +67 -0
- package/core/app/index.js +632 -0
- package/core/app/script.tpl.js +52 -0
- package/core/channel/index.js +899 -0
- package/core/channel/matcher.js +585 -0
- package/core/com/config.tpl.json +16 -0
- package/core/com/index.js +74 -0
- package/core/com/script.tpl.js +5 -0
- package/core/component/component.js +42 -0
- package/core/component/config.tpl.json +63 -0
- package/core/component/index.js +273 -0
- package/core/component/script.tpl.js +19 -0
- package/core/controller/config.tpl.json +14 -0
- package/core/controller/index.js +373 -0
- package/core/controller/script.tpl.js +27 -0
- package/core/factory/config.tpl.json +14 -0
- package/core/factory/entity.js +275 -0
- package/core/factory/index.js +241 -0
- package/core/factory/script.tpl.js +16 -0
- package/core/game/bat/index.js +137 -0
- package/core/game/bat/world.js +622 -0
- package/core/game/config.tpl.json +16 -0
- package/core/game/entity_admin.js +230 -0
- package/core/game/index.js +186 -0
- package/core/handler/config.tpl.json +22 -0
- package/core/handler/index.js +181 -0
- package/core/handler/script.tpl.js +23 -0
- package/core/logic/config.tpl.json +14 -0
- package/core/logic/index.js +59 -0
- package/core/logic/script.tpl.js +19 -0
- package/core/middleware/config.tpl.json +16 -0
- package/core/middleware/index.js +125 -0
- package/core/middleware/script.tpl.js +37 -0
- package/core/mod/config.tpl.json +22 -0
- package/core/mod/index.js +130 -0
- package/core/mod/script.tpl.js +34 -0
- package/core/model/config.tpl.json +219 -0
- package/core/model/index.js +272 -0
- package/core/model/model.js +27 -0
- package/core/model/script.tpl.js +20 -0
- package/core/notifier/config.tpl.json +14 -0
- package/core/notifier/index.js +77 -0
- package/core/notifier/script.tpl.js +20 -0
- package/core/plugin/config.tpl.json +24 -0
- package/core/plugin/index.js +232 -0
- package/core/plugin/script.tpl.js +51 -0
- package/core/pusher/config.tpl.json +14 -0
- package/core/pusher/index.js +161 -0
- package/core/pusher/script.tpl.js +20 -0
- package/core/room/bat/index.js +170 -0
- package/core/room/bat/room.js +524 -0
- package/core/room/config.tpl.json +20 -0
- package/core/room/index.js +249 -0
- package/core/room/room.js +61 -0
- package/core/scene/config.tpl.json +14 -0
- package/core/scene/index.js +466 -0
- package/core/scene/loop.js +1255 -0
- package/core/scene/map.js +28 -0
- package/core/scene/script.tpl.js +22 -0
- package/core/sender/config.tpl.json +14 -0
- package/core/sender/index.js +79 -0
- package/core/sender/script.tpl.js +20 -0
- package/core/service/config.tpl.json +14 -0
- package/core/service/index.js +100 -0
- package/core/service/script.tpl.js +25 -0
- package/core/store/config.tpl.json +26 -0
- package/core/store/index.js +1755 -0
- package/core/store/script.tpl.js +22 -0
- package/core/store/sql.js +1464 -0
- package/core/system/config.tpl.json +18 -0
- package/core/system/index.js +312 -0
- package/core/system/script.tpl.js +77 -0
- package/core/view/config.tpl.json +14 -0
- package/core/view/index.js +91 -0
- package/core/view/script.tpl.js +20 -0
- package/core/zone/bat/index.js +725 -0
- package/core/zone/config.tpl.json +54 -0
- package/core/zone/index.js +614 -0
- package/core/zone/script.tpl.js +10 -0
- package/core/zone/zone_bat.js +136 -0
- package/core//345/237/272/347/261/273/346/250/241/345/235/227/346/270/205/345/215/225.md +24 -0
- package/index.js +17 -314
- package/os.js +57 -0
- package/package.json +60 -58
- package/server.js +598 -0
- package/README.en.md +0 -36
- package/conf.json +0 -3
- package/core/base/mqtt/index.js +0 -1107
- package/core/base/mqtt/lib.js +0 -40
- package/core/base/web/index.js +0 -243
- package/core/com/api/com.json +0 -4
- package/core/com/api/drive.js +0 -668
- package/core/com/api/index.js +0 -108
- package/core/com/api/oauth.js +0 -158
- package/core/com/api/script.js +0 -32
- package/core/com/app/README.md +0 -3
- package/core/com/app/com.json +0 -4
- package/core/com/app/config.tpl.json +0 -16
- package/core/com/app/drive.js +0 -309
- package/core/com/app/index.js +0 -211
- package/core/com/app/script.js +0 -155
- package/core/com/cmd/com.json +0 -4
- package/core/com/cmd/config.tpl.json +0 -66
- package/core/com/cmd/drive.js +0 -513
- package/core/com/cmd/index.js +0 -354
- package/core/com/cmd/old/5w2h.js +0 -54
- package/core/com/cmd/old/drive.js +0 -423
- package/core/com/cmd/script.js +0 -11
- package/core/com/component/README.md +0 -3
- package/core/com/component/com.json +0 -4
- package/core/com/component/component.html +0 -16
- package/core/com/component/drive.js +0 -197
- package/core/com/component/index.js +0 -312
- package/core/com/component/script.js +0 -18
- package/core/com/db/com.json +0 -4
- package/core/com/db/drive.js +0 -1160
- package/core/com/db/index.js +0 -176
- package/core/com/event/com.json +0 -4
- package/core/com/event/drive.js +0 -133
- package/core/com/event/index.js +0 -345
- package/core/com/event/script.js +0 -26
- package/core/com/eventer/com.js +0 -477
- package/core/com/eventer/com.json +0 -4
- package/core/com/middleware/com.js +0 -153
- package/core/com/middleware/com.json +0 -4
- package/core/com/middleware/config.tpl.json +0 -8
- package/core/com/middleware/script.js +0 -9
- package/core/com/mqtt/com.json +0 -4
- package/core/com/mqtt/drive.js +0 -600
- package/core/com/mqtt/index.js +0 -572
- package/core/com/mqtt/mm_mqtt.js +0 -330
- package/core/com/mqtt/script.js +0 -604
- package/core/com/msg/com.js +0 -296
- package/core/com/msg/com.json +0 -4
- package/core/com/nav/com.json +0 -4
- package/core/com/nav/config.tpl.json +0 -75
- package/core/com/nav/drive.js +0 -549
- package/core/com/nav/index.js +0 -182
- package/core/com/nav/tpl/admin_pc/page_form.vue +0 -180
- package/core/com/nav/tpl/admin_pc/page_view.vue +0 -124
- package/core/com/nav/tpl/dev_pc/page_default.vue +0 -247
- package/core/com/nav/tpl/dev_pc/page_type.vue +0 -313
- package/core/com/nav/tpl/home_pc/page_default.vue +0 -234
- package/core/com/nav/tpl/home_pc/page_form.vue +0 -137
- package/core/com/nav/tpl/home_pc/page_list.vue +0 -234
- package/core/com/nav/tpl/home_pc/page_nav.vue +0 -221
- package/core/com/nav/tpl/home_pc/page_type.vue +0 -234
- package/core/com/nav/tpl/home_pc/page_view.vue +0 -125
- package/core/com/nav/tpl/home_phone/page_channel.vue +0 -234
- package/core/com/nav/tpl/home_phone/page_default.vue +0 -234
- package/core/com/nav/tpl/home_phone/page_form.vue +0 -137
- package/core/com/nav/tpl/home_phone/page_nav.vue +0 -237
- package/core/com/nav/tpl/home_phone/page_type.vue +0 -234
- package/core/com/nav/tpl/home_phone/page_view.vue +0 -125
- package/core/com/nav/viewmodel.js +0 -296
- package/core/com/param/drive.js +0 -366
- package/core/com/param/index.js +0 -80
- package/core/com/param/script.js +0 -12
- package/core/com/param/test.js +0 -98
- package/core/com/plugin/README.md +0 -3
- package/core/com/plugin/com.json +0 -4
- package/core/com/plugin/config.tpl.json +0 -26
- package/core/com/plugin/drive.js +0 -536
- package/core/com/plugin/index.js +0 -259
- package/core/com/plugin/script.js +0 -213
- package/core/com/rpc/com.json +0 -4
- package/core/com/rpc/drive.js +0 -160
- package/core/com/rpc/index.js +0 -87
- package/core/com/rpc/rpc.js +0 -118
- package/core/com/socket/com.json +0 -4
- package/core/com/socket/config.tpl.json +0 -14
- package/core/com/socket/drive.js +0 -403
- package/core/com/socket/index.js +0 -62
- package/core/com/socket/script.js +0 -42
- package/core/com/sql/drive.js +0 -1087
- package/core/com/sql/index.js +0 -83
- package/core/com/sql/script.js +0 -48
- package/core/com/static/com.json +0 -4
- package/core/com/static/drive.js +0 -220
- package/core/com/static/index.js +0 -149
- package/core/com/static/script.js +0 -28
- package/core/com/task/com.json +0 -4
- package/core/com/task/drive.js +0 -403
- package/core/com/task/index.js +0 -110
- package/core/com/task/script.js +0 -37
- package/core/com/timer/com.js +0 -217
- package/core/com/timer/com.json +0 -4
- package/core/com/tpl/com.js +0 -19
- package/core/com/tpl/com.json +0 -4
- package/lib/actions.js +0 -50
- package/lib/base.js +0 -361
- package/lib/com.js +0 -29
- package/lib/ref.js +0 -121
- package/middleware/mqtt_base/index.js +0 -10
- package/middleware/mqtt_base/middleware.json +0 -10
- package/middleware/performance/index.js +0 -151
- package/middleware/performance/middleware.json +0 -16
- package/middleware/security_audit/index.js +0 -549
- package/middleware/security_audit/middleware.json +0 -48
- package/middleware/security_headers/index.js +0 -487
- package/middleware/security_headers/middleware.json +0 -45
- package/middleware/waf/index.js +0 -348
- package/middleware/waf/middleware.json +0 -10
- package/middleware/waf_ddos/index.js +0 -520
- package/middleware/waf_ddos/middleware.json +0 -38
- package/middleware/waf_ip/index.js +0 -379
- package/middleware/waf_ip/middleware.json +0 -49
- package/middleware/waf_xss/index.js +0 -269
- package/middleware/waf_xss/middleware.json +0 -18
- package/middleware/web_after/index.js +0 -33
- package/middleware/web_after/middleware.json +0 -9
- package/middleware/web_base/index.js +0 -90
- package/middleware/web_base/middleware.json +0 -9
- package/middleware/web_before/index.js +0 -27
- package/middleware/web_before/middleware.json +0 -9
- package/middleware/web_check/index.js +0 -28
- package/middleware/web_check/middleware.json +0 -9
- package/middleware/web_main/index.js +0 -28
- package/middleware/web_main/middleware.json +0 -9
- package/middleware/web_proxy/index.js +0 -37
- package/middleware/web_proxy/middleware.json +0 -9
- package/middleware/web_render/index.js +0 -87
- package/middleware/web_render/middleware.json +0 -9
- package/middleware/web_socket/index.js +0 -34
- package/middleware/web_socket/middleware.json +0 -9
- package/middleware/web_static/index.js +0 -115
- package/middleware/web_static/middleware.json +0 -9
- /package/{core/com → com}/api/README.md +0 -0
- /package/{core/com → com}/db/README.md +0 -0
- /package/{core/com → com}/event/README.md +0 -0
- /package/{core/com → com}/mqtt/README.md +0 -0
- /package/{core/com → com}/nav/README.md +0 -0
- /package/{core/com → com}/nav/tpl/admin_pc/page_channel.vue +0 -0
- /package/{core/com → com}/nav/tpl/admin_pc/page_default.vue +0 -0
- /package/{core/com → com}/nav/tpl/admin_pc/page_lang.vue +0 -0
- /package/{core/com → com}/nav/tpl/admin_pc/page_nav.vue +0 -0
- /package/{core/com → com}/nav/tpl/admin_pc/page_table.vue +0 -0
- /package/{core/com → com}/nav/tpl/admin_pc/page_type.vue +0 -0
- /package/{core/com → com}/nav/tpl/dev_pc/page_channel.vue +0 -0
- /package/{core/com → com}/nav/tpl/dev_pc/page_config.vue +0 -0
- /package/{core/com → com}/nav/tpl/dev_pc/page_form.vue +0 -0
- /package/{core/com → com}/nav/tpl/dev_pc/page_nav.vue +0 -0
- /package/{core/com → com}/nav/tpl/dev_pc/page_table.vue +0 -0
- /package/{core/com → com}/nav/tpl/home_pc/page_channel.vue +0 -0
- /package/{core/com → com}/nav/tpl/home_phone/page_list.vue +0 -0
- /package/{core/com → com}/param/README.md +0 -0
- /package/{core/com/cmd → com/pendant}/README.md +0 -0
- /package/{core/com → com}/socket/README.md +0 -0
- /package/{core/com → com}/sql/README.md +0 -0
- /package/{core/com → com}/static/README.md +0 -0
- /package/{core/com → com}/task/README.md +0 -0
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
exec
|
|
3
|
-
} = require('child_process');
|
|
4
|
-
const platform = require('os').platform();
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 获取客户端IP
|
|
8
|
-
* @param {Object} req 请求对象
|
|
9
|
-
* @returns {String} 返回真实IP
|
|
10
|
-
*/
|
|
11
|
-
function getClientIP(req) {
|
|
12
|
-
var ip = req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For'] || req.headers['x-real-ip'] ||
|
|
13
|
-
req.connection.remoteAddress ||
|
|
14
|
-
req.socket.remoteAddress ||
|
|
15
|
-
req.connection.socket.remoteAddress;
|
|
16
|
-
if (ip && ip.split(',').length > 0) {
|
|
17
|
-
ip = ip.split(',')[0]; // 取第一个IP地址
|
|
18
|
-
}
|
|
19
|
-
// 处理IPv6本地地址
|
|
20
|
-
if (ip === '::1' || ip === '::ffff:127.0.0.1') {
|
|
21
|
-
ip = '127.0.0.1';
|
|
22
|
-
}
|
|
23
|
-
// 移除可能的端口号
|
|
24
|
-
if (ip && ip.includes(':')) {
|
|
25
|
-
ip = ip.split(':')[0];
|
|
26
|
-
}
|
|
27
|
-
return ip;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 检查IP是否在白名单中
|
|
32
|
-
* @param {String} ip IP地址
|
|
33
|
-
* @param {Array} whitelist 白名单IP数组
|
|
34
|
-
* @returns {Boolean} 是否在白名单中
|
|
35
|
-
*/
|
|
36
|
-
function isInWhitelist(ip, whitelist) {
|
|
37
|
-
if (!whitelist || !Array.isArray(whitelist)) return false;
|
|
38
|
-
return whitelist.some(whiteIP => {
|
|
39
|
-
// 支持CIDR格式(如192.168.1.0/24)
|
|
40
|
-
if (whiteIP.includes('/')) {
|
|
41
|
-
return ipInCidr(ip, whiteIP);
|
|
42
|
-
}
|
|
43
|
-
return ip === whiteIP;
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 检查IP是否在CIDR范围内
|
|
49
|
-
* @param {String} ip IP地址
|
|
50
|
-
* @param {String} cidr CIDR格式字符串(如192.168.1.0/24)
|
|
51
|
-
* @returns {Boolean} 是否在范围内
|
|
52
|
-
*/
|
|
53
|
-
function ipInCidr(ip, cidr) {
|
|
54
|
-
const [network, prefixLength] = cidr.split('/');
|
|
55
|
-
const netmask = -1 << (32 - parseInt(prefixLength, 10));
|
|
56
|
-
|
|
57
|
-
function ipToInt(ip) {
|
|
58
|
-
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return (ipToInt(ip) & netmask) === (ipToInt(network) & netmask);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 设置黑名单
|
|
66
|
-
* @param {String} ip IP地址
|
|
67
|
-
* @param {Object} config 配置参数
|
|
68
|
-
*/
|
|
69
|
-
async function setting_blacklist(ip, config) {
|
|
70
|
-
// 检查是否已在黑名单中
|
|
71
|
-
const blacklistKey = `blacklist_${ip}`;
|
|
72
|
-
try {
|
|
73
|
-
const isBlacklisted = await $.cache.get(blacklistKey);
|
|
74
|
-
if (isBlacklisted) {
|
|
75
|
-
$.log.info(`IP ${ip} 已在黑名单中,无需重复添加`);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
} catch (error) {
|
|
79
|
-
$.log.error('检查黑名单缓存错误:', error);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
var cmd;
|
|
83
|
-
if (platform == "win32") {
|
|
84
|
-
// Windows系统
|
|
85
|
-
cmd = `netsh advfirewall firewall add rule name="Blacklist ${ip}" dir=in action=block remoteip="${ip}" protocol=any`;
|
|
86
|
-
} else {
|
|
87
|
-
// Linux系统 - 不使用sudo,让系统确保权限
|
|
88
|
-
cmd = `iptables -A INPUT -s ${ip} -j DROP`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
// 使用Promise包装exec以支持异步操作
|
|
93
|
-
await new Promise((resolve, reject) => {
|
|
94
|
-
exec(cmd, (error, stdout, stderr) => {
|
|
95
|
-
if (error) {
|
|
96
|
-
console.error(`执行黑名单命令错误: ${error}`);
|
|
97
|
-
reject(error);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
$.log.info(`IP ${ip} 已加入防火墙黑名单`);
|
|
101
|
-
if (stderr) {
|
|
102
|
-
console.error(`黑名单命令标准错误: ${stderr}`);
|
|
103
|
-
}
|
|
104
|
-
resolve();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// 记录到缓存,支持黑名单过期时间
|
|
109
|
-
const expireTime = config.blacklist_expire_time || 86400000; // 默认1天
|
|
110
|
-
await $.cache.set(blacklistKey, true, expireTime);
|
|
111
|
-
|
|
112
|
-
// 记录黑名单IP,便于管理
|
|
113
|
-
const blacklistIPsKey = 'blacklisted_ips';
|
|
114
|
-
let blacklistIPs = [];
|
|
115
|
-
try {
|
|
116
|
-
const existing = await $.cache.get(blacklistIPsKey);
|
|
117
|
-
if (existing) {
|
|
118
|
-
blacklistIPs = typeof existing === 'string' ? JSON.parse(existing) : existing;
|
|
119
|
-
}
|
|
120
|
-
if (!Array.isArray(blacklistIPs)) {
|
|
121
|
-
blacklistIPs = [];
|
|
122
|
-
}
|
|
123
|
-
if (!blacklistIPs.includes(ip)) {
|
|
124
|
-
blacklistIPs.push(ip);
|
|
125
|
-
await $.cache.set(blacklistIPsKey, blacklistIPs, 0); // 永不过期
|
|
126
|
-
}
|
|
127
|
-
} catch (cacheError) {
|
|
128
|
-
$.log.error('保存黑名单IP列表错误:', cacheError);
|
|
129
|
-
}
|
|
130
|
-
} catch (error) {
|
|
131
|
-
$.log.error(`添加IP ${ip} 到黑名单失败:`, error);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* 从防火墙中移除黑名单IP
|
|
137
|
-
* @param {String} ip IP地址
|
|
138
|
-
*/
|
|
139
|
-
async function remove_blacklist(ip) {
|
|
140
|
-
var cmd;
|
|
141
|
-
if (platform == "win32") {
|
|
142
|
-
// Windows系统
|
|
143
|
-
cmd = `netsh advfirewall firewall delete rule name="Blacklist ${ip}"`;
|
|
144
|
-
} else {
|
|
145
|
-
// Linux系统
|
|
146
|
-
cmd = `iptables -D INPUT -s ${ip} -j DROP`;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
await new Promise((resolve, reject) => {
|
|
151
|
-
exec(cmd, (error, stdout, stderr) => {
|
|
152
|
-
if (error) {
|
|
153
|
-
console.error(`移除黑名单命令错误: ${error}`);
|
|
154
|
-
// 对于Windows,如果规则不存在,delete命令也会返回错误,但这不是严重问题
|
|
155
|
-
if (platform !== "win32") {
|
|
156
|
-
reject(error);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
$.log.info(`IP ${ip} 已从防火墙黑名单中移除`);
|
|
161
|
-
if (stderr) {
|
|
162
|
-
console.error(`移除黑名单命令标准错误: ${stderr}`);
|
|
163
|
-
}
|
|
164
|
-
resolve();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// 从缓存中移除
|
|
169
|
-
await $.cache.del(`blacklist_${ip}`);
|
|
170
|
-
|
|
171
|
-
// 从黑名单列表中移除
|
|
172
|
-
const blacklistIPsKey = 'blacklisted_ips';
|
|
173
|
-
try {
|
|
174
|
-
const existing = await $.cache.get(blacklistIPsKey);
|
|
175
|
-
if (existing) {
|
|
176
|
-
let blacklistIPs = typeof existing === 'string' ? JSON.parse(existing) : existing;
|
|
177
|
-
if (Array.isArray(blacklistIPs)) {
|
|
178
|
-
blacklistIPs = blacklistIPs.filter(ipItem => ipItem !== ip);
|
|
179
|
-
await $.cache.set(blacklistIPsKey, blacklistIPs, 0);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
} catch (cacheError) {
|
|
183
|
-
$.log.error('更新黑名单IP列表错误:', cacheError);
|
|
184
|
-
}
|
|
185
|
-
} catch (error) {
|
|
186
|
-
$.log.error(`移除IP ${ip} 从黑名单失败:`, error);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* 初始化功能 - 加载持久化的黑名单并应用
|
|
192
|
-
* @param {Object} config 配置参数
|
|
193
|
-
*/
|
|
194
|
-
async function initBlacklist(config) {
|
|
195
|
-
if (!config.persist_blacklist) return;
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
const blacklistIPsKey = 'blacklisted_ips';
|
|
199
|
-
const existing = await $.cache.get(blacklistIPsKey);
|
|
200
|
-
if (existing) {
|
|
201
|
-
let blacklistIPs = typeof existing === 'string' ? JSON.parse(existing) : existing;
|
|
202
|
-
if (Array.isArray(blacklistIPs) && blacklistIPs.length > 0) {
|
|
203
|
-
$.log.info(`正在应用 ${blacklistIPs.length} 个持久化的黑名单IP规则...`);
|
|
204
|
-
for (const ip of blacklistIPs) {
|
|
205
|
-
// 重新应用黑名单规则
|
|
206
|
-
await setting_blacklist(ip, config);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
} catch (error) {
|
|
211
|
-
$.log.error('初始化持久化黑名单失败:', error);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* IP防火墙
|
|
217
|
-
* @param {Object} server 服务
|
|
218
|
-
* @param {Object} config 配置参数
|
|
219
|
-
*/
|
|
220
|
-
module.exports = async function(server, config) {
|
|
221
|
-
// 从配置中获取参数,提供默认值
|
|
222
|
-
var limit = config.request_limit || 1000; // 默认每分钟1000请求
|
|
223
|
-
var duration = config.request_duration || 60000; // 默认60秒
|
|
224
|
-
var block = config.request_block || true; // 默认启用黑名单
|
|
225
|
-
var whitelist = config.ip_whitelist || []; // IP白名单
|
|
226
|
-
var blacklist_expire_time = config.blacklist_expire_time || 86400000; // 默认黑名单1天过期
|
|
227
|
-
var persist_blacklist = config.persist_blacklist !== undefined ? config.persist_blacklist : true; // 是否持久化黑名单
|
|
228
|
-
|
|
229
|
-
// 初始化持久化黑名单
|
|
230
|
-
if (persist_blacklist) {
|
|
231
|
-
await initBlacklist(config);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (limit && duration) {
|
|
235
|
-
/* WAF(web防火墙) */
|
|
236
|
-
server.use(async (ctx, next) => {
|
|
237
|
-
try {
|
|
238
|
-
var pass = true;
|
|
239
|
-
// 获取IP
|
|
240
|
-
var ip = getClientIP(ctx.req);
|
|
241
|
-
|
|
242
|
-
// 检查白名单
|
|
243
|
-
if (isInWhitelist(ip, whitelist)) {
|
|
244
|
-
ctx.request.ip = ip;
|
|
245
|
-
ctx.ip = ip;
|
|
246
|
-
await next();
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// 检查是否已被黑名单
|
|
251
|
-
try {
|
|
252
|
-
const isBlacklisted = await $.cache.get(`blacklist_${ip}`);
|
|
253
|
-
if (isBlacklisted) {
|
|
254
|
-
ctx.status = 403;
|
|
255
|
-
ctx.body = '您的IP已被禁止访问';
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
} catch (blacklistCheckError) {
|
|
259
|
-
$.log.error('检查黑名单状态错误:', blacklistCheckError);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
var num = 1;
|
|
263
|
-
var now = new Date();
|
|
264
|
-
var date = now.toStr('yyyy-MM-dd');
|
|
265
|
-
var time;
|
|
266
|
-
var json;
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
var str = await $.cache.get("ip_" + ip);
|
|
270
|
-
if (str) {
|
|
271
|
-
if (typeof(str) === "string") {
|
|
272
|
-
try {
|
|
273
|
-
json = JSON.parse(str);
|
|
274
|
-
} catch (jsonError) {
|
|
275
|
-
$.log.error('WAF IP中间件JSON解析错误:', jsonError);
|
|
276
|
-
json = null;
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
json = str;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (json) {
|
|
283
|
-
try {
|
|
284
|
-
if (json.date !== date) {
|
|
285
|
-
num = 1;
|
|
286
|
-
} else {
|
|
287
|
-
// 判断时间间隔是否在范围外
|
|
288
|
-
// 安全地处理json.time,无论它是字符串还是对象
|
|
289
|
-
if (typeof json.time === 'string') {
|
|
290
|
-
// 如果json.time是字符串,尝试解析它
|
|
291
|
-
const savedTime = new Date(json.time);
|
|
292
|
-
if (!isNaN(savedTime.getTime()) && (now - savedTime) > duration) {
|
|
293
|
-
num = 1;
|
|
294
|
-
} else {
|
|
295
|
-
// 如果是在周期内,访问次数+1,并判断是否超出上限
|
|
296
|
-
num = json.num + 1;
|
|
297
|
-
if (num > limit) {
|
|
298
|
-
// 超出上限禁止访问,并加入黑名单
|
|
299
|
-
pass = false;
|
|
300
|
-
if (block) {
|
|
301
|
-
await setting_blacklist(ip, config);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
} else if (json.time && json.time.toTime && typeof json.time.toTime().interval === 'function') {
|
|
306
|
-
// 原有逻辑,处理对象类型的时间
|
|
307
|
-
if (json.time.toTime().interval(now) > duration) {
|
|
308
|
-
num = 1;
|
|
309
|
-
} else {
|
|
310
|
-
// 如果是在周期内,访问次数+1,并判断是否超出上限
|
|
311
|
-
num = json.num + 1;
|
|
312
|
-
if (num > limit) {
|
|
313
|
-
// 超出上限禁止访问,并加入黑名单
|
|
314
|
-
pass = false;
|
|
315
|
-
if (block) {
|
|
316
|
-
await setting_blacklist(ip, config);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
} else {
|
|
321
|
-
// 默认情况:如果时间格式不正确,重置计数
|
|
322
|
-
num = 1;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
} catch (timeError) {
|
|
326
|
-
$.log.error('WAF IP中间件时间处理错误:', timeError);
|
|
327
|
-
// 出错时重置计数以保证安全
|
|
328
|
-
num = 1;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
} catch (cacheError) {
|
|
333
|
-
$.log.error('WAF IP中间件缓存操作错误:', cacheError);
|
|
334
|
-
// 缓存出错时,默认允许请求通过
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (!time) {
|
|
338
|
-
time = now.toStr('yyyy-MM-dd hh:mm:ss');
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (pass) {
|
|
342
|
-
try {
|
|
343
|
-
await $.cache.set("ip_" + ip, JSON.stringify({
|
|
344
|
-
date,
|
|
345
|
-
time,
|
|
346
|
-
num
|
|
347
|
-
}), duration);
|
|
348
|
-
} catch (setCacheError) {
|
|
349
|
-
$.log.error('WAF IP中间件缓存设置错误:', setCacheError);
|
|
350
|
-
// 缓存设置失败不影响请求处理
|
|
351
|
-
}
|
|
352
|
-
ctx.request.ip = ip;
|
|
353
|
-
ctx.ip = ip;
|
|
354
|
-
await next();
|
|
355
|
-
} else {
|
|
356
|
-
ctx.status = 429;
|
|
357
|
-
ctx.body = '请求频率过高,请稍后再试。';
|
|
358
|
-
}
|
|
359
|
-
} catch (error) {
|
|
360
|
-
$.log.error('WAF IP中间件错误:', error);
|
|
361
|
-
// 出错时默认允许请求通过
|
|
362
|
-
ctx.ip = getClientIP(ctx.req);
|
|
363
|
-
await next();
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// 提供手动管理黑名单的API方法
|
|
369
|
-
if (!$.waf_ip) {
|
|
370
|
-
$.waf_ip = {
|
|
371
|
-
addBlacklist: setting_blacklist,
|
|
372
|
-
removeBlacklist: remove_blacklist,
|
|
373
|
-
getClientIP: getClientIP,
|
|
374
|
-
isInWhitelist: isInWhitelist
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return server;
|
|
379
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "web_waf_ip",
|
|
3
|
-
"title": "IP防火墙增强版",
|
|
4
|
-
"description": "增强版IP防火墙,支持IP白名单、黑名单持久化、自动过期等功能,有效防止DOS攻击",
|
|
5
|
-
"version": "2.0",
|
|
6
|
-
"type": "web",
|
|
7
|
-
"process_type": "common_before",
|
|
8
|
-
"sort": 10,
|
|
9
|
-
"state": 1,
|
|
10
|
-
"config": {
|
|
11
|
-
"request_limit": {
|
|
12
|
-
"type": "number",
|
|
13
|
-
"title": "请求限制数量",
|
|
14
|
-
"description": "在指定时间内允许的最大请求数",
|
|
15
|
-
"value": 1000,
|
|
16
|
-
"placeholder": "请输入数字"
|
|
17
|
-
},
|
|
18
|
-
"request_duration": {
|
|
19
|
-
"type": "number",
|
|
20
|
-
"title": "统计时间窗口(毫秒)",
|
|
21
|
-
"description": "请求计数的时间窗口,单位为毫秒",
|
|
22
|
-
"value": 60000,
|
|
23
|
-
"placeholder": "请输入毫秒数"
|
|
24
|
-
},
|
|
25
|
-
"request_block": {
|
|
26
|
-
"type": "boolean",
|
|
27
|
-
"title": "启用黑名单封禁",
|
|
28
|
-
"description": "是否将超限IP添加到系统防火墙黑名单",
|
|
29
|
-
"value": true
|
|
30
|
-
},
|
|
31
|
-
"ip_whitelist": [
|
|
32
|
-
"192.168.31.5",
|
|
33
|
-
"127.0.0.1"
|
|
34
|
-
],
|
|
35
|
-
"blacklist_expire_time": {
|
|
36
|
-
"type": "number",
|
|
37
|
-
"title": "黑名单过期时间(毫秒)",
|
|
38
|
-
"description": "IP被添加到黑名单后自动失效的时间,单位为毫秒",
|
|
39
|
-
"value": 86400000,
|
|
40
|
-
"placeholder": "请输入毫秒数"
|
|
41
|
-
},
|
|
42
|
-
"persist_blacklist": {
|
|
43
|
-
"type": "boolean",
|
|
44
|
-
"title": "持久化黑名单",
|
|
45
|
-
"description": "系统重启后是否自动重新应用已保存的黑名单规则",
|
|
46
|
-
"value": true
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
const mm_expand = require('mm_expand');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* XSS攻击防护中间件
|
|
5
|
-
*/
|
|
6
|
-
class Middleware {
|
|
7
|
-
/**
|
|
8
|
-
* 构造函数
|
|
9
|
-
*/
|
|
10
|
-
constructor() {
|
|
11
|
-
this.default = {
|
|
12
|
-
// 过滤模式: sanitize(清理) | encode(编码) | both(同时进行)
|
|
13
|
-
mode: 'both',
|
|
14
|
-
// 是否过滤URL参数
|
|
15
|
-
query: true,
|
|
16
|
-
// 是否过滤表单数据
|
|
17
|
-
body: true,
|
|
18
|
-
// 是否过滤Cookie
|
|
19
|
-
cookie: true,
|
|
20
|
-
// 白名单标签
|
|
21
|
-
allowedTags: ['p', 'span', 'b', 'i', 'u', 'strong', 'em', 'br'],
|
|
22
|
-
// 白名单属性
|
|
23
|
-
allowedAttributes: ['id', 'class', 'style'],
|
|
24
|
-
// 是否记录XSS攻击尝试
|
|
25
|
-
log: true,
|
|
26
|
-
// 是否阻止恶意请求
|
|
27
|
-
block: true
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 初始化中间件
|
|
33
|
-
* @param {Object} config 配置项
|
|
34
|
-
*/
|
|
35
|
-
init(config) {
|
|
36
|
-
config = Object.assign({}, this.default, config);
|
|
37
|
-
this.config = config;
|
|
38
|
-
return function() {
|
|
39
|
-
// 返回中间件的run方法作为实际的处理函数
|
|
40
|
-
return function(ctx, next) {
|
|
41
|
-
return middleware.run(ctx, next);
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 获取客户端真实IP
|
|
48
|
-
* 在代理环境中,优先从代理头获取真实IP
|
|
49
|
-
* @param {Object} ctx Koa上下文
|
|
50
|
-
* @returns {String} 客户端真实IP
|
|
51
|
-
*/
|
|
52
|
-
getClientIp(ctx) {
|
|
53
|
-
const headers = ctx.headers;
|
|
54
|
-
// 优先检查常用代理头获取真实IP
|
|
55
|
-
// 注意:X-Forwarded-For 可能包含多个IP,取第一个非未知IP的值
|
|
56
|
-
const xForwardedFor = headers['x-forwarded-for'];
|
|
57
|
-
if (xForwardedFor) {
|
|
58
|
-
// X-Forwarded-For 格式通常为: client, proxy1, proxy2
|
|
59
|
-
const ips = xForwardedFor.split(',').map(ip => ip.trim());
|
|
60
|
-
for (let i = 0; i < ips.length; i++) {
|
|
61
|
-
const ip = ips[i];
|
|
62
|
-
if (ip && ip !== 'unknown' && ip !== '127.0.0.1' && ip !== '::1') {
|
|
63
|
-
return ip;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 检查其他常见的代理头
|
|
69
|
-
return headers['x-real-ip'] ||
|
|
70
|
-
headers['x-client-ip'] ||
|
|
71
|
-
headers['cf-connecting-ip'] || // CloudFlare
|
|
72
|
-
headers['fastly-client-ip'] || // Fastly
|
|
73
|
-
headers['true-client-ip'] || // Akamai and Cloudflare
|
|
74
|
-
ctx.ip; // 默认回退到ctx.ip
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 执行中间件
|
|
79
|
-
* @param {Object} ctx Koa上下文
|
|
80
|
-
* @param {Function} next 下一个中间件
|
|
81
|
-
*/
|
|
82
|
-
async run(ctx, next) {
|
|
83
|
-
const config = this.config;
|
|
84
|
-
let hasXSS = false;
|
|
85
|
-
let attackInfo = {};
|
|
86
|
-
|
|
87
|
-
// 清理URL查询参数
|
|
88
|
-
if (config.query && ctx.query) {
|
|
89
|
-
for (let key in ctx.query) {
|
|
90
|
-
const original = ctx.query[key];
|
|
91
|
-
const sanitized = this.sanitizeInput(original, config);
|
|
92
|
-
if (original !== sanitized) {
|
|
93
|
-
ctx.query[key] = sanitized;
|
|
94
|
-
hasXSS = true;
|
|
95
|
-
attackInfo[`query.${key}`] = { original, sanitized };
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 清理请求体数据
|
|
101
|
-
if (config.body && ctx.request.body) {
|
|
102
|
-
this.cleanObject(ctx.request.body, config, attackInfo, 'body', hasXSS);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 清理Cookie
|
|
106
|
-
if (config.cookie && ctx.cookies) {
|
|
107
|
-
const cookies = ctx.headers.cookie;
|
|
108
|
-
if (cookies) {
|
|
109
|
-
// 记录Cookie中的XSS尝试但不修改,因为修改Cookie需要重新设置
|
|
110
|
-
const cookieArr = cookies.split(';');
|
|
111
|
-
for (let cookie of cookieArr) {
|
|
112
|
-
const [name, value] = cookie.split('=').map(item => item.trim());
|
|
113
|
-
const sanitized = this.sanitizeInput(decodeURIComponent(value), config);
|
|
114
|
-
if (value !== sanitized) {
|
|
115
|
-
hasXSS = true;
|
|
116
|
-
attackInfo[`cookie.${name}`] = { original: value, sanitized };
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 记录XSS攻击尝试
|
|
123
|
-
if (hasXSS && config.log && $.log) {
|
|
124
|
-
$.log.warn('XSS Attack Attempt Detected:', {
|
|
125
|
-
ip: this.getClientIp(ctx),
|
|
126
|
-
path: ctx.path,
|
|
127
|
-
method: ctx.method,
|
|
128
|
-
attackInfo
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// 阻止恶意请求
|
|
133
|
-
if (hasXSS && config.block) {
|
|
134
|
-
ctx.status = 403;
|
|
135
|
-
ctx.body = {
|
|
136
|
-
code: 403,
|
|
137
|
-
msg: 'Forbidden: Potential XSS attack detected'
|
|
138
|
-
};
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 添加安全响应头
|
|
143
|
-
this.addSecurityHeaders(ctx);
|
|
144
|
-
|
|
145
|
-
await next();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* 递归清理对象中的XSS内容
|
|
150
|
-
* @param {Object} obj 要清理的对象
|
|
151
|
-
* @param {Object} config 配置项
|
|
152
|
-
* @param {Object} attackInfo 攻击信息收集对象
|
|
153
|
-
* @param {String} prefix 路径前缀
|
|
154
|
-
* @param {Boolean} hasXSS 是否已发现XSS
|
|
155
|
-
*/
|
|
156
|
-
cleanObject(obj, config, attackInfo, prefix, hasXSS) {
|
|
157
|
-
if (!obj || typeof obj !== 'object') return;
|
|
158
|
-
|
|
159
|
-
for (let key in obj) {
|
|
160
|
-
const path = `${prefix}.${key}`;
|
|
161
|
-
if (typeof obj[key] === 'string') {
|
|
162
|
-
const original = obj[key];
|
|
163
|
-
const sanitized = this.sanitizeInput(original, config);
|
|
164
|
-
if (original !== sanitized) {
|
|
165
|
-
obj[key] = sanitized;
|
|
166
|
-
hasXSS = true;
|
|
167
|
-
attackInfo[path] = { original, sanitized };
|
|
168
|
-
}
|
|
169
|
-
} else if (typeof obj[key] === 'object') {
|
|
170
|
-
this.cleanObject(obj[key], config, attackInfo, path, hasXSS);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 清理输入内容
|
|
177
|
-
* @param {String} input 输入内容
|
|
178
|
-
* @param {Object} config 配置项
|
|
179
|
-
* @returns {String} 清理后的内容
|
|
180
|
-
*/
|
|
181
|
-
sanitizeInput(input, config) {
|
|
182
|
-
if (!input || typeof input !== 'string') return input;
|
|
183
|
-
|
|
184
|
-
let output = input;
|
|
185
|
-
|
|
186
|
-
// 根据模式选择清理方式
|
|
187
|
-
if (config.mode === 'sanitize' || config.mode === 'both') {
|
|
188
|
-
output = this.sanitizeHTML(output, config);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (config.mode === 'encode' || config.mode === 'both') {
|
|
192
|
-
output = this.encodeHTML(output);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return output;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* 清理HTML内容
|
|
200
|
-
* @param {String} html HTML内容
|
|
201
|
-
* @param {Object} config 配置项
|
|
202
|
-
* @returns {String} 清理后的内容
|
|
203
|
-
*/
|
|
204
|
-
sanitizeHTML(html, config) {
|
|
205
|
-
// 简单的HTML标签清理实现
|
|
206
|
-
// 在生产环境中,可以使用更强大的库如DOMPurify
|
|
207
|
-
let clean = html;
|
|
208
|
-
|
|
209
|
-
// 移除所有脚本标签
|
|
210
|
-
clean = clean.replace(/<script[^>]*>.*?<\/script>/gi, '');
|
|
211
|
-
|
|
212
|
-
// 移除所有事件处理器
|
|
213
|
-
clean = clean.replace(/\s+on\w+\s*=\s*["\']?[^"\'>\s]+/gi, '');
|
|
214
|
-
|
|
215
|
-
// 移除危险的URL协议
|
|
216
|
-
clean = clean.replace(/(src|href|data|action)\s*=\s*["\']?(javascript|data|vbscript):/gi, '$1=');
|
|
217
|
-
|
|
218
|
-
return clean;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* 编码HTML特殊字符
|
|
223
|
-
* @param {String} html HTML内容
|
|
224
|
-
* @returns {String} 编码后的内容
|
|
225
|
-
*/
|
|
226
|
-
encodeHTML(html) {
|
|
227
|
-
return String(html)
|
|
228
|
-
.replace(/&/g, '&')
|
|
229
|
-
.replace(/</g, '<')
|
|
230
|
-
.replace(/>/g, '>')
|
|
231
|
-
.replace(/"/g, '"')
|
|
232
|
-
.replace(/'/g, ''');
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* 添加安全响应头
|
|
237
|
-
* @param {Object} ctx Koa上下文
|
|
238
|
-
*/
|
|
239
|
-
addSecurityHeaders(ctx) {
|
|
240
|
-
// X-XSS-Protection 头
|
|
241
|
-
ctx.set('X-XSS-Protection', '1; mode=block');
|
|
242
|
-
|
|
243
|
-
// Content-Security-Policy 头
|
|
244
|
-
ctx.set('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;");
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// 创建中间件实例
|
|
249
|
-
const middleware = new Middleware();
|
|
250
|
-
|
|
251
|
-
// 导出符合系统期望的函数
|
|
252
|
-
exports = module.exports = function(server, config) {
|
|
253
|
-
// 初始化中间件
|
|
254
|
-
const handlerFactory = middleware.init(config);
|
|
255
|
-
const middlewareHandler = handlerFactory();
|
|
256
|
-
|
|
257
|
-
// 直接使用server.use注册中间件
|
|
258
|
-
server.use(middlewareHandler);
|
|
259
|
-
|
|
260
|
-
// 记录中间件初始化信息
|
|
261
|
-
if ($.log && $.log.info) {
|
|
262
|
-
$.log.info(`XSS防护中间件已加载: 模式=${middleware.config.mode}, 过滤=${middleware.config.query ? 'query' : ''}${middleware.config.body ? ' body' : ''}${middleware.config.cookie ? ' cookie' : ''}`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return server;
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
// 保留原始实例,以便其他方式调用
|
|
269
|
-
exports.middleware = middleware;
|