diff --git a/src/app.js b/src/app.js index 77e60216..db15df2e 100644 --- a/src/app.js +++ b/src/app.js @@ -68,6 +68,10 @@ class Application { logger.info('🔄 Initializing admin credentials...') await this.initializeAdmin() + // 🔒 安全启动:清理无效/伪造的管理员会话 + logger.info('🔒 Cleaning up invalid admin sessions...') + await this.cleanupInvalidSessions() + // 💰 初始化费用数据 logger.info('💰 Checking cost data initialization...') const costInitService = require('./services/costInitService') @@ -426,6 +430,54 @@ class Application { } } + // 🔒 清理无效/伪造的管理员会话(安全启动检查) + async cleanupInvalidSessions() { + try { + const client = redis.getClient() + + // 获取所有 session:* 键 + const sessionKeys = await client.keys('session:*') + + let validCount = 0 + let invalidCount = 0 + + for (const key of sessionKeys) { + // 跳过 admin_credentials(系统凭据) + if (key === 'session:admin_credentials') { + continue + } + + const sessionData = await client.hgetall(key) + + // 检查会话完整性:必须有 username 和 loginTime + const hasUsername = !!sessionData.username + const hasLoginTime = !!sessionData.loginTime + + if (!hasUsername || !hasLoginTime) { + // 无效会话 - 可能是漏洞利用创建的伪造会话 + invalidCount++ + logger.security( + `🔒 Removing invalid session: ${key} (username: ${hasUsername}, loginTime: ${hasLoginTime})` + ) + await client.del(key) + } else { + validCount++ + } + } + + if (invalidCount > 0) { + logger.security(`🔒 Startup security check: Removed ${invalidCount} invalid sessions`) + } + + logger.success( + `✅ Session cleanup completed: ${validCount} valid, ${invalidCount} invalid removed` + ) + } catch (error) { + // 清理失败不应阻止服务启动 + logger.error('❌ Failed to cleanup invalid sessions:', error.message) + } + } + // 🔍 Redis健康检查 async checkRedisHealth() { try { diff --git a/src/middleware/auth.js b/src/middleware/auth.js index a3d2311a..44e3cb37 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -1389,6 +1389,18 @@ const authenticateAdmin = async (req, res, next) => { }) } + // 🔒 安全修复:验证会话必须字段(防止伪造会话绕过认证) + if (!adminSession.username || !adminSession.loginTime) { + logger.security( + `🔒 Corrupted admin session from ${req.ip || 'unknown'} - missing required fields (username: ${!!adminSession.username}, loginTime: ${!!adminSession.loginTime})` + ) + await redis.deleteSession(token) // 清理无效/伪造的会话 + return res.status(401).json({ + error: 'Invalid session', + message: 'Session data corrupted or incomplete' + }) + } + // 检查会话活跃性(可选:检查最后活动时间) const now = new Date() const lastActivity = new Date(adminSession.lastActivity || adminSession.loginTime) diff --git a/src/routes/web.js b/src/routes/web.js index 8bbdd435..ed3bfa57 100644 --- a/src/routes/web.js +++ b/src/routes/web.js @@ -164,13 +164,27 @@ router.post('/auth/change-password', async (req, res) => { // 获取当前会话 const sessionData = await redis.getSession(token) - if (!sessionData) { + + // 🔒 安全修复:检查空对象 + if (!sessionData || Object.keys(sessionData).length === 0) { return res.status(401).json({ error: 'Invalid token', message: 'Session expired or invalid' }) } + // 🔒 安全修复:验证会话完整性 + if (!sessionData.username || !sessionData.loginTime) { + logger.security( + `🔒 Invalid session structure in /auth/change-password from ${req.ip || 'unknown'}` + ) + await redis.deleteSession(token) + return res.status(401).json({ + error: 'Invalid session', + message: 'Session data corrupted or incomplete' + }) + } + // 获取当前管理员信息 const adminData = await redis.getSession('admin_credentials') if (!adminData) { @@ -269,13 +283,25 @@ router.get('/auth/user', async (req, res) => { // 获取当前会话 const sessionData = await redis.getSession(token) - if (!sessionData) { + + // 🔒 安全修复:检查空对象 + if (!sessionData || Object.keys(sessionData).length === 0) { return res.status(401).json({ error: 'Invalid token', message: 'Session expired or invalid' }) } + // 🔒 安全修复:验证会话完整性 + if (!sessionData.username || !sessionData.loginTime) { + logger.security(`🔒 Invalid session structure in /auth/user from ${req.ip || 'unknown'}`) + await redis.deleteSession(token) + return res.status(401).json({ + error: 'Invalid session', + message: 'Session data corrupted or incomplete' + }) + } + // 获取管理员信息 const adminData = await redis.getSession('admin_credentials') if (!adminData) { @@ -316,13 +342,24 @@ router.post('/auth/refresh', async (req, res) => { const sessionData = await redis.getSession(token) - if (!sessionData) { + // 🔒 安全修复:检查空对象(hgetall 对不存在的 key 返回 {}) + if (!sessionData || Object.keys(sessionData).length === 0) { return res.status(401).json({ error: 'Invalid token', message: 'Session expired or invalid' }) } + // 🔒 安全修复:验证会话完整性(必须有 username 和 loginTime) + if (!sessionData.username || !sessionData.loginTime) { + logger.security(`🔒 Invalid session structure detected from ${req.ip || 'unknown'}`) + await redis.deleteSession(token) // 清理无效/伪造的会话 + return res.status(401).json({ + error: 'Invalid session', + message: 'Session data corrupted or incomplete' + }) + } + // 更新最后活动时间 sessionData.lastActivity = new Date().toISOString() await redis.setSession(token, sessionData, config.security.adminSessionTimeout)