diff --git a/controller/video_proxy.go b/controller/video_proxy.go index 5532802e..520d313a 100644 --- a/controller/video_proxy.go +++ b/controller/video_proxy.go @@ -10,10 +10,12 @@ import ( "strings" "time" + "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/logger" "github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/service" + "github.com/QuantumNous/new-api/setting/system_setting" "github.com/gin-gonic/gin" ) @@ -127,6 +129,13 @@ func VideoProxy(c *gin.Context) { return } + fetchSetting := system_setting.GetFetchSetting() + if err := common.ValidateURLWithFetchSetting(videoURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainFilterMode, fetchSetting.IpFilterMode, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts, fetchSetting.ApplyIPFilterForDomain); err != nil { + logger.LogError(c.Request.Context(), fmt.Sprintf("Video URL blocked for task %s: %v", taskID, err)) + videoProxyError(c, http.StatusForbidden, "server_error", fmt.Sprintf("request blocked: %v", err)) + return + } + req.URL, err = url.Parse(videoURL) if err != nil { logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to parse URL %s: %s", videoURL, err.Error())) diff --git a/relay/mjproxy_handler.go b/relay/mjproxy_handler.go index c2aac969..ee48ca64 100644 --- a/relay/mjproxy_handler.go +++ b/relay/mjproxy_handler.go @@ -49,6 +49,13 @@ func RelayMidjourneyImage(c *gin.Context) { if httpClient == nil { httpClient = service.GetHttpClient() } + fetchSetting := system_setting.GetFetchSetting() + if err := common.ValidateURLWithFetchSetting(midjourneyTask.ImageUrl, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainFilterMode, fetchSetting.IpFilterMode, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts, fetchSetting.ApplyIPFilterForDomain); err != nil { + c.JSON(http.StatusForbidden, gin.H{ + "error": fmt.Sprintf("request blocked: %v", err), + }) + return + } resp, err := httpClient.Get(midjourneyTask.ImageUrl) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ diff --git a/router/api-router.go b/router/api-router.go index 31ab771a..1ef2f266 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -226,7 +226,7 @@ func SetApiRouter(router *gin.Engine) { channelRoute.POST("/batch", controller.DeleteChannelBatch) channelRoute.POST("/fix", controller.FixChannelsAbilities) channelRoute.GET("/fetch_models/:id", controller.FetchUpstreamModels) - channelRoute.POST("/fetch_models", controller.FetchModels) + channelRoute.POST("/fetch_models", middleware.RootAuth(), controller.FetchModels) channelRoute.POST("/codex/oauth/start", controller.StartCodexOAuth) channelRoute.POST("/codex/oauth/complete", controller.CompleteCodexOAuth) channelRoute.POST("/:id/codex/oauth/start", controller.StartCodexOAuthForChannel) diff --git a/setting/system_setting/fetch_setting.go b/setting/system_setting/fetch_setting.go index 07869619..c71be03c 100644 --- a/setting/system_setting/fetch_setting.go +++ b/setting/system_setting/fetch_setting.go @@ -21,7 +21,7 @@ var defaultFetchSetting = FetchSetting{ DomainList: []string{}, IpList: []string{}, AllowedPorts: []string{"80", "443", "8080", "8443"}, - ApplyIPFilterForDomain: false, + ApplyIPFilterForDomain: true, } func init() { diff --git a/web/src/components/settings/SystemSetting.jsx b/web/src/components/settings/SystemSetting.jsx index 8334c01d..91ef3645 100644 --- a/web/src/components/settings/SystemSetting.jsx +++ b/web/src/components/settings/SystemSetting.jsx @@ -108,7 +108,7 @@ const SystemSetting = () => { 'fetch_setting.domain_list': [], 'fetch_setting.ip_list': [], 'fetch_setting.allowed_ports': [], - 'fetch_setting.apply_ip_filter_for_domain': false, + 'fetch_setting.apply_ip_filter_for_domain': true, }); const [originInputs, setOriginInputs] = useState({}); @@ -847,7 +847,7 @@ const SystemSetting = () => { } style={{ marginBottom: 8 }} > - {t('对域名启用 IP 过滤(实验性)')} + {t('对域名启用 IP 过滤(推荐开启)')} {t(domainFilterMode ? '域名白名单' : '域名黑名单')} diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index e41eafdc..49f3182f 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -935,7 +935,7 @@ "在此输入系统名称": "Enter the system name here", "在此输入隐私政策内容,支持 Markdown & HTML 代码": "Enter privacy policy content here, supports Markdown & HTML code", "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页": "Enter the home page content here, supports Markdown", - "域名IP过滤详细说明": "⚠️ This is an experimental option. A domain may resolve to multiple IPv4/IPv6 addresses. If enabled, ensure the IP filter list covers these addresses, otherwise access may fail.", + "域名IP过滤详细说明": "Recommended: When enabled, domains are resolved via DNS and the resulting IPs are checked against private address ranges, effectively preventing DNS rebinding attacks that bypass SSRF protection. Note: A domain may resolve to multiple IPv4/IPv6 addresses. If you have configured an IP filter list, ensure it covers these addresses, otherwise access may fail.", "域名白名单": "Domain Whitelist", "域名黑名单": "Domain Blacklist", "基本信息": "Basic Information", @@ -1105,7 +1105,7 @@ "密钥预览": "Key preview", "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "For official channels, the new-api has a built-in address. Unless it is a third-party proxy site or a special Azure access address, there is no need to fill it in", "对免费模型启用预消耗": "Enable pre-consumption for free models", - "对域名启用 IP 过滤(实验性)": "Enable IP filtering for domains (experimental)", + "对域名启用 IP 过滤(推荐开启)": "Enable IP filtering for domains (recommended)", "对外运营模式": "Default mode", "对象清理规则": "Object Pruning Rules", "导入": "Import", diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index 6ff22ab5..68f9736e 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -928,7 +928,7 @@ "在此输入系统名称": "Saisissez le nom du système ici", "在此输入隐私政策内容,支持 Markdown & HTML 代码": "Saisissez le contenu de la politique de confidentialité ici, prend en charge le code Markdown & HTML", "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页": "Saisissez le contenu de la page d'accueil ici, prend en charge Markdown & HTML. Après configuration, les informations d'état de la page d'accueil ne seront plus affichées. Si un lien est saisi, il sera utilisé comme attribut src de l'iframe, ce qui vous permet de définir n'importe quelle page web comme page d'accueil", - "域名IP过滤详细说明": "⚠️ Il s'agit d'une option expérimentale. Un domaine peut se résoudre en plusieurs adresses IPv4/IPv6. Si cette option est activée, assurez-vous que la liste de filtres IP couvre ces adresses, sinon l'accès peut échouer.", + "域名IP过滤详细说明": "Recommandé : lorsqu'il est activé, les domaines sont résolus par DNS et les IP résultantes sont vérifiées par rapport aux plages d'adresses privées, ce qui empêche efficacement les attaques de DNS rebinding qui contournent la protection SSRF. Remarque : un domaine peut se résoudre en plusieurs adresses IPv4/IPv6. Si vous avez configuré une liste de filtres IP, assurez-vous qu'elle couvre ces adresses, sinon l'accès peut échouer.", "域名白名单": "Liste blanche de domaines", "域名黑名单": "Liste noire de domaines", "基本信息": "Informations de base", @@ -1097,7 +1097,7 @@ "密钥预览": "Aperçu de la clé", "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "Pour les canaux officiels, le new-api a une adresse intégrée. Sauf s'il s'agit d'un site proxy tiers ou d'une adresse d'accès Azure spéciale, il n'est pas nécessaire de la remplir", "对免费模型启用预消耗": "Activer la préconsommation pour les modèles gratuits", - "对域名启用 IP 过滤(实验性)": "Activer le filtrage IP pour les domaines (expérimental)", + "对域名启用 IP 过滤(推荐开启)": "Activer le filtrage IP pour les domaines (recommandé)", "对外运营模式": "Mode par défaut", "对象清理规则": "Règles de nettoyage d'objets", "导入": "Importer", diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index b2a59fc4..cefb0c54 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -919,7 +919,7 @@ "在此输入系统名称": "システム名称を入力してください", "在此输入隐私政策内容,支持 Markdown & HTML 代码": "プライバシーポリシーのコンテンツを入力してください。MarkdownとHTMLコードに対応しています", "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页": "ホームのコンテンツを入力してください。MarkdownとHTMLに対応しています。設定後は、ホームのステータス情報が表示されなくなります。リンクを入力した場合は、そのリンクがiframeのsrc属性として使用され、任意のWebページをホームとして設定できます", - "域名IP过滤详细说明": "ドメインIPフィルタリングの詳細説明", + "域名IP过滤详细说明": "推奨:有効にすると、ドメインをDNS解決し、解決されたIPがプライベートアドレスかどうかを確認します。DNSリバインディング攻撃によるSSRF防護の回避を効果的に防止できます。注意:ドメインは複数のIPv4/IPv6アドレスに解決される場合があります。IPフィルタリストを設定している場合は、これらのアドレスをカバーしていることを確認してください。", "域名白名单": "ドメインホワイトリスト", "域名黑名单": "ドメインブラックリスト", "基本信息": "基本情報", @@ -1088,7 +1088,7 @@ "密钥预览": "APIキーのプレビュー", "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "公式チャネルの場合、new-apiにはベースURLが組み込まれているため、サードパーティのプロキシサイトやAzureの専用のエンドポイントでない限り、入力する必要はありません。", "对免费模型启用预消耗": "Enable pre-consumption for free models", - "对域名启用 IP 过滤(实验性)": "ドメインのIPフィルタリングを有効にする(実験的)", + "对域名启用 IP 过滤(推荐开启)": "ドメインのIPフィルタリングを有効にする(推奨)", "对外运营模式": "公開運用モード", "对象清理规则": "オブジェクトプルーニングルール", "导入": "インポート", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index ccf10242..21a26061 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -934,7 +934,7 @@ "在此输入系统名称": "Введите здесь название системы", "在此输入隐私政策内容,支持 Markdown & HTML 代码": "Введите здесь содержимое политики конфиденциальности, поддерживается Markdown & HTML код", "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页": "Введите здесь содержание главной страницы, поддерживается код Markdown и HTML. После настройки информация о состоянии на главной странице больше не будет отображаться. Если введена ссылка, она будет использована как атрибут src для iframe, что позволяет установить любую веб-страницу как главную страницу", - "域名IP过滤详细说明": "⚠️ Эта функция является экспериментальной опцией, доменное имя может быть разрешено в несколько адресов IPv4/IPv6, если включено, убедитесь, что список фильтрации IP покрывает эти адреса, иначе это может привести к сбою доступа.", + "域名IP过滤详细说明": "Рекомендуется: при включении домены разрешаются через DNS, а полученные IP-адреса проверяются на принадлежность к частным диапазонам, что эффективно предотвращает атаки DNS rebinding, обходящие защиту SSRF. Примечание: домен может разрешаться в несколько адресов IPv4/IPv6. Если вы настроили список фильтрации IP, убедитесь, что он покрывает эти адреса, иначе доступ может быть нарушен.", "域名白名单": "Белый список доменов", "域名黑名单": "Чёрный список доменов", "基本信息": "Основная информация", @@ -1103,7 +1103,7 @@ "密钥预览": "Предпросмотр ключа", "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "Для официальных каналов new-api уже имеет встроенные адреса, если это не сторонние прокси-сайты или специальные адреса доступа Azure, заполнять не нужно", "对免费模型启用预消耗": "Включить предварительное списание для бесплатных моделей", - "对域名启用 IP 过滤(实验性)": "Включить IP-фильтрацию для доменов (экспериментально)", + "对域名启用 IP 过滤(推荐开启)": "Включить IP-фильтрацию для доменов (рекомендуется)", "对外运营模式": "Режим внешней эксплуатации", "对象清理规则": "Правила очистки объектов", "导入": "Импорт", diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index 5c9e1e8c..dd4f1e4e 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -920,7 +920,7 @@ "在此输入系统名称": "Nhập tên hệ thống tại đây", "在此输入隐私政策内容,支持 Markdown & HTML 代码": "Nhập nội dung chính sách bảo mật tại đây, hỗ trợ mã Markdown & HTML", "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页": "Nhập nội dung trang chủ tại đây, hỗ trợ Markdown", - "域名IP过滤详细说明": "⚠️ Đây là tùy chọn thử nghiệm. Một tên miền có thể phân giải thành nhiều địa chỉ IPv4/IPv6. Nếu bật, hãy đảm bảo danh sách lọc IP bao gồm các địa chỉ này, nếu không truy cập có thể thất bại.", + "域名IP过滤详细说明": "Khuyến nghị: Khi được bật, tên miền sẽ được phân giải DNS và các IP kết quả sẽ được kiểm tra xem có thuộc dải địa chỉ riêng tư hay không, ngăn chặn hiệu quả các cuộc tấn công DNS rebinding vượt qua bảo vệ SSRF. Lưu ý: Một tên miền có thể phân giải thành nhiều địa chỉ IPv4/IPv6. Nếu bạn đã cấu hình danh sách lọc IP, hãy đảm bảo nó bao gồm các địa chỉ này, nếu không truy cập có thể thất bại.", "域名白名单": "Danh sách trắng tên miền", "域名黑名单": "Danh sách đen tên miền", "基本信息": "Thông tin cơ bản", @@ -1089,7 +1089,7 @@ "密钥预览": "Xem trước khóa", "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "Đối với các kênh chính thức, new-api đã tích hợp sẵn địa chỉ. Trừ khi đó là trang web proxy của bên thứ ba hoặc địa chỉ truy cập đặc biệt của Azure, không cần điền vào", "对免费模型启用预消耗": "Enable pre-consumption for free models", - "对域名启用 IP 过滤(实验性)": "Bật lọc IP cho tên miền (thử nghiệm)", + "对域名启用 IP 过滤(推荐开启)": "Bật lọc IP cho tên miền (khuyến nghị)", "对外运营模式": "Chế độ mặc định", "对象清理规则": "Quy tắc dọn dẹp đối tượng", "导入": "Nhập", diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index 6b754ccd..a4abffe7 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -735,7 +735,7 @@ "在此输入系统名称": "在此输入系统名称", "在此输入隐私政策内容,支持 Markdown & HTML 代码": "在此输入隐私政策内容,支持 Markdown & HTML 代码", "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页": "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页", - "域名IP过滤详细说明": "⚠️此功能为实验性选项,域名可能解析到多个 IPv4/IPv6 地址,若开启,请确保 IP 过滤列表覆盖这些地址,否则可能导致访问失败。", + "域名IP过滤详细说明": "推荐开启:开启后会对域名进行 DNS 解析并检查解析后的 IP 是否为私有地址,可有效防止 DNS 重绑定攻击绕过 SSRF 防护。注意:域名可能解析到多个 IPv4/IPv6 地址,若配置了 IP 过滤列表,请确保覆盖这些地址,否则可能导致访问失败。", "域名白名单": "域名白名单", "域名黑名单": "域名黑名单", "基本信息": "基本信息", @@ -870,7 +870,7 @@ "密钥预览": "密钥预览", "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写", "对免费模型启用预消耗": "对免费模型启用预消耗", - "对域名启用 IP 过滤(实验性)": "对域名启用 IP 过滤(实验性)", + "对域名启用 IP 过滤(推荐开启)": "对域名启用 IP 过滤(推荐开启)", "对外运营模式": "对外运营模式", "导入": "导入", "导入的配置将覆盖当前设置,是否继续?": "导入的配置将覆盖当前设置,是否继续?", diff --git a/web/src/i18n/locales/zh-TW.json b/web/src/i18n/locales/zh-TW.json index 4e2f6356..29964a50 100644 --- a/web/src/i18n/locales/zh-TW.json +++ b/web/src/i18n/locales/zh-TW.json @@ -737,7 +737,7 @@ "在此输入系统名称": "在此輸入系統名稱", "在此输入隐私政策内容,支持 Markdown & HTML 代码": "在此輸入隱私政策內容,支援 Markdown & HTML 程式碼", "在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页": "在此輸入首頁內容,支援 Markdown & HTML 程式碼,設定後首頁的狀態訊息將不再顯示。如果輸入的是一個連結,則會使用該連結作為 iframe 的 src 屬性,這允許你設定任意網頁作為首頁", - "域名IP过滤详细说明": "⚠️此功能為實驗性選項,域名可能解析到多個 IPv4/IPv6 位址,若開啟,請確保 IP 過濾列表覆蓋這些位址,否則可能導致訪問失敗。", + "域名IP过滤详细说明": "建議開啟:開啟後會對域名進行 DNS 解析並檢查解析後的 IP 是否為私有位址,可有效防止 DNS 重綁定攻擊繞過 SSRF 防護。注意:域名可能解析到多個 IPv4/IPv6 位址,若配置了 IP 過濾列表,請確保覆蓋這些位址,否則可能導致訪問失敗。", "域名白名单": "域名白名單", "域名黑名单": "域名黑名單", "基本信息": "基本資訊", @@ -873,7 +873,7 @@ "密钥预览": "密鑰預覽", "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "對於官方管道,new-api已經內置位址,除非是第三方代理站點或者Azure的特殊接入位址,否則不需要填寫", "对免费模型启用预消耗": "對免費模型啟用預消耗", - "对域名启用 IP 过滤(实验性)": "對域名啟用 IP 過濾(實驗性)", + "对域名启用 IP 过滤(推荐开启)": "對域名啟用 IP 過濾(建議開啟)", "对外运营模式": "對外運營模式", "导入": "導入", "导入的配置将覆盖当前设置,是否继续?": "導入的設定將覆蓋當前設定,是否繼續?",