Commit d2f7f9ee by Seefs Committed by GitHub

fix: limit anonymous request body (#5244)

* fix: limit anonymous request body (env ANONYMOUS_REQUEST_BODY_LIMIT_KB = 512)

* fix: allow disabling anonymous request body limit
parent 83068d11
...@@ -136,6 +136,7 @@ func initConstantEnv() { ...@@ -136,6 +136,7 @@ func initConstantEnv() {
constant.StreamScannerMaxBufferMB = GetEnvOrDefault("STREAM_SCANNER_MAX_BUFFER_MB", 128) constant.StreamScannerMaxBufferMB = GetEnvOrDefault("STREAM_SCANNER_MAX_BUFFER_MB", 128)
// MaxRequestBodyMB 请求体最大大小(解压后),用于防止超大请求/zip bomb导致内存暴涨 // MaxRequestBodyMB 请求体最大大小(解压后),用于防止超大请求/zip bomb导致内存暴涨
constant.MaxRequestBodyMB = GetEnvOrDefault("MAX_REQUEST_BODY_MB", 128) constant.MaxRequestBodyMB = GetEnvOrDefault("MAX_REQUEST_BODY_MB", 128)
constant.AnonymousRequestBodyLimitKB = GetEnvOrDefault("ANONYMOUS_REQUEST_BODY_LIMIT_KB", 512)
// ForceStreamOption 覆盖请求参数,强制返回usage信息 // ForceStreamOption 覆盖请求参数,强制返回usage信息
constant.ForceStreamOption = GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true) constant.ForceStreamOption = GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
constant.CountToken = GetEnvOrDefaultBool("CountToken", true) constant.CountToken = GetEnvOrDefaultBool("CountToken", true)
......
package common
import "github.com/QuantumNous/new-api/constant"
const defaultAnonymousRequestBodyLimitKB = 512
func GetAnonymousRequestBodyLimitBytes() int64 {
limitKB := constant.AnonymousRequestBodyLimitKB
if limitKB < 0 {
limitKB = defaultAnonymousRequestBodyLimitKB
}
return int64(limitKB) << 10
}
...@@ -10,6 +10,7 @@ var GetMediaToken bool ...@@ -10,6 +10,7 @@ var GetMediaToken bool
var GetMediaTokenNotStream bool var GetMediaTokenNotStream bool
var UpdateTask bool var UpdateTask bool
var MaxRequestBodyMB int var MaxRequestBodyMB int
var AnonymousRequestBodyLimitKB int
var AzureDefaultAPIVersion string var AzureDefaultAPIVersion string
var NotifyLimitCount int var NotifyLimitCount int
var NotificationLimitDurationMinute int var NotificationLimitDurationMinute int
......
package middleware
import (
"bytes"
"io"
"net/http"
"github.com/QuantumNous/new-api/common"
"github.com/gin-gonic/gin"
)
func AnonymousRequestBodyLimit() gin.HandlerFunc {
return func(c *gin.Context) {
maxBytes := common.GetAnonymousRequestBodyLimitBytes()
if maxBytes <= 0 || c.Request.Body == nil {
c.Next()
return
}
originalBody := c.Request.Body
limitedBody, err := readAnonymousRequestBody(originalBody, maxBytes)
_ = originalBody.Close()
if err != nil {
if common.IsRequestBodyTooLargeError(err) {
c.AbortWithStatus(http.StatusRequestEntityTooLarge)
return
}
c.AbortWithStatus(http.StatusBadRequest)
return
}
c.Request.Body = io.NopCloser(bytes.NewReader(limitedBody))
c.Request.ContentLength = int64(len(limitedBody))
c.Next()
}
}
func readAnonymousRequestBody(body io.Reader, maxBytes int64) ([]byte, error) {
data, err := io.ReadAll(io.LimitReader(body, maxBytes+1))
if err != nil {
return nil, err
}
if int64(len(data)) > maxBytes {
return nil, common.ErrRequestBodyTooLarge
}
return data, nil
}
...@@ -17,9 +17,10 @@ func SetApiRouter(router *gin.Engine) { ...@@ -17,9 +17,10 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.Use(gzip.Gzip(gzip.DefaultCompression)) apiRouter.Use(gzip.Gzip(gzip.DefaultCompression))
apiRouter.Use(middleware.BodyStorageCleanup()) // 清理请求体存储 apiRouter.Use(middleware.BodyStorageCleanup()) // 清理请求体存储
apiRouter.Use(middleware.GlobalAPIRateLimit()) apiRouter.Use(middleware.GlobalAPIRateLimit())
anonymousRequestBodyLimit := middleware.AnonymousRequestBodyLimit()
{ {
apiRouter.GET("/setup", controller.GetSetup) apiRouter.GET("/setup", controller.GetSetup)
apiRouter.POST("/setup", controller.PostSetup) apiRouter.POST("/setup", anonymousRequestBodyLimit, controller.PostSetup)
apiRouter.GET("/status", controller.GetStatus) apiRouter.GET("/status", controller.GetStatus)
apiRouter.GET("/uptime/status", controller.GetUptimeKumaStatus) apiRouter.GET("/uptime/status", controller.GetUptimeKumaStatus)
apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels) apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels)
...@@ -40,39 +41,39 @@ func SetApiRouter(router *gin.Engine) { ...@@ -40,39 +41,39 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.GET("/rankings", middleware.HeaderNavModuleAuth("rankings"), controller.GetRankings) apiRouter.GET("/rankings", middleware.HeaderNavModuleAuth("rankings"), controller.GetRankings)
apiRouter.GET("/verification", middleware.EmailVerificationRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) apiRouter.GET("/verification", middleware.EmailVerificationRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.ResetPassword)
// OAuth routes - specific routes must come before :provider wildcard // OAuth routes - specific routes must come before :provider wildcard
apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode) apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
apiRouter.POST("/oauth/email/bind", middleware.CriticalRateLimit(), controller.EmailBind) apiRouter.POST("/oauth/email/bind", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.EmailBind)
// Non-standard OAuth (WeChat, Telegram) - keep original routes // Non-standard OAuth (WeChat, Telegram) - keep original routes
apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth) apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
apiRouter.POST("/oauth/wechat/bind", middleware.CriticalRateLimit(), controller.WeChatBind) apiRouter.POST("/oauth/wechat/bind", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.WeChatBind)
apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin) apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin)
apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), controller.TelegramBind) apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), controller.TelegramBind)
// Standard OAuth providers (GitHub, Discord, OIDC, LinuxDO) - unified route // Standard OAuth providers (GitHub, Discord, OIDC, LinuxDO) - unified route
apiRouter.GET("/oauth/:provider", middleware.CriticalRateLimit(), controller.HandleOAuth) apiRouter.GET("/oauth/:provider", middleware.CriticalRateLimit(), controller.HandleOAuth)
apiRouter.GET("/ratio_config", middleware.CriticalRateLimit(), controller.GetRatioConfig) apiRouter.GET("/ratio_config", middleware.CriticalRateLimit(), controller.GetRatioConfig)
apiRouter.POST("/stripe/webhook", controller.StripeWebhook) apiRouter.POST("/stripe/webhook", anonymousRequestBodyLimit, controller.StripeWebhook)
apiRouter.POST("/creem/webhook", controller.CreemWebhook) apiRouter.POST("/creem/webhook", anonymousRequestBodyLimit, controller.CreemWebhook)
apiRouter.POST("/waffo/webhook", controller.WaffoWebhook) apiRouter.POST("/waffo/webhook", anonymousRequestBodyLimit, controller.WaffoWebhook)
// :env separates test vs prod URLs so the operator can register each // :env separates test vs prod URLs so the operator can register each
// in Pancake's matching webhook slot; handler enforces env match. // in Pancake's matching webhook slot; handler enforces env match.
apiRouter.POST("/waffo-pancake/webhook/:env", controller.WaffoPancakeWebhook) apiRouter.POST("/waffo-pancake/webhook/:env", anonymousRequestBodyLimit, controller.WaffoPancakeWebhook)
// Universal secure verification routes // Universal secure verification routes
apiRouter.POST("/verify", middleware.UserAuth(), middleware.CriticalRateLimit(), controller.UniversalVerify) apiRouter.POST("/verify", middleware.UserAuth(), middleware.CriticalRateLimit(), controller.UniversalVerify)
userRoute := apiRouter.Group("/user") userRoute := apiRouter.Group("/user")
{ {
userRoute.POST("/register", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Register) userRoute.POST("/register", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, middleware.TurnstileCheck(), controller.Register)
userRoute.POST("/login", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Login) userRoute.POST("/login", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, middleware.TurnstileCheck(), controller.Login)
userRoute.POST("/login/2fa", middleware.CriticalRateLimit(), controller.Verify2FALogin) userRoute.POST("/login/2fa", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.Verify2FALogin)
userRoute.POST("/passkey/login/begin", middleware.CriticalRateLimit(), controller.PasskeyLoginBegin) userRoute.POST("/passkey/login/begin", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.PasskeyLoginBegin)
userRoute.POST("/passkey/login/finish", middleware.CriticalRateLimit(), controller.PasskeyLoginFinish) userRoute.POST("/passkey/login/finish", middleware.CriticalRateLimit(), anonymousRequestBodyLimit, controller.PasskeyLoginFinish)
//userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog) //userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog)
userRoute.GET("/logout", controller.Logout) userRoute.GET("/logout", controller.Logout)
userRoute.POST("/epay/notify", controller.EpayNotify) userRoute.POST("/epay/notify", anonymousRequestBodyLimit, controller.EpayNotify)
userRoute.GET("/epay/notify", controller.EpayNotify) userRoute.GET("/epay/notify", controller.EpayNotify)
userRoute.GET("/groups", controller.GetUserGroups) userRoute.GET("/groups", controller.GetUserGroups)
...@@ -176,10 +177,10 @@ func SetApiRouter(router *gin.Engine) { ...@@ -176,10 +177,10 @@ func SetApiRouter(router *gin.Engine) {
} }
// Subscription payment callbacks (no auth) // Subscription payment callbacks (no auth)
apiRouter.POST("/subscription/epay/notify", controller.SubscriptionEpayNotify) apiRouter.POST("/subscription/epay/notify", anonymousRequestBodyLimit, controller.SubscriptionEpayNotify)
apiRouter.GET("/subscription/epay/notify", controller.SubscriptionEpayNotify) apiRouter.GET("/subscription/epay/notify", controller.SubscriptionEpayNotify)
apiRouter.GET("/subscription/epay/return", controller.SubscriptionEpayReturn) apiRouter.GET("/subscription/epay/return", controller.SubscriptionEpayReturn)
apiRouter.POST("/subscription/epay/return", controller.SubscriptionEpayReturn) apiRouter.POST("/subscription/epay/return", anonymousRequestBodyLimit, controller.SubscriptionEpayReturn)
optionRoute := apiRouter.Group("/option") optionRoute := apiRouter.Group("/option")
optionRoute.Use(middleware.RootAuth()) optionRoute.Use(middleware.RootAuth())
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment