🔒 修复分享列表接口的目录遍历漏洞

- 在 /api/share/:code/list 增加路径规范化与范围校验
- 使用 path.posix.normalize 处理 subPath,阻断 ../ 遍历攻击
- 调用 isPathWithinShare 验证请求路径在分享范围内
- 统一使用安全的 requestedPath 进行存储访问和直链生成

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-25 11:10:48 +08:00
parent 3045c354f4
commit 2fb2321750

View File

@@ -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);
}