修复SSRF/Host Header注入与Null报错的问题

This commit is contained in:
Troray
2026-03-10 20:58:56 +08:00
parent b4320b7028
commit 802bf80bef
3 changed files with 134 additions and 61 deletions

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getConfig } from '@/lib/config';
import { isValidUrlForProxy } from '@/lib/utils';
export const runtime = 'nodejs';
@@ -34,14 +35,38 @@ export async function GET(request: NextRequest) {
);
}
const DIRECT_PLAY_SOURCE = 'directplay';
// 安全校验:防 SSRF只允许合法的公网 URL
if (source === DIRECT_PLAY_SOURCE && !isValidUrlForProxy(m3u8Url)) {
return NextResponse.json(
{ error: 'Proxy request to local or invalid network is forbidden' },
{ status: 403 }
);
}
// 获取当前请求的 origin
// 优先级SITE_BASE 环境变量 > 从请求头构建
let origin = process.env.SITE_BASE;
if (!origin) {
// 从请求头中获取 Host 和协议
const host = request.headers.get('host') || request.headers.get('x-forwarded-host');
let host = request.headers.get('host') || request.headers.get('x-forwarded-host');
// 安全校验:防 Host 头注入漏洞 (要求仅包含合法域名或 IP 格式字符)
if (host && !/^[a-zA-Z0-9.-]+(:\d+)?$/.test(host)) {
host = null;
}
// Fallback如果以上 Header 无效或未提供,回退到 request.url 获取
if (!host) {
try {
host = new URL(request.url).host;
} catch {
return NextResponse.json({ error: 'Invalid Request Host' }, { status: 400 });
}
}
const proto = request.headers.get('x-forwarded-proto') ||
(host?.includes('localhost') || host?.includes('127.0.0.1') ? 'http' : 'https');
(host.includes('localhost') || host.includes('127.0.0.1') ? 'http' : 'https');
origin = `${proto}://${host}`;
}

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import { getConfig } from "@/lib/config";
import { isValidUrlForProxy } from "@/lib/utils";
export const runtime = 'nodejs';
@@ -19,8 +20,11 @@ export async function GET(request: Request) {
return NextResponse.json({ error: 'Missing source' }, { status: 400 });
}
// 定义直链播放模式常量
const DIRECT_PLAY_SOURCE = 'directplay';
// 直链播放模式:跳过源站配置检查,直接代理
if (source !== 'directplay') {
if (source !== DIRECT_PLAY_SOURCE) {
// 检查该视频源是否启用了代理模式
const config = await getConfig();
const videoSource = config.SourceConfig?.find((s: any) => s.key === source);
@@ -39,6 +43,12 @@ export async function GET(request: Request) {
try {
const decodedUrl = decodeURIComponent(url);
// 安全校验:防 SSRF 拦截请求内网或非法 URL
if (source === DIRECT_PLAY_SOURCE && !isValidUrlForProxy(decodedUrl)) {
return NextResponse.json({ error: 'Proxy request to local or invalid network is forbidden' }, { status: 403 });
}
response = await fetch(decodedUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',