diff --git a/backend/server.js b/backend/server.js index dc321d2..c378ebf 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2902,6 +2902,20 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => }); } + // 构造安全的请求路径,防止越权遍历 + const baseSharePath = (share.share_path || '/').replace(/\\/g, '/'); + const requestedPath = subPath + ? path.posix.normalize(`${baseSharePath}/${subPath}`) + : baseSharePath; + + // 校验请求路径是否在分享范围内 + if (!isPathWithinShare(requestedPath, share)) { + return res.status(403).json({ + success: false, + message: '无权访问该路径' + }); + } + // 使用统一存储接口,根据分享的storage_type选择存储后端 const { StorageInterface } = require('./storage'); const storageType = share.storage_type || 'sftp'; @@ -2957,14 +2971,13 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => } // 如果是目录分享(分享所有文件) else { - const fullPath = subPath ? `${share.share_path}/${subPath}`.replace('//', '/') : share.share_path; - const list = await storage.list(fullPath); + const list = await storage.list(requestedPath); formattedList = list.map(item => { // 构建完整的文件路径用于下载 let httpDownloadUrl = null; if (storageType === 'sftp' && sanitizedShareHttpBase && item.type !== 'd') { - const normalizedPath = fullPath.startsWith('/') ? fullPath : `/${fullPath}`; + const normalizedPath = requestedPath.startsWith('/') ? requestedPath : `/${requestedPath}`; const filePath = normalizedPath === '/' ? `/${item.name}` : `${normalizedPath}/${item.name}`; httpDownloadUrl = buildHttpDownloadUrl(sanitizedShareHttpBase, filePath); }