mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-25 04:17:15 +00:00
fix(web): tighten database restore body-cap exemption (#5609)
This commit is contained in:
@@ -23,7 +23,7 @@ func MaxBodyBytes(limit int64, skipSuffixes ...string) gin.HandlerFunc {
|
|||||||
switch c.Request.Method {
|
switch c.Request.Method {
|
||||||
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
|
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
|
||||||
default:
|
default:
|
||||||
if c.Request.Body != nil && !hasSuffix(c.Request.URL.Path, skipSuffixes) {
|
if c.Request.Body != nil && !hasAnySuffix(c.Request.URL.Path, skipSuffixes) {
|
||||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, limit)
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,10 +32,9 @@ func MaxBodyBytes(limit int64, skipSuffixes ...string) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasSuffix reports whether path ends in any of the given suffixes.
|
func hasAnySuffix(path string, suffixes []string) bool {
|
||||||
func hasSuffix(path string, suffixes []string) bool {
|
for _, suffix := range suffixes {
|
||||||
for _, s := range suffixes {
|
if suffix != "" && strings.HasSuffix(path, suffix) {
|
||||||
if strings.HasSuffix(path, s) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func TestMaxBodyBytes(t *testing.T) {
|
|||||||
|
|
||||||
func TestMaxBodyBytesSkipSuffix(t *testing.T) {
|
func TestMaxBodyBytesSkipSuffix(t *testing.T) {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
const limit = 16
|
const limit = 10 << 20
|
||||||
|
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.Use(MaxBodyBytes(limit, "/server/importDB"))
|
r.Use(MaxBodyBytes(limit, "/server/importDB"))
|
||||||
@@ -61,20 +61,22 @@ func TestMaxBodyBytesSkipSuffix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
c.String(http.StatusOK, "ok")
|
c.String(http.StatusOK, "ok")
|
||||||
}
|
}
|
||||||
r.POST("/server/importDB", read)
|
r.POST("/prefix/panel/api/server/importDB", read)
|
||||||
|
r.POST("/prefix/panel/api/server/importDB/other", read)
|
||||||
r.POST("/x", read)
|
r.POST("/x", read)
|
||||||
|
|
||||||
// Exempt route reads an over-limit body without error.
|
large := bytes.Repeat([]byte("x"), limit+1)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/server/importDB", bytes.NewReader(make([]byte, limit*4))))
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/prefix/panel/api/server/importDB", bytes.NewReader(large)))
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Errorf("exempt route should pass through over-limit body, got %d", w.Code)
|
t.Fatalf("restore route should accept an over-limit body, got %d", w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-exempt route is still capped.
|
for _, path := range []string{"/x", "/prefix/panel/api/server/importDB/other"} {
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/x", bytes.NewReader(make([]byte, limit*4))))
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodPost, path, bytes.NewReader(large)))
|
||||||
if w.Code == http.StatusOK {
|
if w.Code == http.StatusOK {
|
||||||
t.Errorf("non-exempt over-limit POST should not succeed, got 200")
|
t.Fatalf("non-exempt path %q accepted an over-limit body", path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -168,9 +168,9 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
// Cap request bodies on state-changing requests so a stolen session/API
|
// Cap request bodies on state-changing requests so a stolen session/API
|
||||||
// token or a buggy client can't force large allocations or long DB
|
// token or a buggy client can't force large allocations or long DB
|
||||||
// transactions via bulk create/attach/import endpoints. GET/HEAD/OPTIONS
|
// transactions via bulk create/attach/import endpoints. GET/HEAD/OPTIONS
|
||||||
// carry no body and are left untouched. importDB restores a full SQLite
|
// carry no body and are left untouched. Database restore legitimately accepts
|
||||||
// backup that legitimately exceeds the cap, so it's exempt. Follow-up: make
|
// large backups and streams them to disk, so only its exact route suffix is
|
||||||
// the limit a setting.
|
// exempt. Follow-up: make the limit a setting.
|
||||||
const maxRequestBodyBytes = 10 << 20 // 10 MiB
|
const maxRequestBodyBytes = 10 << 20 // 10 MiB
|
||||||
engine.Use(middleware.MaxBodyBytes(maxRequestBodyBytes, "/panel/api/server/importDB"))
|
engine.Use(middleware.MaxBodyBytes(maxRequestBodyBytes, "/panel/api/server/importDB"))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user