Skip to content

Commit a3f16b9

Browse files
longsizhuogithub-actions[bot]
authored andcommitted
fix(upload): contentType 加严格 MIME 正则闸,拒 CR/LF 及其他控制字符
extractPrimaryMime 只切分号 + trim + 小写,不清洗控制字符。像 'image/jpeg\r\nContent-Type: image/svg+xml' 这种值, 走完 extractPrimaryMime 得到 'image/jpeg\r\ncontent-type: image/svg+xml',startsWith('image/') 过、 startsWith('image/svg') 绕(中间有 \r\n,前缀是 image/jpeg\r\n),然后被塞进 PutObjectCommand.ContentType。 下游(AWS SDK / R2 / 浏览器)per RFC 7230 一般会拒 header 值里的 CRLF,但入口先收口更便宜也更正确。 改法:新增 MIME_PATTERN = /^[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*$/, extractPrimaryMime 返回后立刻 .test(),不匹配直接 400 { error: 'contentType 格式非法' }, 放在 image/* 和 SVG 黑名单之前当最外层 gate。合法 image/jpeg / image/svg+xml / image/webp 等都能过(SVG 交给后续黑名单拦),CR/LF/冒号/空格注入一律挡死。
1 parent 8044e39 commit a3f16b9

1 file changed

Lines changed: 16 additions & 0 deletions

File tree

app/api/upload/route.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ function extractPrimaryMime(contentType: string): string {
5454
return contentType.split(";")[0]!.trim().toLowerCase();
5555
}
5656

57+
/**
58+
* 严格 MIME 形状:`type/subtype`,两侧只允许 [a-z0-9.+-],首字符必须是 [a-z0-9]。
59+
*
60+
* 用途:拒绝 CR/LF 及其他控制字符,防止注入被 SDK/R2/浏览器当成多个 header。
61+
* 虽然下游(AWS SDK / R2 / 浏览器)per RFC 7230 也会拒 header 值里的 CR/LF,
62+
* 但入口先收口更便宜也更正确,别依赖下游任何一层。
63+
*/
64+
const MIME_PATTERN = /^[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*$/;
65+
5766
/**
5867
* @description POST /api/upload - 生成 R2 预签名 URL,用于客户端直接上传图片
5968
* @param request - NextRequest 对象,请求体包含以下字段:
@@ -136,6 +145,13 @@ export async function POST(request: NextRequest) {
136145
// 构成存储型 XSS 向量。我们宁可让用户转成 PNG/JPG 也不放行。
137146
// 注意:所有判断都走 primaryMime(分号前的主 MIME),绕不过 `"image/jpeg; image/svg+xml"` 这种夹带。
138147
const primaryMime = extractPrimaryMime(contentType);
148+
// 拒绝 CR/LF 及其他控制字符,防止注入被 SDK/R2/浏览器当成多个 header
149+
if (!MIME_PATTERN.test(primaryMime)) {
150+
return NextResponse.json(
151+
{ error: "contentType 格式非法" },
152+
{ status: 400 },
153+
);
154+
}
139155
if (!primaryMime.startsWith("image/")) {
140156
return NextResponse.json(
141157
{ error: "仅支持图片类型文件" },

0 commit comments

Comments
 (0)