chanjs 2.0.3 → 2.0.5
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/common/api.js +20 -0
- package/common/code.js +20 -0
- package/extend/api.js +21 -0
- package/extend/file.js +92 -0
- package/extend/upload.js +80 -0
- package/extend/utils.js +342 -0
- package/global/env.js +5 -0
- package/global/global.js +27 -0
- package/global/import.js +46 -0
- package/global/index.js +3 -0
- package/helper/api.js +14 -0
- package/helper/bind.js +21 -0
- package/helper/dataparse.js +24 -0
- package/helper/db.js +75 -0
- package/helper/file.js +202 -0
- package/helper/html.js +19 -0
- package/helper/index.js +25 -0
- package/helper/ip.js +36 -0
- package/helper/jwt.js +41 -0
- package/helper/loader.js +65 -0
- package/helper/response.js +20 -0
- package/helper/safe.js +263 -0
- package/helper/time.js +28 -0
- package/helper/tree.js +32 -0
- package/index.js +12 -6
- package/middleware/header.js +3 -3
- package/middleware/safe.js +144 -0
- package/middleware/waf.js +197 -0
- package/package.json +1 -1
- package/utils/dataparse.js +24 -0
- package/utils/db.js +66 -68
- package/utils/loader.js +23 -24
@@ -0,0 +1,197 @@
|
|
1
|
+
import url from "url";
|
2
|
+
import {getIp} from "../helper/ip.js";
|
3
|
+
|
4
|
+
// 原始关键词列表(保持不变)
|
5
|
+
const keywords = [
|
6
|
+
".aspx",
|
7
|
+
".php",
|
8
|
+
".pl",
|
9
|
+
".jsa",
|
10
|
+
".jsp",
|
11
|
+
".asp",
|
12
|
+
".go",
|
13
|
+
".jhtml",
|
14
|
+
".shtml",
|
15
|
+
".cfm",
|
16
|
+
".cgi",
|
17
|
+
".svn",
|
18
|
+
".env",
|
19
|
+
".keys",
|
20
|
+
".cache",
|
21
|
+
".hidden",
|
22
|
+
".bod",
|
23
|
+
".ll",
|
24
|
+
".backup",
|
25
|
+
".json",
|
26
|
+
".xml",
|
27
|
+
".bak",
|
28
|
+
".aws",
|
29
|
+
".database",
|
30
|
+
".cookie",
|
31
|
+
".location",
|
32
|
+
".dump",
|
33
|
+
".ftp",
|
34
|
+
".idea",
|
35
|
+
".s3",
|
36
|
+
".sh",
|
37
|
+
".old",
|
38
|
+
".tf",
|
39
|
+
".sql",
|
40
|
+
".vscode",
|
41
|
+
".docker",
|
42
|
+
".map",
|
43
|
+
"1+1",
|
44
|
+
".save",
|
45
|
+
".gz",
|
46
|
+
".yml",
|
47
|
+
".tar",
|
48
|
+
".rar",
|
49
|
+
".7z",
|
50
|
+
".zip",
|
51
|
+
".git",
|
52
|
+
".log",
|
53
|
+
".local",
|
54
|
+
"../",
|
55
|
+
"db_",
|
56
|
+
"smtp",
|
57
|
+
"meta",
|
58
|
+
"debug",
|
59
|
+
"secret",
|
60
|
+
"/xampp/",
|
61
|
+
"/metadata/",
|
62
|
+
"/internal/",
|
63
|
+
"/aws/",
|
64
|
+
"/debug/",
|
65
|
+
"/configs/",
|
66
|
+
"/cgi-bin/",
|
67
|
+
"/tmp/",
|
68
|
+
"/staging/",
|
69
|
+
"/mail/",
|
70
|
+
"/docker/",
|
71
|
+
"/.secure/",
|
72
|
+
"/php-cgi/",
|
73
|
+
"/wp-",
|
74
|
+
"/backup/",
|
75
|
+
"password",
|
76
|
+
"redirect",
|
77
|
+
"/phpMyAdmin/",
|
78
|
+
"/setup/",
|
79
|
+
"concat(",
|
80
|
+
"version(",
|
81
|
+
"sleep(",
|
82
|
+
"benchmark(",
|
83
|
+
"0x7e",
|
84
|
+
"extractvalue(",
|
85
|
+
"(select",
|
86
|
+
"a%",
|
87
|
+
"union",
|
88
|
+
"drop",
|
89
|
+
"update",
|
90
|
+
"insert",
|
91
|
+
"delete",
|
92
|
+
"alter",
|
93
|
+
"truncate",
|
94
|
+
"create",
|
95
|
+
"exec",
|
96
|
+
];
|
97
|
+
|
98
|
+
// 预处理:合并字符串关键词和正则关键词为两个单一正则(核心优化)
|
99
|
+
const { combinedStrRegex, combinedRegRegex } = (() => {
|
100
|
+
const regexSpecialChars = /[.*+?^${}()|[\]\\]/g;
|
101
|
+
const strKwParts = []; // 存储无特殊字符的关键词(用于合并正则)
|
102
|
+
const regKwParts = []; // 存储转义后的正则关键词(用于合并正则)
|
103
|
+
|
104
|
+
keywords.forEach((keyword) => {
|
105
|
+
if (regexSpecialChars.test(keyword)) {
|
106
|
+
// 含正则特殊字符:转义后加入正则关键词部分
|
107
|
+
const escaped = keyword.replace(regexSpecialChars, "\\$&");
|
108
|
+
regKwParts.push(escaped);
|
109
|
+
} else {
|
110
|
+
// 无特殊字符:直接加入字符串关键词部分
|
111
|
+
strKwParts.push(keyword);
|
112
|
+
}
|
113
|
+
});
|
114
|
+
|
115
|
+
// 构建合并后的正则(空数组时返回匹配失败的正则,避免报错)
|
116
|
+
const buildCombinedRegex = (parts) => {
|
117
|
+
return parts.length
|
118
|
+
? new RegExp(`(?:${parts.join("|")})`, "i") // 非捕获组+不区分大小写
|
119
|
+
: new RegExp("^$"); // 匹配空字符串(永远不命中)
|
120
|
+
};
|
121
|
+
|
122
|
+
return {
|
123
|
+
combinedStrRegex: buildCombinedRegex(strKwParts),
|
124
|
+
combinedRegRegex: buildCombinedRegex(regKwParts),
|
125
|
+
};
|
126
|
+
})();
|
127
|
+
|
128
|
+
const safe = (req, res, next) => {
|
129
|
+
try {
|
130
|
+
// 1. 设置安全头(保持不变)
|
131
|
+
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
132
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
133
|
+
res.setHeader("Referrer-Policy", "no-referrer-when-downgrade");
|
134
|
+
res.removeHeader("Server");
|
135
|
+
|
136
|
+
// 2. 构建检查文本:req.path(仅路径)+ query(非空才加)(优化冗余)
|
137
|
+
let checkText = req.path || "";
|
138
|
+
if (req.query && Object.keys(req.query).length > 0) {
|
139
|
+
const queryStr = Object.entries(req.query)
|
140
|
+
.map(([k, v]) => `${k}=${v}`)
|
141
|
+
.join(' ');
|
142
|
+
checkText += ` ${queryStr}`;
|
143
|
+
}
|
144
|
+
|
145
|
+
// 3. 处理请求体(优化序列化逻辑)
|
146
|
+
let bodyText = "";
|
147
|
+
const contentType = req.headers["content-type"] || "";
|
148
|
+
const isMultipart = contentType.includes("multipart/form-data");
|
149
|
+
|
150
|
+
if (!isMultipart && req.body) {
|
151
|
+
try {
|
152
|
+
// 若已是字符串直接用,否则序列化(避免重复序列化)
|
153
|
+
const bodyStr =
|
154
|
+
typeof req.body === "string" ? req.body : JSON.stringify(req.body);
|
155
|
+
|
156
|
+
// 限制大小(保持原逻辑,避免大文本开销)
|
157
|
+
if (bodyStr.length < 10000) {
|
158
|
+
bodyText = ` ${bodyStr}`;
|
159
|
+
}
|
160
|
+
} catch (e) {
|
161
|
+
// 忽略序列化错误
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
// 合并完整文本(无需 toLowerCase)
|
166
|
+
const fullText = checkText + bodyText;
|
167
|
+
|
168
|
+
// 4. 高效匹配:两次正则 test 替代 N 次循环(核心优化)
|
169
|
+
let foundMatch = false;
|
170
|
+
// 先检查字符串关键词合并正则(更快,因为无复杂正则逻辑)
|
171
|
+
if (combinedStrRegex.test(fullText)) {
|
172
|
+
foundMatch = true;
|
173
|
+
}
|
174
|
+
// 再检查正则关键词合并正则(仅当字符串匹配未命中时)
|
175
|
+
else if (combinedRegRegex.test(fullText)) {
|
176
|
+
foundMatch = true;
|
177
|
+
}
|
178
|
+
|
179
|
+
if (foundMatch) {
|
180
|
+
console.error("[安全拦截] 疑似恶意请求:", {
|
181
|
+
url: req.url,
|
182
|
+
ip: getIp(req),
|
183
|
+
userAgent: req.get("User-Agent") || "",
|
184
|
+
});
|
185
|
+
return res.status(403).send("请求被安全策略拦截");
|
186
|
+
}
|
187
|
+
|
188
|
+
next();
|
189
|
+
} catch (error) {
|
190
|
+
console.error("[安全中间件异常]", error);
|
191
|
+
res.status(500).send("服务器内部错误");
|
192
|
+
}
|
193
|
+
};
|
194
|
+
|
195
|
+
export const waf = (app) => {
|
196
|
+
app.use(safe);
|
197
|
+
};
|
package/package.json
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
/**
|
2
|
+
* 将数组转换为键值对对象
|
3
|
+
* @param {Array} arr - 包含键值对的数组
|
4
|
+
* @param {string} keyField - 作为键的字段名
|
5
|
+
* @param {string} valueField - 作为值的字段名
|
6
|
+
* @returns {Object} 转换后的对象
|
7
|
+
*/
|
8
|
+
export const arrToObj = (arr, keyField = 'config_key', valueField = 'config_value') => {
|
9
|
+
// 输入验证
|
10
|
+
if (!Array.isArray(arr)) {
|
11
|
+
throw new Error('arrToObj 期望接收数组作为第一个参数');
|
12
|
+
}
|
13
|
+
|
14
|
+
return arr.reduce((result, item) => {
|
15
|
+
if (item && typeof item === 'object') {
|
16
|
+
const key = item[keyField];
|
17
|
+
const value = item[valueField];
|
18
|
+
if (key !== undefined && value !== undefined) {
|
19
|
+
result[key] = value;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
return result;
|
23
|
+
}, {});
|
24
|
+
};
|
package/utils/db.js
CHANGED
@@ -1,77 +1,75 @@
|
|
1
|
-
import knex from
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
} ) {
|
15
|
-
|
1
|
+
import knex from "knex";
|
2
|
+
export const db = function ({
|
3
|
+
client = "mysql2",
|
4
|
+
host = "127.0.0.1",
|
5
|
+
user = "root",
|
6
|
+
password = "123456",
|
7
|
+
database = "test",
|
8
|
+
port = 3306,
|
9
|
+
debug = true,
|
10
|
+
charset = "utf8mb4",
|
11
|
+
min = 0,
|
12
|
+
max = 2,
|
13
|
+
}) {
|
16
14
|
let config = {
|
17
|
-
client
|
15
|
+
client,
|
18
16
|
connection: {
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
host,
|
18
|
+
port,
|
19
|
+
user,
|
20
|
+
password,
|
21
|
+
database,
|
22
|
+
charset,
|
23
|
+
},
|
25
24
|
debug,
|
26
|
-
pool: {
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
pool: {
|
26
|
+
//默认为{min: 2, max: 10},连接池配置
|
27
|
+
min,
|
28
|
+
max,
|
29
|
+
},
|
30
30
|
log: {
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
},
|
52
|
-
|
31
|
+
warn(message) {
|
32
|
+
console.error("[knex warn]", message);
|
33
|
+
},
|
34
|
+
error(message) {
|
35
|
+
console.error("[knex error]", message);
|
36
|
+
},
|
37
|
+
debug(message) {
|
38
|
+
console.log("[knex debug]", message);
|
39
|
+
},
|
40
|
+
deprecate(message) {
|
41
|
+
console.warn("[knex deprecate]", message);
|
42
|
+
},
|
43
|
+
trace(message) {
|
44
|
+
console.log("[knex trace]", message);
|
45
|
+
},
|
46
|
+
log(message) {
|
47
|
+
console.log("[knex log]", message);
|
48
|
+
},
|
49
|
+
info(message) {
|
50
|
+
console.log("[knex info]", message);
|
53
51
|
},
|
54
|
-
}
|
55
|
-
|
56
|
-
|
52
|
+
},
|
53
|
+
};
|
54
|
+
return knex(config);
|
55
|
+
};
|
57
56
|
|
58
57
|
const errCode = {
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
}
|
58
|
+
ECONNREFUSED: "数据库连接被拒绝,请检查数据库服务是否正常运行。",
|
59
|
+
ER_ACCESS_DENIED_ERROR: "无权限访问,账号或密码错误。",
|
60
|
+
ER_ROW_IS_REFERENCED_2: "无法删除或更新记录,存在关联数据。",
|
61
|
+
ER_BAD_FIELD_ERROR: "SQL语句中包含无效字段,请检查查询条件或列名。",
|
62
|
+
ER_DUP_ENTRY: "插入失败:数据重复,违反唯一性约束。",
|
63
|
+
ER_NO_SUCH_TABLE: "操作失败:目标表不存在。",
|
64
|
+
ETIMEOUT: "数据库操作超时,请稍后再试。",
|
65
|
+
};
|
67
66
|
const getDefaultErrorMessage = (error) => {
|
68
|
-
if (error.message.includes(
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
return '数据库权限不足,请检查配置。';
|
67
|
+
if (error.message.includes("syntax") || error.message.includes("SQL")) {
|
68
|
+
return "数据库语法错误,请检查您的查询语句。";
|
69
|
+
} else if (error.message.includes("Connection closed")) {
|
70
|
+
return "数据库连接已关闭,请重试。";
|
71
|
+
} else if (error.message.includes("permission")) {
|
72
|
+
return "数据库权限不足,请检查配置。";
|
75
73
|
}
|
76
|
-
return
|
77
|
-
}
|
74
|
+
return "数据库发生未知错误,请稍后重试。";
|
75
|
+
};
|
package/utils/loader.js
CHANGED
@@ -1,32 +1,32 @@
|
|
1
|
-
import fs from
|
2
|
-
import path from
|
3
|
-
import { bindClass } from
|
1
|
+
import fs from "fs";
|
2
|
+
import path from "path";
|
3
|
+
import { bindClass } from "./bind.js";
|
4
4
|
|
5
5
|
/**
|
6
|
-
*
|
6
|
+
*
|
7
7
|
* @param {*} module 模块目录
|
8
|
-
* @returns Array
|
8
|
+
* @returns Array
|
9
9
|
* @description 将web模块放到最后加载
|
10
10
|
*/
|
11
|
-
export const loaderSort = (modules=[])=>{
|
12
|
-
const index = modules.indexOf(
|
11
|
+
export const loaderSort = (modules = []) => {
|
12
|
+
const index = modules.indexOf("web");
|
13
13
|
if (index !== -1) {
|
14
|
-
|
15
|
-
|
14
|
+
const web = modules.splice(index, 1);
|
15
|
+
modules.push(web[0]);
|
16
16
|
}
|
17
17
|
return modules;
|
18
|
-
}
|
18
|
+
};
|
19
19
|
|
20
|
-
export const getPackage = async function(){
|
21
|
-
let pkg = await importFile(
|
20
|
+
export const getPackage = async function () {
|
21
|
+
let pkg = await importFile("package.json");
|
22
22
|
return pkg;
|
23
|
-
}
|
23
|
+
};
|
24
24
|
|
25
|
-
export const loadConfig = async function(){
|
26
|
-
let config = await importFile(
|
25
|
+
export const loadConfig = async function () {
|
26
|
+
let config = await importFile("config/index.js");
|
27
|
+
console.log("config", config);
|
27
28
|
return config;
|
28
|
-
}
|
29
|
-
|
29
|
+
};
|
30
30
|
|
31
31
|
/**
|
32
32
|
* 加载指定模块名下的所有控制器文件
|
@@ -36,22 +36,24 @@ export const loadConfig = async function(){
|
|
36
36
|
export const loadController = async function (moduleName) {
|
37
37
|
const controller = {};
|
38
38
|
|
39
|
-
const dir = path.join(MODULES_PATH, moduleName,
|
39
|
+
const dir = path.join(MODULES_PATH, moduleName, "controller");
|
40
40
|
|
41
41
|
if (!fs.existsSync(dir)) {
|
42
42
|
console.warn(`模块路径不存在,跳过加载控制器: ${dir}`);
|
43
43
|
return controller;
|
44
44
|
}
|
45
45
|
|
46
|
-
const files = fs.readdirSync(dir).filter(file => file.endsWith(
|
46
|
+
const files = fs.readdirSync(dir).filter((file) => file.endsWith(".js"));
|
47
47
|
|
48
48
|
for (const file of files) {
|
49
49
|
const filePath = path.join(dir, file);
|
50
|
-
const name = file.replace(/\.js$/i,
|
50
|
+
const name = file.replace(/\.js$/i, ""); // 安全处理 .js 后缀
|
51
51
|
|
52
52
|
try {
|
53
53
|
const module = await importFile(filePath);
|
54
|
-
|
54
|
+
let obj = module.default || module;
|
55
|
+
|
56
|
+
controller[name] = obj;
|
55
57
|
} catch (e) {
|
56
58
|
console.error(`加载控制器失败: ${filePath}`, e);
|
57
59
|
// 可选:抛出错误或继续加载其他文件
|
@@ -61,6 +63,3 @@ export const loadController = async function (moduleName) {
|
|
61
63
|
|
62
64
|
return controller;
|
63
65
|
};
|
64
|
-
|
65
|
-
|
66
|
-
|