fix(security): 修复多个严重安全漏洞和运行时崩溃

- devices.js: 修复 hasRoomField 重复 const 声明导致模块加载失败
- devices.js: 修复 batch-status 路由 affectedCount 未定义导致崩溃
- devices.js: 修复 sequelize.literal SQL 注入,转义 LIKE 通配符和字段名
- server.js: 修复 initializeApp() 未 await 导致服务器在初始化前接收请求
- backup.js: 添加 resolveBackupPath() 防止路径遍历攻击(6处)
- inventory.js: 用 bulkCreate 替代原始 SQL 拼接,消除 SQL 注入
- systemSettings.js: 添加全局 authMiddleware,修复敏感操作无认证
- dangerousOperations.js: 添加全局认证,修复日志清理功能完全不可用
- background.js: 添加文件名安全检查、扩展名白名单、路径校验
This commit is contained in:
zhang96110
2026-04-01 06:02:55 +00:00
parent fa26964cbb
commit 6eda3ae1f2
7 changed files with 143 additions and 61 deletions
+22 -9
View File
@@ -532,7 +532,12 @@ router.get('/', validateQuery(queryDeviceSchema), async (req, res) => {
console.log('数据库类型:', dbDialect);
// 转义关键词中的特殊字符,防止SQL注入
const escapedKeyword = keyword.replace(/'/g, "''");
// 转义单引号(SQL注入)和 LIKE 通配符(%、_)
const escapedKeyword = keyword
.replace(/\\/g, '\\\\')
.replace(/'/g, "''")
.replace(/%/g, '\\%')
.replace(/_/g, '\\_');
// 基础字段搜索条件(只包含文本类型字段)
const searchConditions = [
@@ -562,15 +567,20 @@ router.get('/', validateQuery(queryDeviceSchema), async (req, res) => {
if (customFields.length > 0) {
// 使用原始SQL查询JSON字段
const jsonConditions = customFields.map(field => {
const fieldName = field.fieldName;
// 转义 fieldName 中的特殊字符,防止通过字段名注入
const safeFieldName = field.fieldName
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/'/g, "''");
// 使用 sequelize.literal 构建原始SQL条件
if (dbDialect === 'mysql') {
return sequelize.literal(
`JSON_EXTRACT(customFields, '$."${fieldName}"') LIKE '%${escapedKeyword}%'`
`JSON_EXTRACT(customFields, '$."${safeFieldName}"') LIKE '%${escapedKeyword}%' ESCAPE '\\\\'`
);
} else {
return sequelize.literal(
`json_extract(customFields, '$.${fieldName}') LIKE '%${escapedKeyword}%'`
`json_extract(customFields, '$.${safeFieldName}') LIKE '%${escapedKeyword}%' ESCAPE '\\'`
);
}
});
@@ -1485,8 +1495,10 @@ router.post('/import', async (req, res) => {
});
for (const [rackId, powerToAdd] of rackPowerMap) {
// 确保功率值为合法数字,防止SQL注入
const safePower = Number(powerToAdd) || 0;
await Rack.update(
{ currentPower: sequelize.literal(`currentPower + ${powerToAdd}`) },
{ currentPower: sequelize.literal(`currentPower + ${safePower}`) },
{ where: { rackId }, transaction: t }
);
}
@@ -1809,8 +1821,9 @@ router.put('/batch-move', async (req, res) => {
for (const [rackId, powerChange] of sourceRackPowerChanges) {
if (rackId !== targetRackId) {
const safePower = Number(powerChange) || 0;
await Rack.update(
{ currentPower: sequelize.literal(`currentPower + ${powerChange}`) },
{ currentPower: sequelize.literal(`currentPower + ${safePower}`) },
{ where: { rackId } }
);
}
@@ -1821,8 +1834,9 @@ router.put('/batch-move', async (req, res) => {
}
if (targetRackPowerChange.change !== 0) {
const safeTargetPower = Number(targetRackPowerChange.change) || 0;
await Rack.update(
{ currentPower: sequelize.literal(`currentPower + ${targetRackPowerChange.change}`) },
{ currentPower: sequelize.literal(`currentPower + ${safeTargetPower}`) },
{ where: { rackId: targetRackId } }
);
}
@@ -1972,8 +1986,7 @@ router.get('/enhanced-export', async (req, res) => {
const headerSet = new Set();
// 添加机房字段(如果存在)
const hasRoomField = allFields.some(f => f.fieldName === 'roomName');
if (!hasRoomField) {
if (!allFields.some(f => f.fieldName === 'roomName')) {
headerSet.add('所在机房');
}