From 4879d4891f9fed4dbbb4a5582e059343253177ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=96=BB=E5=8B=87=E7=A5=A5?= <237899745@qq.com> Date: Fri, 14 Nov 2025 00:15:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=86=E4=BA=AB?= =?UTF-8?q?=E8=BF=87=E6=9C=9F=E6=97=B6=E9=97=B4=E5=92=8C=E9=98=B2=E7=88=86?= =?UTF-8?q?=E7=A0=B4=E4=BF=9D=E6=8A=A4=E8=A6=86=E7=9B=96=E4=B8=8D=E5=85=A8?= =?UTF-8?q?=E7=9A=84=E5=AE=89=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题1 - 分享过期时间未强制校验: - 在ShareDB.findByCode()中添加过期时间检查 - SQL条件: AND (s.expires_at IS NULL OR s.expires_at > datetime('now')) - 现在过期的分享链接将返回404,无法访问 问题2 - 分享密码防爆破保护覆盖不全: - 给/api/share/:code/list添加shareRateLimitMiddleware - 给/api/share/:code/download-file添加shareRateLimitMiddleware - 在两个接口的密码验证失败时调用recordFailure - 在两个接口的密码验证成功时调用recordSuccess - 防止攻击者绕过/verify接口直接暴力破解 影响: - 分享过期后将无法访问(安全性提升) - 所有分享密码验证接口都受到限流保护(10次/10分钟) - 修复了可绕过防爆破保护的安全漏洞 --- backend/database.js | 1 + backend/server.js | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/backend/database.js b/backend/database.js index f711ff4..c77a442 100644 --- a/backend/database.js +++ b/backend/database.js @@ -337,6 +337,7 @@ const ShareDB = { FROM shares s JOIN users u ON s.user_id = u.id WHERE s.share_code = ? + AND (s.expires_at IS NULL OR s.expires_at > datetime('now')) `).get(shareCode); }, diff --git a/backend/server.js b/backend/server.js index b3c5b72..7cac112 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1735,7 +1735,7 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) = }); // 获取分享的文件列表(支持本地存储和SFTP) -app.post('/api/share/:code/list', async (req, res) => { +app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => { const { code } = req.params; const { password, path: subPath } = req.body; @@ -1753,12 +1753,21 @@ app.post('/api/share/:code/list', async (req, res) => { // 验证密码 if (share.share_password && !ShareDB.verifyPassword(password, share.share_password)) { + // 记录密码错误 + if (req.shareRateLimitKey) { + shareLimiter.recordFailure(req.shareRateLimitKey); + } return res.status(401).json({ success: false, message: '密码错误' }); } + // 清除失败记录(密码验证成功或无密码) + if (req.shareRateLimitKey && share.share_password) { + shareLimiter.recordSuccess(req.shareRateLimitKey); + } + // 获取分享者的用户信息 const shareOwner = UserDB.findById(share.user_id); if (!shareOwner) { @@ -1912,7 +1921,7 @@ app.post('/api/share/:code/download', (req, res) => { }); // 分享文件下载(支持本地存储和SFTP,公开API,需要分享码和密码验证) -app.get('/api/share/:code/download-file', async (req, res) => { +app.get('/api/share/:code/download-file', shareRateLimitMiddleware, async (req, res) => { const { code } = req.params; const { path: filePath, password } = req.query; let storage; @@ -1936,11 +1945,20 @@ app.get('/api/share/:code/download-file', async (req, res) => { // 验证密码(如果需要) if (share.share_password) { + // 记录密码错误 + if (req.shareRateLimitKey) { + shareLimiter.recordFailure(req.shareRateLimitKey); + } if (!password || !ShareDB.verifyPassword(password, share.share_password)) { return res.status(401).json({ success: false, message: '密码错误或未提供密码' }); + // 清除失败记录(密码验证成功) + if (req.shareRateLimitKey && share.share_password) { + shareLimiter.recordSuccess(req.shareRateLimitKey); + } + } }