一、风险与目标风险:任意路径读取、MIME嗅探、内联脚本执行、文件名注入与目录遍历。目标:路径规范化与扩展名白名单、精确`Content-Type`与`nosniff`、安全`Content-Disposition`与UTF-8文件名。

二、路径与类型校验import path from 'path'

function normalizeSafe(root: string, rel: string): string | null {

const p = path.normalize('/' + rel).replace(/^\/+/, '')

const full = path.join(root, p)

if (!full.startsWith(path.join(root, path.sep))) return null

return full

}

const mimeMap: Record<string, string> = {

'.pdf': 'application/pdf',

'.txt': 'text/plain; charset=utf-8',

'.csv': 'text/csv; charset=utf-8',

'.zip': 'application/zip'

}

function contentTypeOf(ext: string): string | null { return mimeMap[ext.toLowerCase()] || null }

三、文件名规范化function safeFileName(name: string): string {

const s = name.replace(/[\r\n\u0000]/g, '').replace(/[/\\]/g, '_')

return s.slice(0, 200)

}

function disposition(name: string): string {

const n = safeFileName(name)

const encoded = encodeURIComponent(n)

return `attachment; filename="${n}"; filename*=UTF-8''${encoded}`

}

四、下载响应示例type Res = { setHeader: (k: string, v: string) => void; status: (n: number) => Res; end: (b?: string) => void }

function sendDownload(res: Res, full: string, downloadName: string, fs: any) {

const ext = path.extname(full)

const ct = contentTypeOf(ext)

if (!ct) return res.status(415).end('unsupported_media_type')

res.setHeader('Content-Type', ct)

res.setHeader('X-Content-Type-Options', 'nosniff')

res.setHeader('Content-Disposition', disposition(downloadName))

res.setHeader('Cache-Control', 'private, max-age=0, no-store')

const buf = fs.readFileSync(full)

res.end(buf.toString('binary'))

}

五、整合路由与验收type Req = { query: Record<string, string | undefined> }

function handleDownload(req: Req, res: Res, fs: any) {

const root = '/var/app/files'

const rel = req.query['file'] || ''

const name = req.query['name'] || 'download'

const full = normalizeSafe(root, rel)

if (!full) return res.status(400).end('invalid_path')

sendDownload(res, full, name, fs)

}

路径规范化通过且限制在根目录;扩展名白名单有效;`Content-Type`与`nosniff`设置正确。`Content-Disposition`为`attachment`且包含UTF-8文件名;缓存策略为私有且不存储。对不支持类型返回`415`;异常路径返回`400`;审计包含下载名与相对路径。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部